summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java2
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/TenantSecretStore.java (renamed from configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantSecretStore.java)14
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/application/validation/SecretStoreValidator.java15
-rwxr-xr-xconfig-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java14
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/SecretStore.java13
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/CloudSecretStore.java64
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java46
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java2
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java14
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/SecretStoreValidator.java2
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java2
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java3
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/PrepareParams.java24
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java8
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java24
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/tenant/SecretStoreExternalIdRetriever.java40
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantSecretStoreSerializer.java59
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/MockSecretStoreValidator.java2
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java3
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/SecretStoreValidatorTest.java2
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java13
-rw-r--r--container-disc/src/main/resources/configdefinitions/cloud.config.secret-store.def5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeploymentData.java11
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java10
-rw-r--r--jdisc-cloud-aws/pom.xml6
-rw-r--r--jdisc-cloud-aws/src/main/java/com/yahoo/jdisc/cloud/aws/AwsParameterStore.java67
-rw-r--r--jdisc-cloud-aws/src/main/java/com/yahoo/jdisc/cloud/aws/AwsParameterStoreValidationHandler.java28
27 files changed, 375 insertions, 118 deletions
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
index 91ecb981e12..ebafd26f942 100644
--- a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
+++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
@@ -115,6 +115,8 @@ public interface ModelContext {
default Quota quota() { return Quota.unlimited(); }
+ default List<TenantSecretStore> tenantSecretStores() { return List.of(); }
+
/// Default setting for the gc-options attribute if not specified explicit by application
String jvmGCOptions();
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantSecretStore.java b/config-model-api/src/main/java/com/yahoo/config/model/api/TenantSecretStore.java
index 61ebddbe78c..f39a3901177 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantSecretStore.java
+++ b/config-model-api/src/main/java/com/yahoo/config/model/api/TenantSecretStore.java
@@ -1,7 +1,8 @@
// 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;
+package com.yahoo.config.model.api;
import java.util.Objects;
+import java.util.Optional;
/**
* @author olaa
@@ -11,6 +12,7 @@ public class TenantSecretStore {
private final String name;
private final String awsId;
private final String role;
+ private Optional<String> externalId;
public TenantSecretStore(String name, String awsId, String role) {
this.name = name;
@@ -30,10 +32,12 @@ public class TenantSecretStore {
return role;
}
- public boolean isValid() {
- return !name.isBlank() &&
- !awsId.isBlank() &&
- !role.isBlank();
+ public Optional<String> getExternalId() {
+ return externalId;
+ }
+
+ public void setExternalId(String externalId) {
+ this.externalId = Optional.of(externalId);
}
@Override
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/SecretStoreValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/SecretStoreValidator.java
index bafc0b8b3de..b57d2841a98 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/SecretStoreValidator.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/SecretStoreValidator.java
@@ -19,26 +19,16 @@ import java.util.List;
*/
public class SecretStoreValidator extends Validator {
- private final List<String> VALID_TYPES = List.of("oath-ckms", "cloud");
-
@Override
public void validate(VespaModel model, DeployState deployState) {
if (! deployState.isHosted()) return;
if (model.getAdmin().getApplicationType() != ApplicationType.DEFAULT) return;
for (ContainerCluster cluster : model.getContainerClusters().values()) {
-
- if (cluster.getSecretStore().isPresent() ) {
- var secretStore = (SecretStore) cluster.getSecretStore().get();
-
- if (!isValidType(secretStore.getType())) {
- throw new IllegalArgumentException("Secret store must be one of the following types: " + VALID_TYPES);
- }
- if (! secretStore.isCloud() && ! hasIdentityProvider(cluster))
+ if (cluster.getSecretStore().isPresent() && ! hasIdentityProvider(cluster))
throw new IllegalArgumentException(String.format(
"Container cluster '%s' uses a secret store, so an Athenz domain and an Athenz service" +
" must be declared in deployment.xml.", cluster.getName()));
- }
}
}
@@ -49,7 +39,4 @@ public class SecretStoreValidator extends Validator {
return false;
}
- private boolean isValidType(String type) {
- return VALID_TYPES.contains(type);
- }
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java
index 55d6c0ba6a9..a1db3c43f1c 100755
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java
@@ -4,7 +4,6 @@ package com.yahoo.vespa.model.container;
import com.yahoo.cloud.config.ClusterInfoConfig;
import com.yahoo.cloud.config.ConfigserverConfig;
import com.yahoo.cloud.config.CuratorConfig;
-import com.yahoo.cloud.config.SecretStoreConfig;
import com.yahoo.component.ComponentId;
import com.yahoo.config.application.api.ApplicationMetaData;
import com.yahoo.config.docproc.DocprocConfig;
@@ -102,8 +101,7 @@ public abstract class ContainerCluster<CONTAINER extends Container>
DocprocConfig.Producer,
ClusterInfoConfig.Producer,
ConfigserverConfig.Producer,
- CuratorConfig.Producer,
- SecretStoreConfig.Producer
+ CuratorConfig.Producer
{
/**
@@ -579,16 +577,6 @@ public abstract class ContainerCluster<CONTAINER extends Container>
builder.zookeeperLocalhostAffinity(zooKeeperLocalhostAffinity);
}
- @Override
- public void getConfig(SecretStoreConfig.Builder builder) {
- secretStore.getGroups().forEach(group ->
- builder.groups(new SecretStoreConfig.Groups.Builder()
- .name(group.name)
- .region(group.environment)
- )
- );
- }
-
private List<ClusterInfoConfig.Services.Ports.Builder> getPorts(Service service) {
List<ClusterInfoConfig.Services.Ports.Builder> builders = new ArrayList<>();
PortsMeta portsMeta = service.getPortsMeta();
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/SecretStore.java b/config-model/src/main/java/com/yahoo/vespa/model/container/SecretStore.java
index 0332192c5c6..c803168af81 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/SecretStore.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/SecretStore.java
@@ -11,11 +11,6 @@ import java.util.List;
*/
public class SecretStore {
private final List<Group> groups = new ArrayList<>();
- private final String type;
-
- public SecretStore(String type) {
- this.type = type;
- }
public void addGroup(String name, String environment) {
groups.add(new Group(name, environment));
@@ -25,14 +20,6 @@ public class SecretStore {
return ImmutableList.copyOf(groups);
}
- public String getType() {
- return type;
- }
-
- public boolean isCloud() {
- return "cloud".equals(type);
- }
-
public static class Group {
public final String name;
public final String environment;
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/CloudSecretStore.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/CloudSecretStore.java
new file mode 100644
index 00000000000..392ab382b45
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/CloudSecretStore.java
@@ -0,0 +1,64 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.model.container.xml;
+
+import com.yahoo.cloud.config.SecretStoreConfig;
+import com.yahoo.container.bundle.BundleInstantiationSpecification;
+import com.yahoo.osgi.provider.model.ComponentModel;
+import com.yahoo.vespa.model.container.component.SimpleComponent;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author olaa
+ */
+public class CloudSecretStore extends SimpleComponent implements SecretStoreConfig.Producer {
+
+ private static final String CLASS = "com.yahoo.jdisc.cloud.aws.AwsParameterStore";
+ private static final String BUNDLE = "jdisc-cloud-aws";
+
+ private final List<StoreConfig> configList;
+
+ public CloudSecretStore() {
+ super(new ComponentModel(BundleInstantiationSpecification.getFromStrings(CLASS, CLASS, BUNDLE)));
+ configList = new ArrayList<>();
+ }
+
+ public void addConfig(String name, String region, String awsId, String role, String externalId) {
+ configList.add(
+ new StoreConfig(name, region, awsId, role, externalId)
+ );
+ }
+
+ @Override
+ public void getConfig(SecretStoreConfig.Builder builder) {
+ builder.groups(
+ configList.stream()
+ .map(config -> new SecretStoreConfig.Groups.Builder()
+ .name(config.name)
+ .region(config.region)
+ .awsId(config.awsId)
+ .role(config.role)
+ .externalId(config.externalId)
+ ).collect(Collectors.toList())
+ );
+ }
+
+ class StoreConfig {
+ private final String name;
+ private final String region;
+ private final String awsId;
+ private final String role;
+ private final String externalId;
+
+ public StoreConfig(String name, String region, String awsId, String role, String externalId) {
+ this.name = name;
+ this.region = region;
+ this.awsId = awsId;
+ this.role = role;
+ this.externalId = externalId;
+ }
+
+ }
+}
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 4dc20fc668b..4efd88c7307 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
@@ -14,6 +14,7 @@ import com.yahoo.config.model.ConfigModelContext.ApplicationType;
import com.yahoo.config.model.api.ConfigServerSpec;
import com.yahoo.config.model.api.ContainerEndpoint;
import com.yahoo.config.model.api.EndpointCertificateSecrets;
+import com.yahoo.config.model.api.TenantSecretStore;
import com.yahoo.config.model.application.provider.IncludeDirs;
import com.yahoo.config.model.builder.xml.ConfigModelBuilder;
import com.yahoo.config.model.builder.xml.ConfigModelId;
@@ -180,7 +181,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
DeployState deployState = context.getDeployState();
DocumentFactoryBuilder.buildDocumentFactories(cluster, spec);
addConfiguredComponents(deployState, cluster, spec);
- addSecretStore(cluster, spec);
+ addSecretStore(cluster, spec, deployState);
addRestApis(deployState, spec, cluster);
addServlets(deployState, spec, cluster);
@@ -254,18 +255,49 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
return new SimpleComponent(new ComponentModel(idSpec, null, "zookeeper-server", configId));
}
- private void addSecretStore(ApplicationContainerCluster cluster, Element spec) {
+ private void addSecretStore(ApplicationContainerCluster cluster, Element spec, DeployState deployState) {
+
Element secretStoreElement = XML.getChild(spec, "secret-store");
if (secretStoreElement != null) {
- SecretStore secretStore = new SecretStore(secretStoreElement.getAttribute("type"));
- String attributeName = secretStore.isCloud() ? "region" : "environment";
- for (Element group : XML.getChildren(secretStoreElement, "group")) {
- secretStore.addGroup(group.getAttribute("name"), group.getAttribute(attributeName));
+ String type = secretStoreElement.getAttribute("type");
+ if ("cloud".equals(type)) {
+ addCloudSecretStore(cluster, secretStoreElement, deployState);
+ } else {
+ SecretStore secretStore = new SecretStore();
+ for (Element group : XML.getChildren(secretStoreElement, "group")) {
+ secretStore.addGroup(group.getAttribute("name"), group.getAttribute("environment"));
+ }
+ cluster.setSecretStore(secretStore);
}
- cluster.setSecretStore(secretStore);
}
}
+ private void addCloudSecretStore(ApplicationContainerCluster cluster, Element secretStoreElement, DeployState deployState) {
+ CloudSecretStore cloudSecretStore = new CloudSecretStore();
+ Map<String, TenantSecretStore> secretStoresByName = deployState.getProperties().tenantSecretStores()
+ .stream()
+ .collect(Collectors.toMap(
+ TenantSecretStore::getName,
+ store -> store
+ ));
+
+ for (Element group : XML.getChildren(secretStoreElement, "group")) {
+ String name = group.getAttribute("name");
+ String region = group.getAttribute("region");
+ TenantSecretStore secretStore = secretStoresByName.get(name);
+
+ if (secretStore == null)
+ throw new RuntimeException("No configured secret store named " + name);
+
+ if (secretStore.getExternalId().isEmpty())
+ throw new RuntimeException("No external ID has been set");
+
+ cloudSecretStore.addConfig(name, region, secretStore.getAwsId(), secretStore.getRole(), secretStore.getExternalId().get());
+ }
+
+ cluster.addComponent(cloudSecretStore);
+ }
+
private void addAthensCopperArgos(ApplicationContainerCluster cluster, ConfigModelContext context) {
if ( ! context.getDeployState().isHosted()) return;
app.getDeployment().map(DeploymentSpec::fromXml)
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 21ee9a6e1b1..54ff693dbf5 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
@@ -73,7 +73,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.config.model.api.TenantSecretStore;
import com.yahoo.vespa.curator.Curator;
import com.yahoo.vespa.curator.stats.LockStats;
import com.yahoo.vespa.curator.stats.ThreadLockStats;
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java
index af2f91b62d9..415c73ed37f 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java
@@ -25,6 +25,9 @@ import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.Zone;
+import com.yahoo.config.model.api.TenantSecretStore;
+import com.yahoo.container.jdisc.secretstore.SecretStore;
+import com.yahoo.vespa.config.server.tenant.SecretStoreExternalIdRetriever;
import com.yahoo.vespa.flags.FetchVector;
import com.yahoo.vespa.flags.FlagSource;
import com.yahoo.vespa.flags.Flags;
@@ -255,6 +258,8 @@ public class ModelContextImpl implements ModelContext {
private final Optional<AthenzDomain> athenzDomain;
private final Optional<ApplicationRoles> applicationRoles;
private final Quota quota;
+ private final List<TenantSecretStore> tenantSecretStores;
+ private final SecretStore secretStore;
private final boolean dedicatedClusterControllerCluster;
private final String jvmGcOptions;
@@ -270,6 +275,8 @@ public class ModelContextImpl implements ModelContext {
Optional<AthenzDomain> athenzDomain,
Optional<ApplicationRoles> applicationRoles,
Optional<Quota> maybeQuota,
+ List<TenantSecretStore> tenantSecretStores,
+ SecretStore secretStore,
boolean dedicatedClusterControllerCluster) {
this.featureFlags = new FeatureFlags(flagSource, applicationId);
this.applicationId = applicationId;
@@ -288,6 +295,8 @@ public class ModelContextImpl implements ModelContext {
this.applicationRoles = applicationRoles;
this.quota = maybeQuota.orElseGet(Quota::unlimited);
this.dedicatedClusterControllerCluster = dedicatedClusterControllerCluster;
+ this.tenantSecretStores = tenantSecretStores;
+ this.secretStore = secretStore;
jvmGcOptions = flagValue(flagSource, applicationId, PermanentFlags.JVM_GC_OPTIONS);
}
@@ -344,6 +353,11 @@ public class ModelContextImpl implements ModelContext {
@Override public Quota quota() { return quota; }
+ @Override
+ public List<TenantSecretStore> tenantSecretStores() {
+ return SecretStoreExternalIdRetriever.populateExternalId(secretStore, applicationId.tenant(), zone.system(), tenantSecretStores);
+ }
+
@Override public String jvmGCOptions() { return jvmGcOptions; }
@Override public boolean dedicatedClusterControllerCluster() { return dedicatedClusterControllerCluster; }
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
index d3ce5096583..72f5747e15c 100644
--- 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
@@ -8,7 +8,7 @@ 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.config.model.api.TenantSecretStore;
import com.yahoo.yolean.Exceptions;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
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 e4aae404919..ba2989164ee 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
@@ -29,7 +29,7 @@ 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 com.yahoo.config.model.api.TenantSecretStore;
import java.io.IOException;
import java.net.URLDecoder;
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java
index 5a72175e42a..fd04d4b58fc 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java
@@ -32,6 +32,7 @@ import com.yahoo.vespa.config.server.tenant.ApplicationRolesStore;
import com.yahoo.vespa.config.server.tenant.ContainerEndpointsCache;
import com.yahoo.vespa.config.server.tenant.EndpointCertificateMetadataStore;
import com.yahoo.vespa.config.server.tenant.EndpointCertificateRetriever;
+import com.yahoo.vespa.config.server.tenant.SecretStoreExternalIdRetriever;
import com.yahoo.vespa.config.server.tenant.TenantListener;
import com.yahoo.vespa.config.server.tenant.TenantRepository;
import com.yahoo.vespa.curator.Curator;
@@ -162,6 +163,8 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> {
new ApplicationRolesStore(curator, TenantRepository.getTenantPath(tenant))
.readApplicationRoles(applicationId),
zkClient.readQuota(),
+ zkClient.readTenantSecretStores(),
+ secretStore,
zkClient.readDedicatedClusterControllerCluster());
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/PrepareParams.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/PrepareParams.java
index 1cee80038e0..fc8fbe4d7d5 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/PrepareParams.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/PrepareParams.java
@@ -13,9 +13,11 @@ import com.yahoo.config.provision.TenantName;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.slime.SlimeUtils;
import com.yahoo.vespa.config.server.TimeoutBudget;
+import com.yahoo.config.model.api.TenantSecretStore;
import com.yahoo.vespa.config.server.http.SessionHandler;
import com.yahoo.vespa.config.server.tenant.ContainerEndpointSerializer;
import com.yahoo.vespa.config.server.tenant.EndpointCertificateMetadataSerializer;
+import com.yahoo.vespa.config.server.tenant.TenantSecretStoreSerializer;
import java.time.Clock;
import java.time.Duration;
@@ -43,6 +45,7 @@ public final class PrepareParams {
static final String APPLICATION_HOST_ROLE = "applicationHostRole";
static final String APPLICATION_CONTAINER_ROLE = "applicationContainerRole";
static final String QUOTA_PARAM_NAME = "quota";
+ static final String TENANT_SECRET_STORES_PARAM_NAME = "tenantSecretStores";
static final String FORCE_PARAM_NAME = "force";
static final String WAIT_FOR_RESOURCES_IN_PREPARE = "waitForResourcesInPrepare";
@@ -61,14 +64,15 @@ public final class PrepareParams {
private final Optional<AthenzDomain> athenzDomain;
private final Optional<ApplicationRoles> applicationRoles;
private final Optional<Quota> quota;
+ private final List<TenantSecretStore> tenantSecretStores;
private PrepareParams(ApplicationId applicationId, TimeoutBudget timeoutBudget, boolean ignoreValidationErrors,
boolean dryRun, boolean verbose, boolean isBootstrap, Optional<Version> vespaVersion,
List<ContainerEndpoint> containerEndpoints,
Optional<EndpointCertificateMetadata> endpointCertificateMetadata,
Optional<DockerImage> dockerImageRepository, Optional<AthenzDomain> athenzDomain,
- Optional<ApplicationRoles> applicationRoles, Optional<Quota> quota, boolean force,
- boolean waitForResourcesInPrepare) {
+ Optional<ApplicationRoles> applicationRoles, Optional<Quota> quota, List<TenantSecretStore> tenantSecretStores,
+ boolean force, boolean waitForResourcesInPrepare) {
this.timeoutBudget = timeoutBudget;
this.applicationId = Objects.requireNonNull(applicationId);
this.ignoreValidationErrors = ignoreValidationErrors;
@@ -82,6 +86,7 @@ public final class PrepareParams {
this.athenzDomain = athenzDomain;
this.applicationRoles = applicationRoles;
this.quota = quota;
+ this.tenantSecretStores = tenantSecretStores;
this.force = force;
this.waitForResourcesInPrepare = waitForResourcesInPrepare;
}
@@ -103,6 +108,7 @@ public final class PrepareParams {
private Optional<AthenzDomain> athenzDomain = Optional.empty();
private Optional<ApplicationRoles> applicationRoles = Optional.empty();
private Optional<Quota> quota = Optional.empty();
+ private List<TenantSecretStore> tenantSecretStores = List.of();
public Builder() { }
@@ -198,6 +204,13 @@ public final class PrepareParams {
return this;
}
+ public Builder tenantSecretStores(String serialized) {
+ this.tenantSecretStores = (serialized == null)
+ ? List.of()
+ : TenantSecretStoreSerializer.listFromSlime(SlimeUtils.jsonToSlime(serialized).get());
+ return this;
+ }
+
public Builder waitForResourcesInPrepare(boolean waitForResourcesInPrepare) {
this.waitForResourcesInPrepare = waitForResourcesInPrepare;
return this;
@@ -212,7 +225,7 @@ public final class PrepareParams {
return new PrepareParams(applicationId, timeoutBudget, ignoreValidationErrors, dryRun,
verbose, isBootstrap, vespaVersion, containerEndpoints,
endpointCertificateMetadata, dockerImageRepository, athenzDomain,
- applicationRoles, quota, force, waitForResourcesInPrepare);
+ applicationRoles, quota, tenantSecretStores, force, waitForResourcesInPrepare);
}
}
@@ -229,6 +242,7 @@ public final class PrepareParams {
.athenzDomain(request.getProperty(ATHENZ_DOMAIN))
.applicationRoles(ApplicationRoles.fromString(request.getProperty(APPLICATION_HOST_ROLE), request.getProperty(APPLICATION_CONTAINER_ROLE)))
.quota(request.getProperty(QUOTA_PARAM_NAME))
+ .tenantSecretStores(request.getProperty(TENANT_SECRET_STORES_PARAM_NAME))
.force(request.getBooleanProperty(FORCE_PARAM_NAME))
.waitForResourcesInPrepare(request.getBooleanProperty(WAIT_FOR_RESOURCES_IN_PREPARE))
.build();
@@ -303,4 +317,8 @@ public final class PrepareParams {
public Optional<Quota> quota() {
return quota;
}
+
+ public List<TenantSecretStore> tenantSecretStores() {
+ return tenantSecretStores;
+ }
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java
index 5e2a4b60948..2201ce47d84 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java
@@ -17,6 +17,7 @@ import com.yahoo.config.model.api.EndpointCertificateSecrets;
import com.yahoo.config.model.api.FileDistribution;
import com.yahoo.config.model.api.ModelContext;
import com.yahoo.config.model.api.Quota;
+import com.yahoo.config.model.api.TenantSecretStore;
import com.yahoo.config.provision.AllocatedHosts;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.AthenzDomain;
@@ -44,6 +45,7 @@ import com.yahoo.vespa.config.server.tenant.ContainerEndpointsCache;
import com.yahoo.vespa.config.server.tenant.EndpointCertificateMetadataSerializer;
import com.yahoo.vespa.config.server.tenant.EndpointCertificateMetadataStore;
import com.yahoo.vespa.config.server.tenant.EndpointCertificateRetriever;
+import com.yahoo.vespa.config.server.tenant.SecretStoreExternalIdRetriever;
import com.yahoo.vespa.config.server.tenant.TenantRepository;
import com.yahoo.vespa.curator.Curator;
import com.yahoo.vespa.flags.FlagSource;
@@ -116,6 +118,7 @@ public class SessionPreparer {
ApplicationPackage applicationPackage, SessionZooKeeperClient sessionZooKeeperClient) {
ApplicationId applicationId = params.getApplicationId();
boolean dedicatedClusterControllerCluster = new ApplicationCuratorDatabase(applicationId.tenant(), curator).getDedicatedClusterControllerCluster(applicationId);
+
Preparation preparation = new Preparation(hostValidator, logger, params, activeApplicationSet,
TenantRepository.getTenantPath(applicationId.tenant()),
serverDbSessionDir, applicationPackage, sessionZooKeeperClient,
@@ -204,6 +207,8 @@ public class SessionPreparer {
athenzDomain,
applicationRoles,
params.quota(),
+ params.tenantSecretStores(),
+ secretStore,
dedicatedClusterControllerCluster);
this.fileDistributionProvider = fileDistributionFactory.createProvider(serverDbSessionDir);
this.preparedModelsBuilder = new PreparedModelsBuilder(modelFactoryRegistry,
@@ -277,6 +282,7 @@ public class SessionPreparer {
prepareResult.allocatedHosts(),
athenzDomain,
params.quota(),
+ params.tenantSecretStores(),
properties.dedicatedClusterControllerCluster());
checkTimeout("write state to zookeeper");
}
@@ -327,6 +333,7 @@ public class SessionPreparer {
AllocatedHosts allocatedHosts,
Optional<AthenzDomain> athenzDomain,
Optional<Quota> quota,
+ List<TenantSecretStore> tenantSecretStores,
boolean dedicatedClusterControllerCluster) {
ZooKeeperDeployer zkDeployer = zooKeeperClient.createDeployer(deployLogger);
try {
@@ -338,6 +345,7 @@ public class SessionPreparer {
zooKeeperClient.writeDockerImageRepository(dockerImageRepository);
zooKeeperClient.writeAthenzDomain(athenzDomain);
zooKeeperClient.writeQuota(quota);
+ zooKeeperClient.writeTenantSecretStores(tenantSecretStores);
if (dedicatedClusterControllerCluster)
zooKeeperClient.writeDedicatedClusterControllerCluster();
} catch (RuntimeException | IOException e) {
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java
index 197964c2677..0757267ab0d 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java
@@ -8,6 +8,7 @@ import com.yahoo.config.application.api.ApplicationPackage;
import com.yahoo.config.application.api.DeployLogger;
import com.yahoo.config.model.api.ConfigDefinitionRepo;
import com.yahoo.config.model.api.Quota;
+import com.yahoo.config.model.api.TenantSecretStore;
import com.yahoo.config.provision.AllocatedHosts;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.AthenzDomain;
@@ -21,6 +22,7 @@ import com.yahoo.vespa.config.server.UserConfigDefinitionRepo;
import com.yahoo.vespa.config.server.deploy.ZooKeeperClient;
import com.yahoo.vespa.config.server.deploy.ZooKeeperDeployer;
import com.yahoo.vespa.config.server.tenant.TenantRepository;
+import com.yahoo.vespa.config.server.tenant.TenantSecretStoreSerializer;
import com.yahoo.vespa.config.server.zookeeper.ConfigCurator;
import com.yahoo.vespa.config.server.zookeeper.ZKApplicationPackage;
import com.yahoo.vespa.curator.Curator;
@@ -28,6 +30,7 @@ import com.yahoo.vespa.curator.transaction.CuratorOperations;
import com.yahoo.vespa.curator.transaction.CuratorTransaction;
import java.time.Instant;
+import java.util.List;
import java.util.Optional;
import java.util.logging.Level;
@@ -52,6 +55,7 @@ public class SessionZooKeeperClient {
private static final String DOCKER_IMAGE_REPOSITORY_PATH = "dockerImageRepository";
private static final String ATHENZ_DOMAIN = "athenzDomain";
private static final String QUOTA_PATH = "quota";
+ private static final String TENANT_SECRET_STORES_PATH = "tenantSecretStores";
// Whether the deployment of this particular session should use a dedicated CCC.
// The one in ApplicationCuratorDatabase signals what all future preparations should use, i.e., here.
@@ -193,6 +197,10 @@ public class SessionZooKeeperClient {
return sessionPath.append(QUOTA_PATH).getAbsolute();
}
+ private String tenantSecretStorePath() {
+ return sessionPath.append(TENANT_SECRET_STORES_PATH).getAbsolute();
+ }
+
private String dedicatedClusterControllerClusterPath() {
return sessionPath.append(DEDICATED_CLUSTER_CONTROLLER_CLUSTER_PATH).getAbsolute();
}
@@ -272,6 +280,22 @@ public class SessionZooKeeperClient {
.map(slime -> Quota.fromSlime(slime.get()));
}
+ public void writeTenantSecretStores(List<TenantSecretStore> tenantSecretStores) {
+ if (!tenantSecretStores.isEmpty()) {
+ var bytes = uncheck(() -> SlimeUtils.toJsonBytes(TenantSecretStoreSerializer.toSlime(tenantSecretStores)));
+ configCurator.putData(tenantSecretStorePath(), bytes);
+ }
+
+ }
+
+ public List<TenantSecretStore> readTenantSecretStores() {
+ if ( ! configCurator.exists(tenantSecretStorePath())) return List.of();
+ return Optional.ofNullable(configCurator.getData(tenantSecretStorePath()))
+ .map(SlimeUtils::jsonToSlime)
+ .map(slime -> TenantSecretStoreSerializer.listFromSlime(slime.get()))
+ .orElse(List.of());
+ }
+
public void writeDedicatedClusterControllerCluster() {
configCurator.createNode(dedicatedClusterControllerClusterPath());
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/SecretStoreExternalIdRetriever.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/SecretStoreExternalIdRetriever.java
new file mode 100644
index 00000000000..86eb2590ae6
--- /dev/null
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/SecretStoreExternalIdRetriever.java
@@ -0,0 +1,40 @@
+// 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.tenant;
+
+import com.yahoo.config.model.api.TenantSecretStore;
+import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.container.jdisc.secretstore.SecretStore;
+
+import java.util.List;
+
+/**
+ * @author olaa
+ */
+public class SecretStoreExternalIdRetriever {
+
+ private static final String SECRET_NAME_FORMAT = "%s.external.id.%s.%s";
+
+ public static List<TenantSecretStore> populateExternalId(SecretStore secretStore, TenantName tenant, SystemName system, List<TenantSecretStore> tenantSecretStores) {
+ tenantSecretStores.forEach(tenantSecretStore -> {
+ var secretName = secretName(tenant, system, tenantSecretStore.getName());
+ tenantSecretStore.setExternalId(secretStore.getSecret(secretName));
+ });
+ return tenantSecretStores;
+ }
+
+ private static String secretName(TenantName tenant, SystemName system, String storeName) {
+ return String.format(SECRET_NAME_FORMAT, tenantSecretGroup(system), tenant.value(), storeName);
+ }
+
+ private static String tenantSecretGroup(SystemName system) {
+ switch (system) {
+ case Public:
+ return "vespa.external.tenant.secrets";
+ case PublicCd:
+ return "vespa.external.cd.tenant.secrets";
+ default:
+ throw new IllegalArgumentException("No tenant secret store key group defined for system " + system);
+ }
+ }
+}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantSecretStoreSerializer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantSecretStoreSerializer.java
new file mode 100644
index 00000000000..f55a29efe01
--- /dev/null
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantSecretStoreSerializer.java
@@ -0,0 +1,59 @@
+// 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.tenant;
+
+import com.yahoo.config.model.api.TenantSecretStore;
+import com.yahoo.slime.ArrayTraverser;
+import com.yahoo.slime.Cursor;
+import com.yahoo.slime.Inspector;
+import com.yahoo.slime.Slime;
+import com.yahoo.slime.Type;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author olaa
+ */
+public class TenantSecretStoreSerializer {
+
+ // WARNING: Since there are multiple servers in a ZooKeeper cluster and they upgrade one by one
+ // (and rewrite all nodes on startup), changes to the serialized format must be made
+ // such that what is serialized on version N+1 can be read by version N:
+ // - ADDING FIELDS: Always ok
+ // - REMOVING FIELDS: Stop reading the field first. Stop writing it on a later version.
+ // - CHANGING THE FORMAT OF A FIELD: Don't do it bro.
+
+ private final static String awsIdField = "awsId";
+ private final static String roleField = "role";
+ private final static String nameField = "name";
+
+ public static Slime toSlime(List<TenantSecretStore> tenantSecretStores) {
+ Slime slime = new Slime();
+ Cursor cursor = slime.setArray();
+ tenantSecretStores.forEach(tenantSecretStore -> toSlime(tenantSecretStore, cursor.addObject()));
+ return slime;
+ }
+
+ public static void toSlime(TenantSecretStore tenantSecretStore, Cursor object) {
+ object.setString(awsIdField, tenantSecretStore.getAwsId());
+ object.setString(nameField, tenantSecretStore.getName());
+ object.setString(roleField, tenantSecretStore.getRole());
+ }
+
+ public static TenantSecretStore fromSlime(Inspector inspector) {
+ if (inspector.type() == Type.OBJECT) {
+ return new TenantSecretStore(
+ inspector.field(nameField).asString(),
+ inspector.field(awsIdField).asString(),
+ inspector.field(roleField).asString()
+ );
+ }
+ throw new IllegalArgumentException("Unknown format encountered for endpoint certificate metadata!");
+ }
+
+ public static List<TenantSecretStore> listFromSlime(Inspector inspector) {
+ List<TenantSecretStore> tenantSecretStores = new ArrayList<>();
+ inspector.traverse(((ArrayTraverser)(idx, store) -> tenantSecretStores.add(fromSlime(store))));
+ return tenantSecretStores;
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/MockSecretStoreValidator.java b/configserver/src/test/java/com/yahoo/vespa/config/server/MockSecretStoreValidator.java
index a46cfc4fef0..c464af404d9 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/MockSecretStoreValidator.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/MockSecretStoreValidator.java
@@ -5,7 +5,7 @@ import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.SecretStoreProvider;
import com.yahoo.restapi.StringResponse;
import com.yahoo.vespa.config.server.application.Application;
-import com.yahoo.vespa.config.server.application.TenantSecretStore;
+import com.yahoo.config.model.api.TenantSecretStore;
import com.yahoo.vespa.config.server.http.SecretStoreValidator;
/**
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java
index 718c001d930..0d066f748c8 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java
@@ -14,6 +14,7 @@ import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.test.MockApplicationPackage;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Zone;
+import com.yahoo.container.jdisc.SecretStoreProvider;
import com.yahoo.vespa.config.server.deploy.ModelContextImpl;
import com.yahoo.vespa.flags.InMemoryFlagSource;
import org.junit.Test;
@@ -72,6 +73,8 @@ public class ModelContextImplTest {
Optional.empty(),
Optional.empty(),
Optional.empty(),
+ List.of(),
+ new SecretStoreProvider().get(),
false),
Optional.empty(),
Optional.empty(),
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SecretStoreValidatorTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SecretStoreValidatorTest.java
index affab6ab835..d308cd72b55 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SecretStoreValidatorTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SecretStoreValidatorTest.java
@@ -6,7 +6,7 @@ import com.yahoo.config.model.api.Model;
import com.yahoo.config.model.api.ServiceInfo;
import com.yahoo.container.jdisc.secretstore.SecretStore;
import com.yahoo.vespa.config.server.application.Application;
-import com.yahoo.vespa.config.server.application.TenantSecretStore;
+import com.yahoo.config.model.api.TenantSecretStore;
import org.junit.Rule;
import org.junit.Test;
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java
index ac1a5547646..f977011efbc 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.config.server.session;
import com.yahoo.config.FileReference;
import com.yahoo.config.model.api.Quota;
+import com.yahoo.config.model.api.TenantSecretStore;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.TenantName;
import com.yahoo.path.Path;
@@ -18,6 +19,7 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.time.Instant;
+import java.util.List;
import java.util.Optional;
import static org.hamcrest.core.Is.is;
@@ -148,6 +150,17 @@ public class SessionZooKeeperClientTest {
assertEquals(quota, zkc.readQuota());
}
+ @Test
+ public void require_tenant_secret_stores_written_and_parsed() {
+ var secretStores = List.of(
+ new TenantSecretStore("name1", "awsId1", "role1"),
+ new TenantSecretStore("name2", "awsId2", "role2")
+ );
+ var zkc = createSessionZKClient(4);
+ zkc.writeTenantSecretStores(secretStores);
+ assertEquals(secretStores, zkc.readTenantSecretStores());
+ }
+
private void assertApplicationIdParse(long sessionId, String idString, String expectedIdString) {
SessionZooKeeperClient zkc = createSessionZKClient(sessionId);
String path = sessionPath(sessionId).append(SessionZooKeeperClient.APPLICATION_ID_PATH).getAbsolute();
diff --git a/container-disc/src/main/resources/configdefinitions/cloud.config.secret-store.def b/container-disc/src/main/resources/configdefinitions/cloud.config.secret-store.def
index 04408441889..9e4789a8597 100644
--- a/container-disc/src/main/resources/configdefinitions/cloud.config.secret-store.def
+++ b/container-disc/src/main/resources/configdefinitions/cloud.config.secret-store.def
@@ -2,4 +2,7 @@
namespace=cloud.config
groups[].name string
-groups[].region string \ No newline at end of file
+groups[].region string
+groups[].awsId string
+groups[].role string
+groups[].externalId string \ No newline at end of file
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeploymentData.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeploymentData.java
index 702c6bd0f41..4a577632746 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeploymentData.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeploymentData.java
@@ -9,7 +9,9 @@ import com.yahoo.vespa.hosted.controller.api.integration.aws.TenantRoles;
import com.yahoo.vespa.hosted.controller.api.integration.billing.Quota;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateMetadata;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ContainerEndpoint;
+import com.yahoo.vespa.hosted.controller.api.integration.secrets.TenantSecretStore;
+import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -32,6 +34,7 @@ public class DeploymentData {
private final Optional<AthenzDomain> athenzDomain;
private final Optional<TenantRoles> tenantRoles;
private final Quota quota;
+ private final List<TenantSecretStore> tenantSecretStores;
public DeploymentData(ApplicationId instance, ZoneId zone, byte[] applicationPackage, Version platform,
Set<ContainerEndpoint> containerEndpoints,
@@ -39,7 +42,8 @@ public class DeploymentData {
Optional<DockerImage> dockerImageRepo,
Optional<AthenzDomain> athenzDomain,
Optional<TenantRoles> tenantRoles,
- Quota quota) {
+ Quota quota,
+ List<TenantSecretStore> tenantSecretStores) {
this.instance = requireNonNull(instance);
this.zone = requireNonNull(zone);
this.applicationPackage = requireNonNull(applicationPackage);
@@ -50,6 +54,7 @@ public class DeploymentData {
this.athenzDomain = athenzDomain;
this.tenantRoles = tenantRoles;
this.quota = quota;
+ this.tenantSecretStores = tenantSecretStores;
}
public ApplicationId instance() {
@@ -92,4 +97,8 @@ public class DeploymentData {
return quota;
}
+ public List<TenantSecretStore> tenantSecretStores() {
+ return tenantSecretStores;
+ }
+
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
index c966a923139..0a1af8817d7 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
@@ -47,6 +47,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterId;
import com.yahoo.vespa.hosted.controller.api.integration.noderepository.RestartFilter;
+import com.yahoo.vespa.hosted.controller.api.integration.secrets.TenantSecretStore;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackageValidator;
import com.yahoo.vespa.hosted.controller.application.Deployment;
@@ -67,6 +68,7 @@ import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import com.yahoo.vespa.hosted.controller.security.AccessControl;
import com.yahoo.vespa.hosted.controller.security.Credentials;
import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
+import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
import com.yahoo.yolean.Exceptions;
@@ -594,10 +596,16 @@ public class ApplicationController {
Quota deploymentQuota = DeploymentQuotaCalculator.calculate(billingController.getQuota(application.tenant()),
asList(application.tenant()), application, zone, applicationPackage.deploymentSpec());
+ List<TenantSecretStore> tenantSecretStores = controller.tenants()
+ .get(application.tenant())
+ .filter(tenant-> tenant instanceof CloudTenant)
+ .map(tenant -> ((CloudTenant) tenant).tenantSecretStores())
+ .orElse(List.of());
+
ConfigServer.PreparedApplication preparedApplication =
configServer.deploy(new DeploymentData(application, zone, applicationPackage.zippedContent(), platform,
endpoints, endpointCertificateMetadata, dockerImageRepo, domain,
- tenantRoles, deploymentQuota));
+ tenantRoles, deploymentQuota, tenantSecretStores));
return new ActivateResult(new RevisionId(applicationPackage.hash()), preparedApplication.prepareResponse(),
applicationPackage.zippedContent().length);
diff --git a/jdisc-cloud-aws/pom.xml b/jdisc-cloud-aws/pom.xml
index dcedab50a64..bbb31d380ff 100644
--- a/jdisc-cloud-aws/pom.xml
+++ b/jdisc-cloud-aws/pom.xml
@@ -49,6 +49,12 @@
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>component</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
</dependencies>
<build>
diff --git a/jdisc-cloud-aws/src/main/java/com/yahoo/jdisc/cloud/aws/AwsParameterStore.java b/jdisc-cloud-aws/src/main/java/com/yahoo/jdisc/cloud/aws/AwsParameterStore.java
index 1636c6aeb6d..e23dc54f4c6 100644
--- a/jdisc-cloud-aws/src/main/java/com/yahoo/jdisc/cloud/aws/AwsParameterStore.java
+++ b/jdisc-cloud-aws/src/main/java/com/yahoo/jdisc/cloud/aws/AwsParameterStore.java
@@ -9,55 +9,56 @@ import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagement
import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagementClient;
import com.amazonaws.services.simplesystemsmanagement.model.GetParametersRequest;
import com.amazonaws.services.simplesystemsmanagement.model.GetParametersResult;
+import com.google.inject.Inject;
import com.yahoo.cloud.config.SecretStoreConfig;
+import com.yahoo.component.AbstractComponent;
import com.yahoo.container.jdisc.secretstore.SecretNotFoundException;
import com.yahoo.container.jdisc.secretstore.SecretStore;
/**
* @author mortent
*/
-public class AwsParameterStore implements SecretStore {
+public class AwsParameterStore extends AbstractComponent implements SecretStore {
private final VespaAwsCredentialsProvider credentialsProvider;
- private final String roleToAssume;
- private final String externalId;
- private final String region;
+ private final SecretStoreConfig secretStoreConfig;
- AwsParameterStore(VespaAwsCredentialsProvider credentialsProvider, String roleToAssume, String externalId, String region) {
- this.credentialsProvider = credentialsProvider;
- this.roleToAssume = roleToAssume;
- this.externalId = externalId;
- this.region = region;
+ @Inject
+ public AwsParameterStore(SecretStoreConfig secretStoreConfig) {
+ this.secretStoreConfig = secretStoreConfig;
+ this.credentialsProvider = new VespaAwsCredentialsProvider();
}
@Override
public String getSecret(String key) {
- AWSSecurityTokenService tokenService = AWSSecurityTokenServiceClientBuilder
- .standard()
- .withRegion(region)
- .withCredentials(credentialsProvider)
- .build();
+ for (var group : secretStoreConfig.groups()) {
+ AWSSecurityTokenService tokenService = AWSSecurityTokenServiceClientBuilder
+ .standard()
+ .withRegion(group.region())
+ .withCredentials(credentialsProvider)
+ .build();
- STSAssumeRoleSessionCredentialsProvider assumeExtAccountRole = new STSAssumeRoleSessionCredentialsProvider
- .Builder(roleToAssume, "vespa")
- .withExternalId(externalId)
- .withStsClient(tokenService)
- .build();
+ STSAssumeRoleSessionCredentialsProvider assumeExtAccountRole = new STSAssumeRoleSessionCredentialsProvider
+ .Builder(toRoleArn(group.awsId(), group.role()), "vespa")
+ .withExternalId(group.externalId())
+ .withStsClient(tokenService)
+ .build();
- AWSSimpleSystemsManagement client = AWSSimpleSystemsManagementClient.builder()
- .withCredentials(assumeExtAccountRole)
- .withRegion(region)
- .build();
+ AWSSimpleSystemsManagement client = AWSSimpleSystemsManagementClient.builder()
+ .withCredentials(assumeExtAccountRole)
+ .withRegion(group.region())
+ .build();
- GetParametersRequest parametersRequest = new GetParametersRequest().withNames(key).withWithDecryption(true);
- GetParametersResult parameters = client.getParameters(parametersRequest);
- int count = parameters.getParameters().size();
- if (count < 1) {
- throw new SecretNotFoundException("Could not find secret " + key + " using role " + roleToAssume);
- } else if (count > 1) {
- throw new RuntimeException("Found too many parameters, expected 1, but found " + count);
+ GetParametersRequest parametersRequest = new GetParametersRequest().withNames(key).withWithDecryption(true);
+ GetParametersResult parameters = client.getParameters(parametersRequest);
+ int count = parameters.getParameters().size();
+ if (count == 1) {
+ return parameters.getParameters().get(0).getValue();
+ } else if (count > 1) {
+ throw new RuntimeException("Found too many parameters, expected 1, but found " + count);
+ }
}
- return parameters.getParameters().get(0).getValue();
+ throw new SecretNotFoundException("Could not find secret " + key + " in any configured secret store");
}
@Override
@@ -65,4 +66,8 @@ public class AwsParameterStore implements SecretStore {
// TODO
return getSecret(key);
}
+
+ private String toRoleArn(String awsId, String role) {
+ return "arn:aws:iam::" + awsId + ":role/" + role;
+ }
}
diff --git a/jdisc-cloud-aws/src/main/java/com/yahoo/jdisc/cloud/aws/AwsParameterStoreValidationHandler.java b/jdisc-cloud-aws/src/main/java/com/yahoo/jdisc/cloud/aws/AwsParameterStoreValidationHandler.java
index 5d5cad2f75d..8b6e3d52d37 100644
--- a/jdisc-cloud-aws/src/main/java/com/yahoo/jdisc/cloud/aws/AwsParameterStoreValidationHandler.java
+++ b/jdisc-cloud-aws/src/main/java/com/yahoo/jdisc/cloud/aws/AwsParameterStoreValidationHandler.java
@@ -28,22 +28,14 @@ import java.util.logging.Logger;
public class AwsParameterStoreValidationHandler extends LoggingRequestHandler {
private static final Logger log = Logger.getLogger(AwsParameterStoreValidationHandler.class.getName());
- private final VespaAwsCredentialsProvider credentialsProvider;
- private final SecretStoreConfig secretStoreConfig;
+ private final AwsParameterStore awsParameterStore;
@Inject
- public AwsParameterStoreValidationHandler(Context ctx, SecretStoreConfig secretStoreConfig) {
- this(ctx, secretStoreConfig, new VespaAwsCredentialsProvider());
- }
-
-
- public AwsParameterStoreValidationHandler(Context ctx, SecretStoreConfig secretStoreConfig, VespaAwsCredentialsProvider credentialsProvider) {
+ public AwsParameterStoreValidationHandler(Context ctx, AwsParameterStore awsParameterStore) {
super(ctx);
- this.credentialsProvider = credentialsProvider;
- this.secretStoreConfig = secretStoreConfig;
+ this.awsParameterStore = awsParameterStore;
}
-
@Override
public HttpResponse handle(HttpRequest request) {
try {
@@ -66,10 +58,7 @@ public class AwsParameterStoreValidationHandler extends LoggingRequestHandler {
settings.toSlime(root.setObject("settings"));
try {
- var arn = "arn:aws:iam::" + settings.awsId + ":role/" + settings.role;
- var region = getRegion(settings);
- var store = new AwsParameterStore(this.credentialsProvider, arn, settings.externalId, region);
- store.getSecret("vespa-secret");
+ awsParameterStore.getSecret("vespa-secret");
root.setString("status", "ok");
} catch (RuntimeException e) {
root.setString("status", "error");
@@ -90,15 +79,6 @@ public class AwsParameterStoreValidationHandler extends LoggingRequestHandler {
}
}
- private String getRegion(AwsSettings settings) {
- return secretStoreConfig.groups()
- .stream()
- .filter(group -> group.name().equals(settings.name))
- .map(SecretStoreConfig.Groups::region)
- .findFirst()
- .orElseThrow(() -> new RuntimeException("No secret store named '" + settings.name + "' configured in services.xml"));
- }
-
private static class AwsSettings {
String name;
String role;