summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOla Aunrønning <olaa@verizonmedia.com>2021-03-03 16:49:01 +0100
committerOla Aunrønning <olaa@verizonmedia.com>2021-03-04 13:33:47 +0100
commit9a99b7bd1345e7c89bf842143e1bdbcdee1adfe5 (patch)
tree2fc5d724a1250e62777c2df3545e3fc383423f89
parent301f68c3b48b5ecbb94e0671fd710d0672afb046 (diff)
Include tenant secret stores in deploy call
AwsParameterStore iterates through configured stores to find secret Set up AwsParameterStore ModelContextImpl properties fetches external ID for every tenant secret store
-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;