summaryrefslogtreecommitdiffstats
path: root/configserver
diff options
context:
space:
mode:
authorOla Aunrønning <olaa@verizonmedia.com>2021-02-17 09:46:14 +0100
committerOla Aunrønning <olaa@verizonmedia.com>2021-02-18 15:02:26 +0100
commitcf904cc81f6a39a2e68c4aa7433befdaf9ca9cf3 (patch)
tree5e3f6b4c3a659e38a61a19a5bda3c7d13fc86ad5 /configserver
parent530582e559716a96cc108d7a04b7f8e18e306be3 (diff)
Parameter validation from controller to container
Diffstat (limited to 'configserver')
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java19
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantSecretStore.java62
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/SecretStoreValidator.java80
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java29
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); } };
}