diff options
author | Morten Tokle <mortent@yahooinc.com> | 2024-02-13 13:58:51 +0100 |
---|---|---|
committer | Morten Tokle <mortent@yahooinc.com> | 2024-02-13 14:06:27 +0100 |
commit | 05ab6800a9a9d2119aba89b2bf9d15aa29b11a48 (patch) | |
tree | 798fb3ef69c3d9447d04967e8332aefc673e1f6e /configserver/src | |
parent | 283af757b42ccb5ac6bfa8339a0a0674ae51c733 (diff) |
Proxy endpoint certificate secrets through EndpointCertificateSecretStore
Diffstat (limited to 'configserver/src')
11 files changed, 227 insertions, 28 deletions
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 ad785a33d5b..5017f25b2f8 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 @@ -6,6 +6,7 @@ import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.component.Version; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.model.api.ConfigDefinitionRepo; +import com.yahoo.config.model.api.EndpointCertificateSecretStore; import com.yahoo.config.model.api.Model; import com.yahoo.config.model.api.ModelContext; import com.yahoo.config.model.api.ModelFactory; @@ -39,6 +40,7 @@ import com.yahoo.vespa.model.container.ApplicationContainerCluster; import com.yahoo.vespa.model.content.cluster.ContentCluster; import java.util.Comparator; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.ExecutorService; @@ -65,6 +67,7 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> { private final SecretStore secretStore; private final ExecutorService executor; private final OnnxModelCost onnxModelCost; + private final List<EndpointCertificateSecretStore> endpointCertificateSecretStores; public ActivatedModelsBuilder(TenantName tenant, long applicationGeneration, @@ -80,7 +83,8 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> { Zone zone, ModelFactoryRegistry modelFactoryRegistry, ConfigDefinitionRepo configDefinitionRepo, - OnnxModelCost onnxModelCost) { + OnnxModelCost onnxModelCost, + List<EndpointCertificateSecretStore> endpointCertificateSecretStores) { super(modelFactoryRegistry, configserverConfig, zone, hostProvisionerProvider, new SilentDeployLogger()); this.tenant = tenant; this.applicationGeneration = applicationGeneration; @@ -93,6 +97,7 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> { this.secretStore = secretStore; this.executor = executor; this.onnxModelCost = onnxModelCost; + this.endpointCertificateSecretStores = endpointCertificateSecretStores; } @Override @@ -160,7 +165,7 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> { LegacyFlags.from(applicationPackage, flagSource), new EndpointCertificateMetadataStore(curator, TenantRepository.getTenantPath(tenant)) .readEndpointCertificateMetadata(applicationId) - .flatMap(new EndpointCertificateRetriever(secretStore)::readEndpointCertificateSecrets), + .flatMap(new EndpointCertificateRetriever(endpointCertificateSecretStores)::readEndpointCertificateSecrets), zkClient.readAthenzDomain(), zkClient.readQuota(), zkClient.readTenantSecretStores(), 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 b3772127636..fffa77fc419 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 @@ -16,6 +16,7 @@ import com.yahoo.config.application.api.FileRegistry; import com.yahoo.config.model.api.ConfigDefinitionRepo; import com.yahoo.config.model.api.ContainerEndpoint; import com.yahoo.config.model.api.EndpointCertificateMetadata; +import com.yahoo.config.model.api.EndpointCertificateSecretStore; import com.yahoo.config.model.api.EndpointCertificateSecrets; import com.yahoo.config.model.api.FileDistribution; import com.yahoo.config.model.api.OnnxModelCost; @@ -95,6 +96,7 @@ public class SessionPreparer { private final ExecutorService executor; private final BooleanFlag writeSessionData; private final OnnxModelCost onnxModelCost; + private final List<EndpointCertificateSecretStore> endpointCertificateSecretStores; public SessionPreparer(ModelFactoryRegistry modelFactoryRegistry, FileDistributionFactory fileDistributionFactory, @@ -106,7 +108,8 @@ public class SessionPreparer { Zone zone, FlagSource flagSource, SecretStore secretStore, - OnnxModelCost onnxModelCost) { + OnnxModelCost onnxModelCost, + List<EndpointCertificateSecretStore> endpointCertificateSecretStores) { this.modelFactoryRegistry = modelFactoryRegistry; this.fileDistributionFactory = fileDistributionFactory; this.hostProvisionerProvider = hostProvisionerProvider; @@ -119,6 +122,7 @@ public class SessionPreparer { this.executor = executor; this.writeSessionData = Flags.WRITE_CONFIG_SERVER_SESSION_DATA_AS_ONE_BLOB.bindTo(flagSource); this.onnxModelCost = onnxModelCost; + this.endpointCertificateSecretStores = endpointCertificateSecretStores; } ExecutorService getExecutor() { return executor; } @@ -139,7 +143,7 @@ public class SessionPreparer { Preparation preparation = new Preparation(hostValidator, logger, params, activeApplicationVersions, TenantRepository.getTenantPath(applicationId.tenant()), serverDbSessionDir, applicationPackage, sessionZooKeeperClient, - onnxModelCost); + onnxModelCost, endpointCertificateSecretStores); preparation.preprocess(); try { AllocatedHosts allocatedHosts = preparation.buildModels(now); @@ -191,7 +195,8 @@ public class SessionPreparer { Preparation(HostValidator hostValidator, DeployLogger logger, PrepareParams params, Optional<ApplicationVersions> activeApplicationVersions, Path tenantPath, File serverDbSessionDir, ApplicationPackage applicationPackage, - SessionZooKeeperClient sessionZooKeeperClient, OnnxModelCost onnxModelCost) { + SessionZooKeeperClient sessionZooKeeperClient, OnnxModelCost onnxModelCost, + List<EndpointCertificateSecretStore> endpointCertificateSecretStores) { this.logger = logger; this.params = params; this.applicationPackage = applicationPackage; @@ -201,7 +206,7 @@ public class SessionPreparer { this.vespaVersion = params.vespaVersion().orElse(Vtag.currentVersion); this.containerEndpointsCache = new ContainerEndpointsCache(tenantPath, curator); this.endpointCertificateMetadataStore = new EndpointCertificateMetadataStore(curator, tenantPath); - EndpointCertificateRetriever endpointCertificateRetriever = new EndpointCertificateRetriever(secretStore); + EndpointCertificateRetriever endpointCertificateRetriever = new EndpointCertificateRetriever(endpointCertificateSecretStores); this.endpointCertificateMetadata = params.endpointCertificateMetadata(); Optional<EndpointCertificateSecrets> endpointCertificateSecrets = endpointCertificateMetadata .or(() -> endpointCertificateMetadataStore.readEndpointCertificateMetadata(applicationId)) diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java index 2f0d8b4065d..546277c4aba 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java @@ -9,6 +9,7 @@ import com.yahoo.concurrent.StripedExecutor; 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.EndpointCertificateSecretStore; import com.yahoo.config.model.api.OnnxModelCost; import com.yahoo.config.model.application.provider.DeployData; import com.yahoo.config.model.application.provider.FilesApplicationPackage; @@ -121,6 +122,7 @@ public class SessionRepository { private final Path sessionsPath; private final TenantName tenantName; private final OnnxModelCost onnxModelCost; + private final List<EndpointCertificateSecretStore> endpointCertificateSecretStores; private final SessionCounter sessionCounter; private final SecretStore secretStore; private final HostProvisionerProvider hostProvisionerProvider; @@ -152,9 +154,11 @@ public class SessionRepository { ModelFactoryRegistry modelFactoryRegistry, ConfigDefinitionRepo configDefinitionRepo, int maxNodeSize, - OnnxModelCost onnxModelCost) { + OnnxModelCost onnxModelCost, + List<EndpointCertificateSecretStore> endpointCertificateSecretStores) { this.tenantName = tenantName; this.onnxModelCost = onnxModelCost; + this.endpointCertificateSecretStores = endpointCertificateSecretStores; sessionCounter = new SessionCounter(curator, tenantName); this.sessionsPath = TenantRepository.getSessionsPath(tenantName); this.clock = clock; @@ -561,7 +565,8 @@ public class SessionRepository { zone, modelFactoryRegistry, configDefinitionRepo, - onnxModelCost); + onnxModelCost, + endpointCertificateSecretStores); return ApplicationVersions.fromList(builder.buildModels(session.getApplicationId(), session.getDockerImageRepository(), session.getVespaVersion(), diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/DefaultEndpointCertificateSecretStore.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/DefaultEndpointCertificateSecretStore.java new file mode 100644 index 00000000000..575fd155bdd --- /dev/null +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/DefaultEndpointCertificateSecretStore.java @@ -0,0 +1,44 @@ +// Copyright Vespa.ai. 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.EndpointCertificateMetadata; +import com.yahoo.config.model.api.EndpointCertificateSecretStore; +import com.yahoo.container.jdisc.secretstore.SecretNotFoundException; +import com.yahoo.container.jdisc.secretstore.SecretStore; + +import javax.inject.Inject; +import java.util.Optional; + +public class DefaultEndpointCertificateSecretStore extends EndpointCertificateSecretStore { + + private final SecretStore secretStore; + + @Inject + public DefaultEndpointCertificateSecretStore(SecretStore secretStore) { + this.secretStore = secretStore; + } + + + @Override + public Optional<String> getPrivateKey(EndpointCertificateMetadata metadata) { + return getValue(metadata.keyName(), metadata.version()); + } + + @Override + public Optional<String> getCertificate(EndpointCertificateMetadata metadata) { + return getValue(metadata.certName(), metadata.version()); + } + + private Optional<String> getValue(String key, int version) { + try { + return Optional.ofNullable(secretStore.getSecret(key, version)); + } catch (SecretNotFoundException e) { + return Optional.empty(); + } + } + @Override + public boolean supports(EndpointCertificateMetadata.Provider provider) { + return provider == EndpointCertificateMetadata.Provider.digicert || provider == EndpointCertificateMetadata.Provider.globalsign; + } +} diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/EndpointCertificateRetriever.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/EndpointCertificateRetriever.java index 10f59290572..43baac8fec9 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/EndpointCertificateRetriever.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/EndpointCertificateRetriever.java @@ -2,14 +2,17 @@ package com.yahoo.vespa.config.server.tenant; import com.yahoo.config.model.api.EndpointCertificateMetadata; +import com.yahoo.config.model.api.EndpointCertificateSecretStore; import com.yahoo.config.model.api.EndpointCertificateSecrets; import com.yahoo.container.jdisc.secretstore.SecretStore; import com.yahoo.security.KeyUtils; import com.yahoo.security.X509CertificateUtils; +import com.yahoo.stream.CustomCollectors; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.X509Certificate; +import java.util.List; import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; @@ -19,7 +22,13 @@ import java.util.logging.Logger; * * @author andreer */ -public record EndpointCertificateRetriever(SecretStore secretStore) { +public class EndpointCertificateRetriever { + + private final List<EndpointCertificateSecretStore> secretStores; + + public EndpointCertificateRetriever(List<EndpointCertificateSecretStore> secretStores) { + this.secretStores = List.copyOf(secretStores); + } private static final Logger log = Logger.getLogger(EndpointCertificateRetriever.class.getName()); @@ -29,12 +38,17 @@ public record EndpointCertificateRetriever(SecretStore secretStore) { private EndpointCertificateSecrets readFromSecretStore(EndpointCertificateMetadata endpointCertificateMetadata) { try { - String cert = secretStore.getSecret(endpointCertificateMetadata.certName(), endpointCertificateMetadata.version()); - String key = secretStore.getSecret(endpointCertificateMetadata.keyName(), endpointCertificateMetadata.version()); + EndpointCertificateSecrets endpointCertificateSecrets = secretStores.stream() + .filter(store -> store.supports(endpointCertificateMetadata.issuer())) + .collect(CustomCollectors.singleton()) + .orElseThrow(() -> new RuntimeException("No provider of secrets for issuer " + endpointCertificateMetadata.issuer())) + .getSecret(endpointCertificateMetadata); - verifyKeyMatchesCertificate(endpointCertificateMetadata, cert, key); + if (endpointCertificateSecrets.isMissing()) + return endpointCertificateSecrets; - return new EndpointCertificateSecrets(cert, key, endpointCertificateMetadata.version()); + verifyKeyMatchesCertificate(endpointCertificateMetadata, endpointCertificateSecrets); + return endpointCertificateSecrets; } catch (RuntimeException e) { log.log(Level.WARNING, "Exception thrown during certificate retrieval", e); // Assume not ready yet @@ -42,10 +56,10 @@ public record EndpointCertificateRetriever(SecretStore secretStore) { } } - private void verifyKeyMatchesCertificate(EndpointCertificateMetadata endpointCertificateMetadata, String cert, String key) { - X509Certificate x509Certificate = X509CertificateUtils.fromPem(cert); + private void verifyKeyMatchesCertificate(EndpointCertificateMetadata endpointCertificateMetadata, EndpointCertificateSecrets endpointCertificateSecrets) { + X509Certificate x509Certificate = X509CertificateUtils.fromPem(endpointCertificateSecrets.certificate()); - PrivateKey privateKey = KeyUtils.fromPemEncodedPrivateKey(key); + PrivateKey privateKey = KeyUtils.fromPemEncodedPrivateKey(endpointCertificateSecrets.key()); PublicKey publicKey = x509Certificate.getPublicKey(); if(!X509CertificateUtils.privateKeyMatchesPublicKey(privateKey, publicKey)) { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRepository.java index 895c9819a03..8c4445f897c 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRepository.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRepository.java @@ -5,12 +5,14 @@ import com.google.common.collect.ImmutableSet; import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.cloud.config.ZookeeperServerConfig; import com.yahoo.component.annotation.Inject; +import com.yahoo.component.provider.ComponentRegistry; import com.yahoo.concurrent.DaemonThreadFactory; import com.yahoo.concurrent.Lock; import com.yahoo.concurrent.Locks; import com.yahoo.concurrent.StripedExecutor; import com.yahoo.concurrent.ThreadFactoryFactory; import com.yahoo.config.model.api.ConfigDefinitionRepo; +import com.yahoo.config.model.api.EndpointCertificateSecretStore; import com.yahoo.config.model.api.OnnxModelCost; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.TenantName; @@ -120,6 +122,7 @@ public class TenantRepository { new ScheduledThreadPoolExecutor(1, new DaemonThreadFactory("check for removed applications")); private final Curator.DirectoryCache directoryCache; private final ZookeeperServerConfig zookeeperServerConfig; + private final List<EndpointCertificateSecretStore> endpointCertificateSecretStores; private final OnnxModelCost onnxModelCost; /** @@ -141,7 +144,8 @@ public class TenantRepository { TenantListener tenantListener, ZookeeperServerConfig zookeeperServerConfig, FileDirectory fileDirectory, - OnnxModelCost onnxModelCost) { + OnnxModelCost onnxModelCost, + ComponentRegistry<EndpointCertificateSecretStore> endpointCertificateSecretStores) { this(hostRegistry, curator, metrics, @@ -161,7 +165,8 @@ public class TenantRepository { configActivationListener, tenantListener, zookeeperServerConfig, - onnxModelCost); + onnxModelCost, + endpointCertificateSecretStores.allComponents()); } public TenantRepository(HostRegistry hostRegistry, @@ -183,7 +188,8 @@ public class TenantRepository { ConfigActivationListener configActivationListener, TenantListener tenantListener, ZookeeperServerConfig zookeeperServerConfig, - OnnxModelCost onnxModelCost) { + OnnxModelCost onnxModelCost, + List<EndpointCertificateSecretStore> endpointCertificateSecretStores) { this.hostRegistry = hostRegistry; this.configserverConfig = configserverConfig; this.curator = curator; @@ -204,6 +210,7 @@ public class TenantRepository { this.configActivationListener = configActivationListener; this.tenantListener = tenantListener; this.zookeeperServerConfig = zookeeperServerConfig; + this.endpointCertificateSecretStores = endpointCertificateSecretStores; // This we should control with a feature flag. this.deployHelperExecutor = createModelBuilderExecutor(); this.onnxModelCost = onnxModelCost; @@ -360,7 +367,8 @@ public class TenantRepository { zone, flagSource, secretStore, - onnxModelCost); + onnxModelCost, + endpointCertificateSecretStores); SessionRepository sessionRepository = new SessionRepository(tenantName, applicationRepo, sessionPreparer, @@ -379,7 +387,8 @@ public class TenantRepository { modelFactoryRegistry, configDefinitionRepo, zookeeperServerConfig.juteMaxBuffer(), - onnxModelCost); + onnxModelCost, + endpointCertificateSecretStores); log.log(Level.FINE, "Adding tenant '" + tenantName + "'" + ", created " + created + ". Bootstrapping in " + Duration.between(start, clock.instant())); Tenant tenant = new Tenant(tenantName, sessionRepository, applicationRepo, created); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/MockSecretStore.java b/configserver/src/test/java/com/yahoo/vespa/config/server/MockSecretStore.java index ac97fe0ba05..cdee9f5eb65 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/MockSecretStore.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/MockSecretStore.java @@ -19,7 +19,9 @@ public class MockSecretStore implements SecretStore { @Override public String getSecret(String key, int version) { - return secrets.get(key).get(version); + if(secrets.containsKey(key)) + return secrets.get(key).get(version); + throw new SecretNotFoundException("Key not found: " + key); } public void put(String key, int version, String value) { diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java index 240826b93da..f736eab0576 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java @@ -41,6 +41,7 @@ import com.yahoo.vespa.config.server.model.TestModelFactory; import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry; import com.yahoo.vespa.config.server.provision.HostProvisionerProvider; import com.yahoo.vespa.config.server.tenant.ContainerEndpointsCache; +import com.yahoo.vespa.config.server.tenant.DefaultEndpointCertificateSecretStore; import com.yahoo.vespa.config.server.tenant.EndpointCertificateMetadataStore; import com.yahoo.vespa.config.server.tenant.EndpointCertificateRetriever; import com.yahoo.vespa.config.server.tenant.TenantRepository; @@ -134,7 +135,8 @@ public class SessionPreparerTest { zone, flagSource, secretStore, - OnnxModelCost.disabled()); + OnnxModelCost.disabled(), + List.of(new DefaultEndpointCertificateSecretStore(secretStore))); } @Test(expected = InvalidApplicationException.class) @@ -312,7 +314,7 @@ public class SessionPreparerTest { Path tenantPath = TenantRepository.getTenantPath(applicationId.tenant()); Optional<EndpointCertificateSecrets> endpointCertificateSecrets = new EndpointCertificateMetadataStore(curator, tenantPath) .readEndpointCertificateMetadata(applicationId) - .flatMap(p -> new EndpointCertificateRetriever(secretStore).readEndpointCertificateSecrets(p)); + .flatMap(p -> new EndpointCertificateRetriever(List.of(new DefaultEndpointCertificateSecretStore(secretStore))).readEndpointCertificateSecrets(p)); assertTrue(endpointCertificateSecrets.isPresent()); assertTrue(endpointCertificateSecrets.get().key().startsWith("-----BEGIN EC PRIVATE KEY")); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/EndpointCertificateRetrieverTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/EndpointCertificateRetrieverTest.java new file mode 100644 index 00000000000..75dbb7c7815 --- /dev/null +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/EndpointCertificateRetrieverTest.java @@ -0,0 +1,105 @@ +// Copyright Vespa.ai. 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.EndpointCertificateMetadata; +import com.yahoo.config.model.api.EndpointCertificateSecretStore; +import com.yahoo.config.model.api.EndpointCertificateSecrets; +import com.yahoo.security.KeyAlgorithm; +import com.yahoo.security.KeyUtils; +import com.yahoo.security.SignatureAlgorithm; +import com.yahoo.security.X509CertificateBuilder; +import com.yahoo.security.X509CertificateUtils; +import com.yahoo.vespa.config.server.MockSecretStore; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import javax.security.auth.x500.X500Principal; +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.cert.X509Certificate; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Optional; + +public class EndpointCertificateRetrieverTest { + + + private final KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); + private final X509Certificate digicertCertificate = X509CertificateBuilder.fromKeypair(keyPair, new X500Principal("CN=digicert"), + Instant.now(), Instant.now().plus(1, ChronoUnit.DAYS), SignatureAlgorithm.SHA512_WITH_ECDSA, BigInteger.valueOf(12345)).build(); + + private final X509Certificate zerosslCertificate = X509CertificateBuilder.fromKeypair(keyPair, new X500Principal("CN=zerossl"), + Instant.now(), Instant.now().plus(1, ChronoUnit.DAYS), SignatureAlgorithm.SHA512_WITH_ECDSA, BigInteger.valueOf(12345)).build(); + + + @Test + void reads_from_correct_endpoint_certificate_store() { + MockSecretStore secretStore = new MockSecretStore(); + secretStore.put("cert", 1, X509CertificateUtils.toPem(digicertCertificate)); + secretStore.put("key", 1, KeyUtils.toPem(keyPair.getPrivate())); + DefaultEndpointCertificateSecretStore defaultEndpointCertificateSecretStore = new DefaultEndpointCertificateSecretStore(secretStore); + TestEndpointCertificateSecretStore zerosslStore = new TestEndpointCertificateSecretStore(X509CertificateUtils.toPem(zerosslCertificate), KeyUtils.toPem(keyPair.getPrivate())); + + EndpointCertificateRetriever retriever = new EndpointCertificateRetriever(List.of(defaultEndpointCertificateSecretStore, zerosslStore)); + + { + Optional<EndpointCertificateSecrets> endpointCertificateSecrets = retriever.readEndpointCertificateSecrets( + new EndpointCertificateMetadata("key", "cert", 1, EndpointCertificateMetadata.Provider.digicert)); + Assertions.assertTrue(endpointCertificateSecrets.isPresent()); + Assertions.assertEquals("CN=digicert", X509CertificateUtils.fromPem(endpointCertificateSecrets.get().certificate()).getSubjectX500Principal().getName()); + } + { + Optional<EndpointCertificateSecrets> endpointCertificateSecrets = retriever.readEndpointCertificateSecrets( + new EndpointCertificateMetadata("key", "cert", 1, EndpointCertificateMetadata.Provider.zerossl)); + Assertions.assertTrue(endpointCertificateSecrets.isPresent()); + Assertions.assertEquals("CN=zerossl", X509CertificateUtils.fromPem(endpointCertificateSecrets.get().certificate()).getSubjectX500Principal().getName()); + } + } + + @Test + void returns_missing_when_cert_version_not_found() { + DefaultEndpointCertificateSecretStore defaultEndpointCertificateSecretStore = new DefaultEndpointCertificateSecretStore(new MockSecretStore()); + TestEndpointCertificateSecretStore zerosslStore = new TestEndpointCertificateSecretStore(null, null); + EndpointCertificateRetriever retriever = new EndpointCertificateRetriever(List.of(defaultEndpointCertificateSecretStore, zerosslStore)); + { + Optional<EndpointCertificateSecrets> endpointCertificateSecrets = retriever.readEndpointCertificateSecrets( + new EndpointCertificateMetadata("key", "cert", 1, EndpointCertificateMetadata.Provider.digicert)); + Assertions.assertTrue(endpointCertificateSecrets.isPresent()); + Assertions.assertTrue(endpointCertificateSecrets.get().isMissing()); + } + { + Optional<EndpointCertificateSecrets> endpointCertificateSecrets = retriever.readEndpointCertificateSecrets( + new EndpointCertificateMetadata("key", "cert", 1, EndpointCertificateMetadata.Provider.zerossl)); + Assertions.assertTrue(endpointCertificateSecrets.isPresent()); + Assertions.assertTrue(endpointCertificateSecrets.get().isMissing()); + } + } + + private static class TestEndpointCertificateSecretStore extends EndpointCertificateSecretStore { + + private final String certificate; + private final String privatekey; + + public TestEndpointCertificateSecretStore(String certificate, String privatekey) { + this.certificate = certificate; + this.privatekey = privatekey; + } + + @Override + public Optional<String> getPrivateKey(EndpointCertificateMetadata metadata) { + return Optional.ofNullable(privatekey); + } + + @Override + public Optional<String> getCertificate(EndpointCertificateMetadata metadata) { + return Optional.ofNullable(certificate); + } + + @Override + public boolean supports(EndpointCertificateMetadata.Provider provider) { + return provider == EndpointCertificateMetadata.Provider.zerossl; + } + } +} diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantRepositoryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantRepositoryTest.java index c95fee00c9c..515ecf0005e 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantRepositoryTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantRepositoryTest.java @@ -13,6 +13,7 @@ import com.yahoo.config.provision.ApplicationName; import com.yahoo.config.provision.InstanceName; 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.ConfigServerDB; import com.yahoo.vespa.config.server.MockSecretStore; import com.yahoo.vespa.config.server.ServerCache; @@ -41,6 +42,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; +import org.mockito.Mock; import org.xml.sax.SAXException; import java.io.IOException; import java.time.Clock; @@ -211,6 +213,7 @@ public class TenantRepositoryTest { private static class FailingDuringBootstrapTenantRepository extends TenantRepository { private static final FlagSource flagSource = new InMemoryFlagSource(); + private static final SecretStore mockSecretStore = new MockSecretStore(); public FailingDuringBootstrapTenantRepository(ConfigserverConfig configserverConfig) { super(new HostRegistry(), @@ -221,7 +224,7 @@ public class TenantRepositoryTest { new FileDistributionFactory(configserverConfig, new FileDirectory(configserverConfig)), flagSource, new InThreadExecutorService(), - new MockSecretStore(), + mockSecretStore, HostProvisionerProvider.empty(), configserverConfig, new ConfigServerDB(configserverConfig), @@ -232,7 +235,8 @@ public class TenantRepositoryTest { new TenantApplicationsTest.MockConfigActivationListener(), new MockTenantListener(), new ZookeeperServerConfig.Builder().myid(0).build(), - OnnxModelCost.disabled()); + OnnxModelCost.disabled(), + List.of(new DefaultEndpointCertificateSecretStore(mockSecretStore))); } @Override diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TestTenantRepository.java b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TestTenantRepository.java index d890864a51b..91f2d7194b1 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TestTenantRepository.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TestTenantRepository.java @@ -8,6 +8,7 @@ import com.yahoo.concurrent.StripedExecutor; import com.yahoo.config.model.api.ConfigDefinitionRepo; import com.yahoo.config.model.api.OnnxModelCost; import com.yahoo.config.provision.Zone; +import com.yahoo.container.jdisc.secretstore.SecretStore; import com.yahoo.vespa.config.server.ConfigServerDB; import com.yahoo.vespa.config.server.MockSecretStore; import com.yahoo.vespa.config.server.ConfigActivationListener; @@ -34,6 +35,8 @@ import java.util.List; */ public class TestTenantRepository extends TenantRepository { + private static final MockSecretStore mockSecretStore = new MockSecretStore(); + public TestTenantRepository(HostRegistry hostRegistry, Curator curator, Metrics metrics, @@ -55,7 +58,7 @@ public class TestTenantRepository extends TenantRepository { fileDistributionFactory, flagSource, new InThreadExecutorService(), - new MockSecretStore(), + mockSecretStore, hostProvisionerProvider, configserverConfig, new ConfigServerDB(configserverConfig), @@ -66,7 +69,8 @@ public class TestTenantRepository extends TenantRepository { configActivationListener, tenantListener, new ZookeeperServerConfig.Builder().myid(0).build(), - OnnxModelCost.disabled()); + OnnxModelCost.disabled(), + List.of(new DefaultEndpointCertificateSecretStore(mockSecretStore))); } public static class Builder { |