diff options
author | Ola Aunrønning <olaa@verizonmedia.com> | 2021-02-17 09:46:14 +0100 |
---|---|---|
committer | Ola Aunrønning <olaa@verizonmedia.com> | 2021-02-18 15:02:26 +0100 |
commit | cf904cc81f6a39a2e68c4aa7433befdaf9ca9cf3 (patch) | |
tree | 5e3f6b4c3a659e38a61a19a5bda3c7d13fc86ad5 /configserver | |
parent | 530582e559716a96cc108d7a04b7f8e18e306be3 (diff) |
Parameter validation from controller to container
Diffstat (limited to 'configserver')
4 files changed, 187 insertions, 3 deletions
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 81ea8d76f14..8c13d52a201 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 @@ -23,6 +23,8 @@ import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.Zone; import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.container.jdisc.SecretStoreProvider; +import com.yahoo.container.jdisc.secretstore.SecretStore; import com.yahoo.docproc.jdisc.metric.NullMetric; import com.yahoo.io.IOUtils; import com.yahoo.jdisc.Metric; @@ -39,6 +41,7 @@ import com.yahoo.vespa.config.server.application.ConfigConvergenceChecker; import com.yahoo.vespa.config.server.application.DefaultClusterReindexingStatusClient; import com.yahoo.vespa.config.server.application.FileDistributionStatus; import com.yahoo.vespa.config.server.application.HttpProxy; +import com.yahoo.vespa.config.server.http.SecretStoreValidator; import com.yahoo.vespa.config.server.application.TenantApplications; import com.yahoo.vespa.config.server.configchange.ConfigChangeActions; import com.yahoo.vespa.config.server.configchange.RefeedActions; @@ -69,6 +72,7 @@ import com.yahoo.vespa.config.server.tenant.EndpointCertificateMetadataStore; import com.yahoo.vespa.config.server.tenant.Tenant; import com.yahoo.vespa.config.server.tenant.TenantMetaData; import com.yahoo.vespa.config.server.tenant.TenantRepository; +import com.yahoo.vespa.config.server.application.TenantSecretStore; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.curator.stats.LockStats; import com.yahoo.vespa.curator.stats.ThreadLockStats; @@ -88,7 +92,6 @@ import java.time.Clock; import java.time.Duration; import java.time.Instant; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -138,6 +141,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye private final LogRetriever logRetriever; private final TesterClient testerClient; private final Metric metric; + private final SecretStoreValidator secretStoreValidator; private final ClusterReindexingStatusClient clusterReindexingStatusClient; @Inject @@ -149,7 +153,8 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye ConfigserverConfig configserverConfig, Orchestrator orchestrator, TesterClient testerClient, - Metric metric) { + Metric metric, + SecretStore secretStore) { this(tenantRepository, hostProvisionerProvider.getHostProvisioner(), infraDeployerProvider.getInfraDeployer(), @@ -161,6 +166,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye Clock.systemUTC(), testerClient, metric, + new SecretStoreValidator(secretStore), new DefaultClusterReindexingStatusClient()); } @@ -175,6 +181,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye Clock clock, TesterClient testerClient, Metric metric, + SecretStoreValidator secretStoreValidator, ClusterReindexingStatusClient clusterReindexingStatusClient) { this.tenantRepository = Objects.requireNonNull(tenantRepository); this.hostProvisioner = Objects.requireNonNull(hostProvisioner); @@ -187,6 +194,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye this.clock = Objects.requireNonNull(clock); this.testerClient = Objects.requireNonNull(testerClient); this.metric = Objects.requireNonNull(metric); + this.secretStoreValidator = Objects.requireNonNull(secretStoreValidator); this.clusterReindexingStatusClient = clusterReindexingStatusClient; } @@ -200,6 +208,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye private LogRetriever logRetriever = new LogRetriever(); private TesterClient testerClient = new TesterClient(); private Metric metric = new NullMetric(); + private SecretStoreValidator secretStoreValidator = new SecretStoreValidator(new SecretStoreProvider().get()); private FlagSource flagSource = new InMemoryFlagSource(); public Builder withTenantRepository(TenantRepository tenantRepository) { @@ -271,6 +280,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye clock, testerClient, metric, + secretStoreValidator, ClusterReindexingStatusClient.DUMMY_INSTANCE); } @@ -671,6 +681,11 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye : applicationSet.get().getAllVersions(applicationId); } + public HttpResponse validateSecretStore(ApplicationId applicationId, TenantSecretStore tenantSecretStore, String tenantSecretName) { + Application application = getApplication(applicationId); + return secretStoreValidator.validateSecretStore(application, tenantSecretStore, tenantSecretName); + } + // ---------------- Convergence ---------------------------------------------------------------- public HttpResponse checkServiceForConfigConvergence(ApplicationId applicationId, String hostAndPort, URI uri, diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantSecretStore.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantSecretStore.java new file mode 100644 index 00000000000..61ebddbe78c --- /dev/null +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantSecretStore.java @@ -0,0 +1,62 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.config.server.application; + +import java.util.Objects; + +/** + * @author olaa + */ +public class TenantSecretStore { + + private final String name; + private final String awsId; + private final String role; + + public TenantSecretStore(String name, String awsId, String role) { + this.name = name; + this.awsId = awsId; + this.role = role; + } + + public String getName() { + return name; + } + + public String getAwsId() { + return awsId; + } + + public String getRole() { + return role; + } + + public boolean isValid() { + return !name.isBlank() && + !awsId.isBlank() && + !role.isBlank(); + } + + @Override + public String toString() { + return "TenantSecretStore{" + + "name='" + name + '\'' + + ", awsId='" + awsId + '\'' + + ", role='" + role + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TenantSecretStore that = (TenantSecretStore) o; + return name.equals(that.name) && + awsId.equals(that.awsId) && + role.equals(that.role); + } + + @Override + public int hashCode() { + return Objects.hash(name, awsId, role); + } +} diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/SecretStoreValidator.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/SecretStoreValidator.java new file mode 100644 index 00000000000..d8c09eecd6d --- /dev/null +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/SecretStoreValidator.java @@ -0,0 +1,80 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.config.server.http; + +import ai.vespa.util.http.VespaHttpClientBuilder; +import com.yahoo.config.model.api.HostInfo; +import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.container.jdisc.secretstore.SecretStore; +import com.yahoo.slime.Slime; +import com.yahoo.slime.SlimeUtils; +import com.yahoo.vespa.config.server.application.Application; +import com.yahoo.vespa.config.server.application.TenantSecretStore; +import com.yahoo.yolean.Exceptions; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.impl.client.CloseableHttpClient; + +import java.io.IOException; +import java.net.URI; + +import static com.yahoo.config.model.api.container.ContainerServiceType.CONTAINER; +import static com.yahoo.yolean.Exceptions.uncheck; + +/** + * @author olaa + */ +public class SecretStoreValidator { + + private static final String AWS_PARAMETER_VALIDATION_HANDLER_POSTFIX = ":4080/validate-secret-store"; + private final SecretStore secretStore; + private final CloseableHttpClient httpClient = VespaHttpClientBuilder.create().build(); + + public SecretStoreValidator(SecretStore secretStore) { + this.secretStore = secretStore; + } + + public HttpResponse validateSecretStore(Application application, TenantSecretStore tenantSecretStore, String tenantSecretName) { + var slime = toSlime(tenantSecretStore, tenantSecretName); + var uri = getUri(application); + return postRequest(uri, slime); + } + + private Slime toSlime(TenantSecretStore tenantSecretStore, String tenantSecretName) { + var slime = new Slime(); + var cursor = slime.setObject(); + cursor.setString("externalId", secretStore.getSecret(tenantSecretName)); + cursor.setString("awsId", tenantSecretStore.getAwsId()); + cursor.setString("name", tenantSecretStore.getName()); + cursor.setString("role", tenantSecretStore.getRole()); + return slime; + } + + private URI getUri(Application application) { + var hostname = application.getModel().getHosts() + .stream() + .filter(hostInfo -> + hostInfo.getServices() + .stream() + .filter(service -> CONTAINER.serviceName.equals(service.getServiceType())) + .count() > 0) + .map(HostInfo::getHostname) + .findFirst().orElseThrow(); + return URI.create(hostname + AWS_PARAMETER_VALIDATION_HANDLER_POSTFIX); + } + + private HttpResponse postRequest(URI uri, Slime slime) { + var postRequest = new HttpPost(uri); + var data = uncheck(() -> SlimeUtils.toJsonBytes(slime)); + var entity = new ByteArrayEntity(data); + postRequest.setEntity(entity); + try (CloseableHttpResponse response = httpClient.execute(postRequest)){ + return new ProxyResponse(response); + } catch (IOException e) { + return HttpErrorResponse.internalServerError( + String.format("Failed to post request to %s: %s", uri, Exceptions.toMessageString(e)) + ); + } + } + +} 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 08ba028396f..ec714eed575 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 @@ -17,6 +17,7 @@ import com.yahoo.jdisc.Response; import com.yahoo.jdisc.application.BindingMatch; import com.yahoo.jdisc.application.UriPattern; import com.yahoo.slime.Cursor; +import com.yahoo.slime.SlimeUtils; import com.yahoo.text.StringUtilities; import com.yahoo.vespa.config.server.ApplicationRepository; import com.yahoo.vespa.config.server.application.ApplicationReindexing; @@ -28,12 +29,12 @@ import com.yahoo.vespa.config.server.http.HttpHandler; import com.yahoo.vespa.config.server.http.JSONResponse; import com.yahoo.vespa.config.server.http.NotFoundException; import com.yahoo.vespa.config.server.tenant.Tenant; +import com.yahoo.vespa.config.server.application.TenantSecretStore; import java.io.IOException; import java.net.URLDecoder; import java.time.Duration; import java.time.Instant; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -44,6 +45,7 @@ import java.util.TreeMap; import java.util.TreeSet; import java.util.stream.Stream; +import static com.yahoo.yolean.Exceptions.uncheck; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Map.Entry.comparingByKey; import static java.util.stream.Collectors.toList; @@ -68,6 +70,7 @@ public class ApplicationHandler extends HttpHandler { "http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/metrics/*", "http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/metrics/*", "http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/logs", + "http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/validate-secret-store/*", "http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/tester/*/*", "http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/tester/*", "http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/quota", @@ -227,6 +230,12 @@ public class ApplicationHandler extends HttpHandler { return createMessageResponse("Reindexing enabled"); } + if (isValidateSecretStoreRequest(request)) { + var tenantSecretStore = tenantSecretStoreFromRequest(request); + var tenantSecretName = tenantSecretNameFromRequest(request); + return applicationRepository.validateSecretStore(applicationId, tenantSecretStore, tenantSecretName); + } + throw new NotFoundException("Illegal POST request '" + request.getUri() + "'"); } @@ -350,6 +359,11 @@ public class ApplicationHandler extends HttpHandler { request.getUri().getPath().endsWith("/logs"); } + private static boolean isValidateSecretStoreRequest(HttpRequest request) { + return getBindingMatch(request).groupCount() == 8 && + request.getUri().getPath().contains("/validate-secret-store/"); + } + private static boolean isServiceConvergeListRequest(HttpRequest request) { return getBindingMatch(request).groupCount() == 7 && request.getUri().getPath().endsWith("/serviceconverge"); @@ -410,6 +424,11 @@ public class ApplicationHandler extends HttpHandler { return bm.group(8); } + private static String tenantSecretNameFromRequest(HttpRequest req) { + BindingMatch<?> bm = getBindingMatch(req); + return bm.group(8); + } + private static ApplicationId getApplicationIdFromRequest(HttpRequest req) { // Two bindings for this: with full app id or only application name BindingMatch<?> bm = getBindingMatch(req); @@ -514,6 +533,14 @@ public class ApplicationHandler extends HttpHandler { } + private TenantSecretStore tenantSecretStoreFromRequest(HttpRequest httpRequest) { + var data = uncheck(() -> SlimeUtils.jsonToSlime(httpRequest.getData().readAllBytes()).get()); + var awsId = data.field("awsId").asString(); + var name = data.field("name").asString(); + var role = data.field("role").asString(); + return new TenantSecretStore(name, awsId, role); + } + private static JSONResponse createMessageResponse(String message) { return new JSONResponse(Response.Status.OK) { { object.setString("message", message); } }; } |