diff options
Diffstat (limited to 'configserver/src/main/java/com/yahoo/vespa/config/server')
7 files changed, 148 insertions, 8 deletions
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/GlobalComponentRegistry.java b/configserver/src/main/java/com/yahoo/vespa/config/server/GlobalComponentRegistry.java index d420c3f21fe..1eb18773898 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/GlobalComponentRegistry.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/GlobalComponentRegistry.java @@ -7,6 +7,7 @@ import com.yahoo.config.model.api.ConfigDefinitionRepo; import com.yahoo.config.provision.Provisioner; import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.Zone; +import com.yahoo.container.jdisc.secretstore.SecretStore; import com.yahoo.vespa.config.server.application.PermanentApplicationPackage; import com.yahoo.vespa.config.server.host.HostRegistries; import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry; @@ -46,4 +47,5 @@ public interface GlobalComponentRegistry { StripedExecutor<TenantName> getZkWatcherExecutor(); FlagSource getFlagSource(); ExecutorService getZkCacheExecutor(); + SecretStore getSecretStore(); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistry.java b/configserver/src/main/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistry.java index ff76afd1c98..9badd19009f 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistry.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistry.java @@ -9,6 +9,7 @@ import com.yahoo.config.model.api.ConfigDefinitionRepo; import com.yahoo.config.provision.Provisioner; import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.Zone; +import com.yahoo.container.jdisc.secretstore.SecretStore; import com.yahoo.vespa.config.server.application.PermanentApplicationPackage; import com.yahoo.vespa.config.server.host.HostRegistries; import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry; @@ -48,6 +49,7 @@ public class InjectedGlobalComponentRegistry implements GlobalComponentRegistry private final Zone zone; private final ConfigServerDB configServerDB; private final FlagSource flagSource; + private final SecretStore secretStore; private final StripedExecutor<TenantName> zkWatcherExecutor; private final ExecutorService zkCacheExecutor; @@ -67,7 +69,8 @@ public class InjectedGlobalComponentRegistry implements GlobalComponentRegistry HostProvisionerProvider hostProvisionerProvider, Zone zone, ConfigServerDB configServerDB, - FlagSource flagSource) { + FlagSource flagSource, + SecretStore secretStore) { this.curator = curator; this.configCurator = configCurator; this.metrics = metrics; @@ -82,6 +85,7 @@ public class InjectedGlobalComponentRegistry implements GlobalComponentRegistry this.zone = zone; this.configServerDB = configServerDB; this.flagSource = flagSource; + this.secretStore = secretStore; this.zkWatcherExecutor = new StripedExecutor<>(); this.zkCacheExecutor = Executors.newFixedThreadPool(1, ThreadFactoryFactory.getThreadFactory(TenantRepository.class.getName())); } @@ -137,4 +141,9 @@ public class InjectedGlobalComponentRegistry implements GlobalComponentRegistry public ExecutorService getZkCacheExecutor() { return zkCacheExecutor; } + + @Override + public SecretStore getSecretStore() { + return secretStore; + } } 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 4627d350eb2..d875385d14d 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 @@ -11,6 +11,7 @@ import com.yahoo.config.model.api.ContainerEndpoint; import com.yahoo.config.model.api.HostProvisioner; import com.yahoo.config.model.api.Model; import com.yahoo.config.model.api.ModelContext; +import com.yahoo.config.model.api.TlsSecrets; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.Rotation; @@ -134,6 +135,7 @@ public class ModelContextImpl implements ModelContext { private final boolean useFdispatchByDefault; private final boolean useAdaptiveDispatch; private final boolean dispatchWithProtobuf; + private final Optional<TlsSecrets> tlsSecrets; public Properties(ApplicationId applicationId, boolean multitenantFromConfig, @@ -147,7 +149,8 @@ public class ModelContextImpl implements ModelContext { Set<ContainerEndpoint> endpoints, boolean isBootstrap, boolean isFirstTimeDeployment, - FlagSource flagSource) { + FlagSource flagSource, + Optional<TlsSecrets> tlsSecrets) { this.applicationId = applicationId; this.multitenant = multitenantFromConfig || hostedVespa || Boolean.getBoolean("multitenant"); this.configServerSpecs = configServerSpecs; @@ -168,6 +171,7 @@ public class ModelContextImpl implements ModelContext { .with(FetchVector.Dimension.APPLICATION_ID, applicationId.serializedForm()).value(); this.useAdaptiveDispatch = Flags.USE_ADAPTIVE_DISPATCH.bindTo(flagSource) .with(FetchVector.Dimension.APPLICATION_ID, applicationId.serializedForm()).value(); + this.tlsSecrets = tlsSecrets; } @Override @@ -222,6 +226,8 @@ public class ModelContextImpl implements ModelContext { @Override public boolean useAdaptiveDispatch() { return useAdaptiveDispatch; } + @Override + public Optional<TlsSecrets> tlsSecrets() { return tlsSecrets; } } } 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 117a9e0cac5..94cd30de28b 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 @@ -13,6 +13,7 @@ import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.TenantName; +import com.yahoo.container.jdisc.secretstore.SecretStore; import com.yahoo.log.LogLevel; import com.yahoo.vespa.config.server.ConfigServerSpec; import com.yahoo.vespa.config.server.GlobalComponentRegistry; @@ -28,6 +29,7 @@ import com.yahoo.vespa.config.server.session.SilentDeployLogger; import com.yahoo.vespa.config.server.tenant.ContainerEndpointsCache; import com.yahoo.vespa.config.server.tenant.Rotations; import com.yahoo.vespa.config.server.tenant.TenantRepository; +import com.yahoo.vespa.config.server.tenant.TlsSecretsKeys; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.flags.FlagSource; @@ -55,6 +57,7 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> { private final Curator curator; private final DeployLogger logger; private final FlagSource flagSource; + private final SecretStore secretStore; public ActivatedModelsBuilder(TenantName tenant, long appGeneration, @@ -73,6 +76,7 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> { this.curator = globalComponentRegistry.getCurator(); this.logger = new SilentDeployLogger(); this.flagSource = globalComponentRegistry.getFlagSource(); + this.secretStore = globalComponentRegistry.getSecretStore(); } @Override @@ -132,7 +136,8 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> { ImmutableSet.copyOf(new ContainerEndpointsCache(TenantRepository.getTenantPath(tenant), curator).read(applicationId)), false, // We may be bootstrapping, but we only know and care during prepare false, // Always false, assume no one uses it when activating - flagSource); + flagSource, + new TlsSecretsKeys(curator, TenantRepository.getTenantPath(tenant), secretStore).readTlsSecretsKeyFromZookeeper(applicationId)); } } 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 00a7625ee87..5bf70c55f9e 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 @@ -35,6 +35,7 @@ public final class PrepareParams { static final String VESPA_VERSION_PARAM_NAME = "vespaVersion"; static final String ROTATIONS_PARAM_NAME = "rotations"; static final String CONTAINER_ENDPOINTS_PARAM_NAME = "containerEndpoints"; + static final String TLS_SECRETS_KEY_NAME_PARAM_NAME = "tlsSecretsKeyName"; private final ApplicationId applicationId; private final TimeoutBudget timeoutBudget; @@ -45,10 +46,11 @@ public final class PrepareParams { private final Optional<Version> vespaVersion; private final Set<Rotation> rotations; private final List<ContainerEndpoint> containerEndpoints; + private final Optional<String> tlsSecretsKeyName; private PrepareParams(ApplicationId applicationId, TimeoutBudget timeoutBudget, boolean ignoreValidationErrors, - boolean dryRun, boolean verbose, boolean isBootstrap, Optional<Version> vespaVersion, - Set<Rotation> rotations, List<ContainerEndpoint> containerEndpoints) { + boolean dryRun, boolean verbose, boolean isBootstrap, Optional<Version> vespaVersion, Set<Rotation> rotations, + List<ContainerEndpoint> containerEndpoints, Optional<String> tlsSecretsKeyName) { this.timeoutBudget = timeoutBudget; this.applicationId = applicationId; this.ignoreValidationErrors = ignoreValidationErrors; @@ -61,6 +63,7 @@ public final class PrepareParams { if ((rotations != null && !rotations.isEmpty()) && !containerEndpoints.isEmpty()) { throw new IllegalArgumentException("Cannot set both rotations and containerEndpoints"); } + this.tlsSecretsKeyName = tlsSecretsKeyName; } public static class Builder { @@ -74,6 +77,7 @@ public final class PrepareParams { private Optional<Version> vespaVersion = Optional.empty(); private Set<Rotation> rotations; private List<ContainerEndpoint> containerEndpoints = List.of(); + private Optional<String> tlsSecretsKeyName = Optional.empty(); public Builder() { } @@ -136,12 +140,18 @@ public final class PrepareParams { if (serialized == null) return this; Slime slime = SlimeUtils.jsonToSlime(serialized); containerEndpoints = ContainerEndpointSerializer.endpointListFromSlime(slime); + return this; + } + + public Builder tlsSecretsKeyName(String tlsSecretsKeyName) { + this.tlsSecretsKeyName = Optional.ofNullable(tlsSecretsKeyName) + .filter(s -> ! s.isEmpty()); return this; } public PrepareParams build() { return new PrepareParams(applicationId, timeoutBudget, ignoreValidationErrors, dryRun, - verbose, isBootstrap, vespaVersion, rotations, containerEndpoints); + verbose, isBootstrap, vespaVersion, rotations, containerEndpoints, tlsSecretsKeyName); } } @@ -155,6 +165,7 @@ public final class PrepareParams { .vespaVersion(request.getProperty(VESPA_VERSION_PARAM_NAME)) .rotations(request.getProperty(ROTATIONS_PARAM_NAME)) .containerEndpoints(request.getProperty(CONTAINER_ENDPOINTS_PARAM_NAME)) + .tlsSecretsKeyName(request.getProperty(TLS_SECRETS_KEY_NAME_PARAM_NAME)) .build(); } @@ -212,4 +223,7 @@ public final class PrepareParams { return timeoutBudget; } + public Optional<String> tlsSecretsKeyName() { + return tlsSecretsKeyName; + } } 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 30ba9989343..54c96c0461d 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 @@ -13,11 +13,13 @@ import com.yahoo.config.application.api.DeploymentSpec; import com.yahoo.config.application.api.FileRegistry; import com.yahoo.config.model.api.ConfigDefinitionRepo; import com.yahoo.config.model.api.ModelContext; +import com.yahoo.config.model.api.TlsSecrets; import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.Rotation; import com.yahoo.config.provision.Zone; +import com.yahoo.container.jdisc.secretstore.SecretStore; import com.yahoo.lang.SettableOptional; import com.yahoo.log.LogLevel; import com.yahoo.path.Path; @@ -34,6 +36,7 @@ import com.yahoo.vespa.config.server.provision.HostProvisionerProvider; import com.yahoo.config.model.api.ContainerEndpoint; import com.yahoo.vespa.config.server.tenant.ContainerEndpointsCache; import com.yahoo.vespa.config.server.tenant.Rotations; +import com.yahoo.vespa.config.server.tenant.TlsSecretsKeys; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.flags.FlagSource; import org.xml.sax.SAXException; @@ -69,6 +72,7 @@ public class SessionPreparer { private final Curator curator; private final Zone zone; private final FlagSource flagSource; + private final SecretStore secretStore; @Inject public SessionPreparer(ModelFactoryRegistry modelFactoryRegistry, @@ -79,7 +83,8 @@ public class SessionPreparer { ConfigDefinitionRepo configDefinitionRepo, Curator curator, Zone zone, - FlagSource flagSource) { + FlagSource flagSource, + SecretStore secretStore) { this.modelFactoryRegistry = modelFactoryRegistry; this.fileDistributionFactory = fileDistributionFactory; this.hostProvisionerProvider = hostProvisionerProvider; @@ -89,6 +94,7 @@ public class SessionPreparer { this.curator = curator; this.zone = zone; this.flagSource = flagSource; + this.secretStore = secretStore; } /** @@ -112,6 +118,7 @@ public class SessionPreparer { if ( ! params.isDryRun()) { preparation.writeStateZK(); preparation.writeRotZK(); + preparation.writeTlsZK(); var globalServiceId = context.getApplicationPackage().getDeployment() .map(DeploymentSpec::fromXml) .flatMap(DeploymentSpec::globalServiceId); @@ -145,6 +152,8 @@ public class SessionPreparer { final Set<Rotation> rotationsSet; final Set<ContainerEndpoint> endpointsSet; final ModelContext.Properties properties; + private final TlsSecretsKeys tlsSecretsKeys; + private final Optional<TlsSecrets> tlsSecrets; private ApplicationPackage applicationPackage; private List<PreparedModelsBuilder.PreparedModelResult> modelResultList; @@ -165,7 +174,10 @@ public class SessionPreparer { this.rotations = new Rotations(curator, tenantPath); this.containerEndpoints = new ContainerEndpointsCache(tenantPath, curator); this.rotationsSet = getRotations(params.rotations()); + this.tlsSecretsKeys = new TlsSecretsKeys(curator, tenantPath, secretStore); + this.tlsSecrets = tlsSecretsKeys.getTlsSecrets(params.tlsSecretsKeyName(), applicationId); this.endpointsSet = getEndpoints(params.containerEndpoints()); + this.properties = new ModelContextImpl.Properties(params.getApplicationId(), configserverConfig.multitenant(), ConfigServerSpec.fromConfig(configserverConfig), @@ -178,7 +190,8 @@ public class SessionPreparer { endpointsSet, params.isBootstrap(), ! currentActiveApplicationSet.isPresent(), - context.getFlagSource()); + context.getFlagSource(), + tlsSecrets); this.preparedModelsBuilder = new PreparedModelsBuilder(modelFactoryRegistry, permanentApplicationPackage, configDefinitionRepo, @@ -238,6 +251,11 @@ public class SessionPreparer { checkTimeout("write rotations to zookeeper"); } + void writeTlsZK() { + tlsSecretsKeys.writeTlsSecretsKeyToZooKeeper(applicationId, params.tlsSecretsKeyName().orElse(null)); + checkTimeout("write tlsSecretsKey to zookeeper"); + } + void writeContainerEndpointsZK(Optional<String> globalServiceId) { if (!params.containerEndpoints().isEmpty()) { // Use endpoints from parameter when explicitly given containerEndpoints.write(applicationId, params.containerEndpoints()); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TlsSecretsKeys.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TlsSecretsKeys.java new file mode 100644 index 00000000000..eaa4916d8fc --- /dev/null +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TlsSecretsKeys.java @@ -0,0 +1,86 @@ +// Copyright 2017 Yahoo Holdings. 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.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.yahoo.config.model.api.TlsSecrets; +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.container.jdisc.secretstore.SecretStore; +import com.yahoo.path.Path; +import com.yahoo.vespa.curator.Curator; +import com.yahoo.vespa.curator.transaction.CuratorOperations; +import com.yahoo.vespa.curator.transaction.CuratorTransaction; + +import java.util.Optional; + +/** + * TLS Secret keys for applications (used to retrieve actual certificate/key from secret store). Persisted in ZooKeeper. + * + * @author andreer + */ +public class TlsSecretsKeys { + + private final Path path; + private final SecretStore secretStore; + private final Curator curator; + + public TlsSecretsKeys(Curator curator, Path tenantPath, SecretStore secretStore) { + this.curator = curator; + this.path = tenantPath.append("tlsSecretsKeys/"); + this.secretStore = secretStore; + } + + public Optional<TlsSecrets> readTlsSecretsKeyFromZookeeper(ApplicationId application) { + try { + Optional<byte[]> data = curator.getData(tlsSecretsKeyOf(application)); + if (data.isEmpty() || data.get().length == 0) return Optional.empty(); + String tlsSecretsKey = new ObjectMapper().readValue(data.get(), new TypeReference<String>() {}); + return readFromSecretStore(Optional.ofNullable(tlsSecretsKey)); + } catch (Exception e) { + throw new RuntimeException("Error reading TLS secret key of " + application, e); + } + } + + public void writeTlsSecretsKeyToZooKeeper(ApplicationId application, String tlsSecretsKey) { + if (tlsSecretsKey == null) return; + try { + byte[] data = new ObjectMapper().writeValueAsBytes(tlsSecretsKey); + curator.set(tlsSecretsKeyOf(application), data); + } catch (Exception e) { + throw new RuntimeException("Could not write TLS secret key of " + application, e); + } + } + + public Optional<TlsSecrets> getTlsSecrets(Optional<String> secretKeyname, ApplicationId applicationId) { + if (secretKeyname == null || secretKeyname.isEmpty()) { + return readTlsSecretsKeyFromZookeeper(applicationId); + } + return readFromSecretStore(secretKeyname); + } + + private Optional<TlsSecrets> readFromSecretStore(Optional<String> secretKeyname) { + if(secretKeyname.isEmpty()) return Optional.empty(); + TlsSecrets tlsSecretParameters = TlsSecrets.MISSING; + try { + String cert = secretStore.getSecret(secretKeyname.get() + "-cert"); + String key = secretStore.getSecret(secretKeyname.get() + "-key"); + tlsSecretParameters = new TlsSecrets(cert, key); + } catch (RuntimeException e) { + // Assume not ready yet +// log.log(LogLevel.DEBUG, "Could not fetch certificate/key with prefix: " + secretKeyname.get(), e); + } + return Optional.of(tlsSecretParameters); + } + + /** Returns a transaction which deletes these tls secrets key if they exist */ + public CuratorTransaction delete(ApplicationId application) { + if (!curator.exists(tlsSecretsKeyOf(application))) return CuratorTransaction.empty(curator); + return CuratorTransaction.from(CuratorOperations.delete(tlsSecretsKeyOf(application).getAbsolute()), curator); + } + + /** Returns the path storing the tls secrets key for an application */ + private Path tlsSecretsKeyOf(ApplicationId application) { + return path.append(application.serializedForm()); + } + +} |