From bdbf18c44dd718228404b39b341f46c1408fc358 Mon Sep 17 00:00:00 2001 From: jonmv Date: Wed, 27 Sep 2023 17:03:39 +0200 Subject: Set up token-tell-handler with data-plane token filter --- .../cloud/CloudTokenDataPlaneHandlerTest.java | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandlerTest.java (limited to 'jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security') diff --git a/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandlerTest.java b/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandlerTest.java new file mode 100644 index 00000000000..b84d35841da --- /dev/null +++ b/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandlerTest.java @@ -0,0 +1,52 @@ +package com.yahoo.jdisc.http.filter.security.cloud; + +import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.jdisc.http.filter.security.cloud.config.CloudTokenDataPlaneFilterConfig.Builder; +import com.yahoo.jdisc.http.filter.security.cloud.config.CloudTokenDataPlaneFilterConfig.Clients; +import com.yahoo.jdisc.http.filter.security.cloud.config.CloudTokenDataPlaneFilterConfig.Clients.Tokens; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; + +import static com.yahoo.container.jdisc.HttpRequest.createTestRequest; +import static com.yahoo.jdisc.http.HttpRequest.Method.GET; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CloudTokenDataPlaneHandlerTest { + + @Test + void testFingerprints() throws IOException { + CloudTokenDataPlaneHandler handler = new CloudTokenDataPlaneHandler( + new Builder().tokenContext("context") + .clients(new Clients.Builder().id("client1") + .permissions("read") + .tokens(new Tokens.Builder().id("id1") + .fingerprints(List.of("pinky", "ring", "middle", "index", "thumb")) + .checkAccessHashes(List.of("a", "b", "c", "d", "e")) + .expirations(List.of("", "", "", "", ""))) + .tokens(new Tokens.Builder().id("id2") + .fingerprints("toasty") + .checkAccessHashes("hash") + .expirations(""))) + .clients(new Clients.Builder().id("client2") + .permissions("write") + .tokens(new Tokens.Builder().id("id2") + .fingerprints("toasty") + .checkAccessHashes("hash") + .expirations(""))) + .build(), + Runnable::run + ); + + HttpResponse response = handler.handle(createTestRequest("", GET)); + assertEquals(200, + response.getStatus()); + assertEquals(""" + {"fingerprints":["index","middle","pinky","ring","thumb","toasty"]}""", + new ByteArrayOutputStream() {{ response.render(this); }}.toString(UTF_8)); + } + +} -- cgit v1.2.3 From dbe943625055a16243d7a33f11eb27d925f9d446 Mon Sep 17 00:00:00 2001 From: jonmv Date: Thu, 28 Sep 2023 11:47:55 +0200 Subject: Address review --- .../com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java | 2 +- .../jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandler.java | 3 ++- .../http/filter/security/cloud/CloudTokenDataPlaneHandlerTest.java | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) (limited to 'jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security') diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java index f8dfe5c82a1..7bc8d3a9608 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java @@ -673,7 +673,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder { class CloudTokenDataPlaneHandler extends Handler implements CloudTokenDataPlaneFilterConfig.Producer { CloudTokenDataPlaneHandler() { super(new ComponentModel("com.yahoo.jdisc.http.filter.security.cloud.CloudTokenDataPlaneHandler", null, "jdisc-security-filters", null)); - addServerBindings(SystemBindingPattern.fromHttpPortAndPath(Defaults.getDefaults().vespaWebServicePort(), "cloud-token-data-plane-fingerprints")); + addServerBindings(SystemBindingPattern.fromHttpPortAndPath(Defaults.getDefaults().vespaWebServicePort(), "data-plane-tokens/v1")); } @Override public void getConfig(Builder builder) { tokenFilter.getConfig(builder); } } diff --git a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandler.java b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandler.java index 2270f514fb7..affc34ecb0a 100644 --- a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandler.java +++ b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandler.java @@ -1,12 +1,13 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.jdisc.http.filter.security.cloud; +import com.yahoo.component.annotation.Inject; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.container.jdisc.ThreadedHttpRequestHandler; import com.yahoo.jdisc.http.filter.security.cloud.config.CloudTokenDataPlaneFilterConfig; import com.yahoo.restapi.SlimeJsonResponse; -import javax.inject.Inject; import java.util.List; import java.util.concurrent.Executor; diff --git a/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandlerTest.java b/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandlerTest.java index b84d35841da..7e2c1f6c815 100644 --- a/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandlerTest.java +++ b/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandlerTest.java @@ -1,3 +1,4 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.jdisc.http.filter.security.cloud; import com.yahoo.container.jdisc.HttpResponse; -- cgit v1.2.3 From 71bfc352333411f0a3bdebdb7cde032fdb2ff96a Mon Sep 17 00:00:00 2001 From: jonmv Date: Thu, 28 Sep 2023 15:30:30 +0200 Subject: Use switch expressions --- .../config/server/http/v2/ApplicationHandler.java | 39 ++++++++-------------- .../config/server/http/SessionHandlerTest.java | 4 ++- .../security/cloud/CloudTokenDataPlaneHandler.java | 3 ++ .../cloud/CloudTokenDataPlaneHandlerTest.java | 3 ++ 4 files changed, 22 insertions(+), 27 deletions(-) (limited to 'jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security') diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java index bd6e0f90b54..5206308a664 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java @@ -98,6 +98,7 @@ public class ApplicationHandler extends HttpHandler { if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/logs")) return logs(applicationId(path), request); if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/metrics/deployment")) return deploymentMetrics(applicationId(path)); if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/metrics/searchnode")) return searchNodeMetrics(applicationId(path)); + if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/quota")) return quotaUsage(applicationId(path)); if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/reindexing")) return getReindexingStatus(applicationId(path)); if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/service/{service}/{hostname}/status/{*}")) return serviceStatusPage(applicationId(path), path.get("service"), path.get("hostname"), path.getRest(), request); if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/service/{service}/{hostname}/state/v1/{*}")) return serviceStateV1(applicationId(path), path.get("service"), path.get("hostname"), path.getRest(), request); @@ -105,7 +106,6 @@ public class ApplicationHandler extends HttpHandler { if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/serviceconverge/{hostAndPort}")) return checkServiceConverge(applicationId(path), path.get("hostAndPort"), request); if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/suspended")) return isSuspended(applicationId(path)); if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/tester/{command}")) return testerRequest(applicationId(path), path.get("command"), request); - if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/quota")) return quotaUsage(applicationId(path)); return ErrorResponse.notFoundError("Nothing at " + path); } @@ -150,18 +150,11 @@ public class ApplicationHandler extends HttpHandler { } private HttpResponse serviceStatusPage(ApplicationId applicationId, String service, String hostname, HttpURL.Path pathSuffix, HttpRequest request) { - HttpURL.Path pathPrefix = HttpURL.Path.empty(); - switch (service) { - case "container-clustercontroller": - pathPrefix = pathPrefix.append("clustercontroller-status").append("v1"); - break; - case "distributor": - case "storagenode": - pathPrefix = pathPrefix.append("contentnode-status").append("v1"); - break; - default: - throw new com.yahoo.vespa.config.server.NotFoundException("No status page for service: " + service); - } + HttpURL.Path pathPrefix = switch (service) { + case "container-clustercontroller" -> HttpURL.Path.empty().append("clustercontroller-status").append("v1"); + case "distributor", "storagenode" -> HttpURL.Path.empty().append("contentnode-status").append("v1"); + default -> throw new NotFoundException("No status page for service: " + service); + }; return applicationRepository.proxyServiceHostnameRequest(applicationId, hostname, service, pathPrefix.append(pathSuffix), Query.empty().add(request.getJDiscRequest().parameters()), null); } @@ -213,19 +206,13 @@ public class ApplicationHandler extends HttpHandler { } private HttpResponse testerRequest(ApplicationId applicationId, String command, HttpRequest request) { - switch (command) { - case "status": - return applicationRepository.getTesterStatus(applicationId); - case "log": - Long after = Long.valueOf(request.getProperty("after")); - return applicationRepository.getTesterLog(applicationId, after); - case "ready": - return applicationRepository.isTesterReady(applicationId); - case "report": - return applicationRepository.getTestReport(applicationId); - default: - throw new IllegalArgumentException("Unknown tester command in request " + request.getUri().toString()); - } + return switch (command) { + case "status" -> applicationRepository.getTesterStatus(applicationId); + case "log" -> applicationRepository.getTesterLog(applicationId, Long.valueOf(request.getProperty("after"))); + case "ready" -> applicationRepository.isTesterReady(applicationId); + case "report" -> applicationRepository.getTestReport(applicationId); + default -> throw new IllegalArgumentException("Unknown tester command in request " + request.getUri().toString()); + }; } private HttpResponse quotaUsage(ApplicationId applicationId) { diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java index 6bd20a29cf8..72cfe466993 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java @@ -10,6 +10,8 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Map; +import static java.nio.charset.StandardCharsets.UTF_8; + /** * Base class for session handler tests * @@ -52,7 +54,7 @@ public class SessionHandlerTest { public static String getRenderedString(HttpResponse response) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); response.render(baos); - return baos.toString(StandardCharsets.UTF_8); + return baos.toString(UTF_8); } public enum Cmd { diff --git a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandler.java b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandler.java index affc34ecb0a..88a2e3b5a00 100644 --- a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandler.java +++ b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandler.java @@ -11,6 +11,9 @@ import com.yahoo.restapi.SlimeJsonResponse; import java.util.List; import java.util.concurrent.Executor; +/** + * @author jonmv + */ public class CloudTokenDataPlaneHandler extends ThreadedHttpRequestHandler { private final List fingerprints; diff --git a/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandlerTest.java b/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandlerTest.java index 7e2c1f6c815..f4a57f81d0f 100644 --- a/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandlerTest.java +++ b/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandlerTest.java @@ -16,6 +16,9 @@ import static com.yahoo.jdisc.http.HttpRequest.Method.GET; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertEquals; +/** + * @author jonmv + */ public class CloudTokenDataPlaneHandlerTest { @Test -- cgit v1.2.3 From 1b0cfae1d7c756c2caeef468d5d2725f81493fdc Mon Sep 17 00:00:00 2001 From: jonmv Date: Thu, 28 Sep 2023 17:17:09 +0200 Subject: Group fingerprints by token id --- .../vespa/config/server/ApplicationRepository.java | 7 +++-- .../application/ActiveTokenFingerprints.java | 6 ++-- .../application/ActiveTokenFingerprintsClient.java | 22 +++++++++------ .../config/server/http/v2/ApplicationHandler.java | 9 ++++-- .../ActiveTokenFingerprintsClientTest.java | 8 ++++-- .../server/http/v2/ApplicationHandlerTest.java | 8 ++++-- .../api/integration/configserver/ConfigServer.java | 5 +++- .../controller/integration/ConfigServerMock.java | 12 ++++---- .../security/cloud/CloudTokenDataPlaneHandler.java | 32 ++++++++++++++++++---- .../cloud/CloudTokenDataPlaneHandlerTest.java | 2 +- 10 files changed, 76 insertions(+), 35 deletions(-) (limited to 'jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security') diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java index 058606a789e..e675e00b642 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java @@ -41,6 +41,7 @@ import com.yahoo.slime.Slime; import com.yahoo.transaction.NestedTransaction; import com.yahoo.transaction.Transaction; import com.yahoo.vespa.applicationmodel.InfrastructureApplication; +import com.yahoo.vespa.config.server.application.ActiveTokenFingerprints.Token; import com.yahoo.vespa.config.server.application.ActiveTokenFingerprintsClient; import com.yahoo.vespa.config.server.application.Application; import com.yahoo.vespa.config.server.application.ApplicationCuratorDatabase; @@ -241,7 +242,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye private SecretStoreValidator secretStoreValidator = new SecretStoreValidator(new SecretStoreProvider().get()); private FlagSource flagSource = new InMemoryFlagSource(); private ConfigConvergenceChecker configConvergenceChecker = new ConfigConvergenceChecker(); - private Map> activeTokens = Map.of(); + private Map> activeTokens = Map.of(); public Builder withTenantRepository(TenantRepository tenantRepository) { this.tenantRepository = tenantRepository; @@ -303,7 +304,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye return this; } - public Builder withActiveTokens(Map> tokens) { + public Builder withActiveTokens(Map> tokens) { this.activeTokens = tokens; return this; } @@ -623,7 +624,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye return uncheck(() -> clusterReindexingStatusClient.getReindexingStatus(getApplication(applicationId))); } - public Map> activeTokenFingerprints(ApplicationId applicationId) { + public Map> activeTokenFingerprints(ApplicationId applicationId) { return activeTokenFingerprints.get(getApplication(applicationId)); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprints.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprints.java index cf67d6e4abd..9cde5e38302 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprints.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprints.java @@ -10,7 +10,9 @@ import java.util.Map; */ public interface ActiveTokenFingerprints { - /** Lists all active token fingerprints for each token-enabled container host in the application, that is currently up. */ - Map> get(ModelResult application); + /** Lists all active tokens and their fingerprints for each token-enabled container host in the application, that is currently up. */ + Map> get(ModelResult application); + + record Token(String id, List fingerprints) { } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClient.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClient.java index d5beb263155..4e9eac7a9a6 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClient.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClient.java @@ -32,6 +32,7 @@ import static com.yahoo.config.model.api.container.ContainerServiceType.CONTAINE import static com.yahoo.config.model.api.container.ContainerServiceType.QRSERVER; import static com.yahoo.slime.SlimeUtils.entriesStream; import static com.yahoo.slime.SlimeUtils.jsonToSlime; +import static java.util.stream.Collectors.toMap; import static java.util.stream.Collectors.toSet; /** @@ -46,7 +47,7 @@ public class ActiveTokenFingerprintsClient implements ActiveTokenFingerprints, A } @Override - public Map> get(ModelResult application) { + public Map> get(ModelResult application) { Set containersWithTokenFilter = application.getModel().applicationClusterInfo().stream() .flatMap(cluster -> cluster.endpoints().stream()) .filter(endpoint -> endpoint.authMethod() == AuthMethod.token) @@ -60,17 +61,17 @@ public class ActiveTokenFingerprintsClient implements ActiveTokenFingerprints, A .toList()); } - private Map> getFingerprints(List services) { - Map> fingerprints = new ConcurrentHashMap<>(); + private Map> getFingerprints(List services) { + Map> tokens = new ConcurrentHashMap<>(); Phaser phaser = new Phaser(services.size() + 1); - for (ServiceInfo service : services) getFingerprints(fingerprints, service, phaser); + for (ServiceInfo service : services) getFingerprints(tokens, service, phaser); phaser.arriveAndAwaitAdvance(); - return fingerprints; + return tokens; } // A container may be unable to provide its fingerprints for a number of reasons, which may be OK, so // we only track those containers which return an OK response, but we do require at least one such response. - private void getFingerprints(Map> hostFingerprints, ServiceInfo service, Phaser phaser) { + private void getFingerprints(Map> hostTokens, ServiceInfo service, Phaser phaser) { URI uri = HttpURL.create(Scheme.http, DomainName.of(service.getHostName()), service.getPorts().stream().filter(port -> port.getTags().stream().anyMatch("http"::equals)).findAny().get().getPort(), @@ -78,7 +79,7 @@ public class ActiveTokenFingerprintsClient implements ActiveTokenFingerprints, A .asURI(); httpClient.execute(SimpleRequestBuilder.get(uri).build(), new FutureCallback<>() { @Override public void completed(SimpleHttpResponse result) { - if (result.getCode() == 200) hostFingerprints.put(service.getHostName(), parseFingerprints(result)); + if (result.getCode() == 200) hostTokens.put(service.getHostName(), parseTokens(result)); phaser.arrive(); } @Override public void failed(Exception ex) { phaser.arrive(); } @@ -86,8 +87,11 @@ public class ActiveTokenFingerprintsClient implements ActiveTokenFingerprints, A }); } - private static List parseFingerprints(SimpleHttpResponse response) { - return entriesStream(jsonToSlime(response.getBodyBytes()).get().field("fingerprints")).map(Inspector::asString).toList(); + private static List parseTokens(SimpleHttpResponse response) { + return entriesStream(jsonToSlime(response.getBodyBytes()).get().field("tokens")) + .map(entry -> new Token(entry.field("id").asString(), + entriesStream(entry.field("fingerprints")).map(Inspector::asString).toList())) + .toList(); } private static CloseableHttpAsyncClient createHttpClient() { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java index 69d0365f089..f39feceeeb1 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java @@ -191,10 +191,15 @@ public class ApplicationHandler extends HttpHandler { private HttpResponse activeTokenFingerprints(ApplicationId applicationId) { Slime slime = new Slime(); Cursor hostsArray = slime.setObject().setArray("hosts"); - applicationRepository.activeTokenFingerprints(applicationId).forEach((host, fingerprints) -> { + applicationRepository.activeTokenFingerprints(applicationId).forEach((host, tokens) -> { Cursor hostObject = hostsArray.addObject(); hostObject.setString("host", host); - fingerprints.forEach(hostObject.setArray("fingerprints")::addString); + Cursor tokensArray = hostObject.setArray("tokens"); + tokens.forEach(token -> { + Cursor tokenObject = tokensArray.addObject(); + tokenObject.setString("id", token.id()); + token.fingerprints().forEach(tokenObject.setArray("fingerprints")::addString); + }); }); return new SlimeJsonResponse(slime); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClientTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClientTest.java index dea224d2e9b..03e379311cc 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClientTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClientTest.java @@ -17,6 +17,7 @@ import com.yahoo.config.model.api.ServiceInfo; import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.vespa.config.ConfigKey; import com.yahoo.vespa.config.buildergen.ConfigDefinition; +import com.yahoo.vespa.config.server.application.ActiveTokenFingerprints.Token; import com.yahoo.vespa.config.server.modelfactory.ModelResult; import org.junit.Rule; import org.junit.Test; @@ -54,13 +55,14 @@ public class ActiveTokenFingerprintsClientTest { String uriPath = "/data-plane-tokens/v1"; server1.stubFor(get(urlEqualTo(uriPath)).willReturn(serverError())); server2.stubFor(get(urlEqualTo(uriPath)).willReturn(okJson(""" - { "fingerprints": [ "foo", "bar", "baz" ] } + { "tokens": [ {"id": "t1", "fingerprints": [ "foo", "bar", "baz" ] } ] } """))); server3.stubFor(get(urlEqualTo(uriPath)).willReturn(aResponse().withStatus(503))); server4.stubFor(get(urlEqualTo(uriPath)).willReturn(okJson(""" - { "fingerprints": [ "quu", "qux", "fez" ] } + { "tokens": [ {"id": "t2", "fingerprints": [ "quu" ] } ] } """))); - Map> expected = Map.of("localhost", List.of("foo", "bar", "baz")); + Map> expected = Map.of("localhost", + List.of(new Token("t1", List.of("foo", "bar", "baz")))); assertEquals(expected, client.get(app)); } } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java index 5b2d5d491a1..6fb5db70b68 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java @@ -28,6 +28,7 @@ import com.yahoo.vespa.config.server.MockLogRetriever; import com.yahoo.vespa.config.server.MockProvisioner; import com.yahoo.vespa.config.server.MockSecretStoreValidator; import com.yahoo.vespa.config.server.MockTesterClient; +import com.yahoo.vespa.config.server.application.ActiveTokenFingerprints.Token; import com.yahoo.vespa.config.server.application.ApplicationCuratorDatabase; import com.yahoo.vespa.config.server.application.ApplicationReindexing; import com.yahoo.vespa.config.server.application.ClusterReindexing; @@ -117,7 +118,7 @@ public class ApplicationHandlerTest { private ManualClock clock; private List expectedEndpoints; private Availability availability; - private Map> activeTokenFingerprints; + private Map> activeTokenFingerprints; @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); @@ -243,12 +244,13 @@ public class ApplicationHandlerTest { @Test public void testGetTokenFingerprints() throws IOException { applicationRepository.deploy(testApp, prepareParams(applicationId)); - activeTokenFingerprints.putAll(Map.of("host", List.of("fingers", "toes"), + activeTokenFingerprints.putAll(Map.of("host", List.of(new Token("t1", List.of("fingers", "toes")), + new Token("t2", List.of())), "toast", List.of())); HttpResponse response = createApplicationHandler().handleGET(createTestRequest(toUrlPath(applicationId, Zone.defaultZone(), true) + "/active-token-fingerprints", GET)); assertEquals(200, response.getStatus()); assertEquals(""" - {"hosts":[{"host":"host","fingerprints":["fingers","toes"]},{"host":"toast","fingerprints":[]}]}""", + {"hosts":[{"host":"host","tokens":[{"id":"t1","fingerprints":["fingers","toes"]},{"id":"t2","fingerprints":[]}]},{"host":"toast","tokens":[]}]}""", getRenderedString(response)); } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java index 9b15311622e..31fdc9d1b64 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java @@ -6,6 +6,7 @@ import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.EndpointsChecker.Availability; import com.yahoo.config.provision.EndpointsChecker.Endpoint; +import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.zone.ZoneId; import ai.vespa.http.DomainName; import ai.vespa.http.HttpURL.Path; @@ -16,6 +17,8 @@ import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus import com.yahoo.vespa.hosted.controller.api.application.v4.model.SearchNodeMetrics; import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; import com.yahoo.vespa.hosted.controller.api.integration.LogEntry; +import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.FingerPrint; +import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.TokenId; import com.yahoo.vespa.hosted.controller.api.integration.deployment.TestReport; import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterCloud; import com.yahoo.vespa.hosted.controller.api.integration.noderepository.RestartFilter; @@ -159,6 +162,6 @@ public interface ConfigServer { String validateSecretStore(DeploymentId deploymentId, TenantSecretStore tenantSecretStore, String region, String parameterName); /** Fingerprints of active data plane tokens, per healthy host with token auth, in the given deployment. */ - Map> activeTokenFingerprints(DeploymentId deploymentId); + Map>> activeTokenFingerprints(DeploymentId deploymentId); } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java index 4a1ad86b5a4..0e5308fcef5 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java @@ -42,6 +42,8 @@ import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeFilter import com.yahoo.vespa.hosted.controller.api.integration.configserver.ProxyResponse; import com.yahoo.vespa.hosted.controller.api.integration.configserver.QuotaUsage; import com.yahoo.vespa.hosted.controller.api.integration.configserver.ServiceConvergence; +import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.FingerPrint; +import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.TokenId; import com.yahoo.vespa.hosted.controller.api.integration.deployment.TestReport; import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterCloud; import com.yahoo.vespa.hosted.controller.api.integration.dns.NameService; @@ -103,7 +105,7 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer private final Map testReport = new HashMap<>(); private final Map cloudAccounts = new HashMap<>(); private final Map> additionalCertificates = new HashMap<>(); - private final Map> activeTokenFingerprints = new HashMap<>(); + private final Map>> activeTokenFingerprints = new HashMap<>(); private List searchNodeMetrics; private Version lastPrepareVersion = null; @@ -320,8 +322,8 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer return additionalCertificates.getOrDefault(deployment, List.of()); } - public void setActiveTokenFingerprints(String hostname, List fingerprints) { - activeTokenFingerprints.put(hostname, List.copyOf(fingerprints)); + public void setActiveTokenFingerprints(HostName hostname, Map> tokens) { + activeTokenFingerprints.put(hostname, tokens); } @Override @@ -591,8 +593,8 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer } @Override - public Map> activeTokenFingerprints(DeploymentId deploymentId) { - return Map.copyOf(activeTokenFingerprints); + public Map>> activeTokenFingerprints(DeploymentId deploymentId) { + return activeTokenFingerprints; } public static class Application { diff --git a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandler.java b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandler.java index 88a2e3b5a00..09cf2abdbd3 100644 --- a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandler.java +++ b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandler.java @@ -6,30 +6,50 @@ import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.container.jdisc.ThreadedHttpRequestHandler; import com.yahoo.jdisc.http.filter.security.cloud.config.CloudTokenDataPlaneFilterConfig; +import com.yahoo.jdisc.http.filter.security.cloud.config.CloudTokenDataPlaneFilterConfig.Clients.Tokens; import com.yahoo.restapi.SlimeJsonResponse; +import com.yahoo.slime.Cursor; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; import java.util.concurrent.Executor; +import static java.util.stream.Collectors.flatMapping; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.toCollection; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; + /** * @author jonmv */ public class CloudTokenDataPlaneHandler extends ThreadedHttpRequestHandler { - private final List fingerprints; + private final Map> tokens; @Inject public CloudTokenDataPlaneHandler(CloudTokenDataPlaneFilterConfig config, Executor executor) { super(executor); - fingerprints = config.clients().stream() - .flatMap(client -> client.tokens().stream()) - .flatMap(token -> token.fingerprints().stream()) - .distinct().sorted().toList(); + tokens = new TreeMap<>(config.clients().stream() + .flatMap(client -> client.tokens().stream()) + .collect(groupingBy(Tokens::id, + flatMapping(token -> token.fingerprints().stream(), + toCollection(TreeSet::new))))); } @Override public HttpResponse handle(HttpRequest request) { - return new SlimeJsonResponse() {{ fingerprints.forEach(slime.setObject().setArray("fingerprints")::addString); }}; + return new SlimeJsonResponse() {{ + Cursor tokensArray = slime.setObject().setArray("tokens"); + tokens.forEach((id, fingerprints) -> { + Cursor tokenObject = tokensArray.addObject(); + tokenObject.setString("id", id); + fingerprints.forEach(tokenObject.setArray("fingerprints")::addString); + }); + }}; } } diff --git a/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandlerTest.java b/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandlerTest.java index f4a57f81d0f..c066dae6dca 100644 --- a/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandlerTest.java +++ b/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/cloud/CloudTokenDataPlaneHandlerTest.java @@ -49,7 +49,7 @@ public class CloudTokenDataPlaneHandlerTest { assertEquals(200, response.getStatus()); assertEquals(""" - {"fingerprints":["index","middle","pinky","ring","thumb","toasty"]}""", + {"tokens":[{"id":"id1","fingerprints":["index","middle","pinky","ring","thumb"]},{"id":"id2","fingerprints":["toasty"]}]}""", new ByteArrayOutputStream() {{ response.render(this); }}.toString(UTF_8)); } -- cgit v1.2.3