diff options
author | Bjørn Christian Seime <bjorncs@oath.com> | 2018-09-06 13:32:07 +0200 |
---|---|---|
committer | Bjørn Christian Seime <bjorncs@oath.com> | 2018-09-11 14:01:35 +0200 |
commit | 89d9b98ed0aac700ff414b85fa3e406a14529df2 (patch) | |
tree | 05c4528b0da6205efe1045864778bea20b549c2c /athenz-identity-provider-service | |
parent | 5f2187d413304eaaafed3f655b5e7a052e249a47 (diff) |
Configure https connector using SslContextFactoryProvider
Diffstat (limited to 'athenz-identity-provider-service')
4 files changed, 206 insertions, 279 deletions
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslKeyStoreConfigurator.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslKeyStoreConfigurator.java deleted file mode 100644 index 0b64f206267..00000000000 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslKeyStoreConfigurator.java +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.athenz.instanceproviderservice; - -import com.google.inject.Inject; -import com.yahoo.cloud.config.ConfigserverConfig; -import com.yahoo.component.AbstractComponent; -import com.yahoo.config.provision.Zone; -import com.yahoo.jdisc.http.ssl.SslKeyStoreConfigurator; -import com.yahoo.jdisc.http.ssl.SslKeyStoreContext; -import com.yahoo.log.LogLevel; -import com.yahoo.vespa.athenz.api.AthenzService; -import com.yahoo.vespa.athenz.client.zts.DefaultZtsClient; -import com.yahoo.vespa.athenz.client.zts.Identity; -import com.yahoo.vespa.athenz.client.zts.ZtsClient; -import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider; -import com.yahoo.security.KeyStoreBuilder; -import com.yahoo.security.KeyStoreType; -import com.yahoo.security.KeyUtils; -import com.yahoo.vespa.athenz.utils.SiaUtils; -import com.yahoo.vespa.defaults.Defaults; -import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig; - -import java.net.URI; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.security.KeyPair; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.cert.X509Certificate; -import java.time.Duration; -import java.time.Instant; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.logging.Logger; - -import static com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.Utils.getZoneConfig; - -/** - * A component that is responsible for retrieving an Athenz TLS certificate and configuring the configserver to use - * that certificate for its HTTPS endpoint. - * - * @author bjorncs - */ -@SuppressWarnings("unused") // Component injected into Jetty connector factory -public class AthenzSslKeyStoreConfigurator extends AbstractComponent implements SslKeyStoreConfigurator { - private static final Logger log = Logger.getLogger(AthenzSslKeyStoreConfigurator.class.getName()); - private static final String CERTIFICATE_ALIAS = "athenz"; - private static final Duration EXPIRATION_MARGIN = Duration.ofHours(6); - private static final Path VESPA_SIA_DIRECTORY = Paths.get(Defaults.getDefaults().underVespaHome("var/vespa/sia")); - - private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); - private final ZtsClient ztsClient; - private final KeyProvider keyProvider; - private final AthenzProviderServiceConfig.Zones zoneConfig; - private final Duration updatePeriod; - private final AthenzService configserverIdentity; - private volatile KeyStoreAndPassword currentKeyStore; - - @Inject - public AthenzSslKeyStoreConfigurator(ServiceIdentityProvider bootstrapIdentity, - KeyProvider keyProvider, - AthenzProviderServiceConfig config, - Zone zone, - ConfigserverConfig configserverConfig) { - AthenzProviderServiceConfig.Zones zoneConfig = getZoneConfig(config, zone); - AthenzService configserverIdentity = new AthenzService(zoneConfig.domain(), zoneConfig.serviceName()); - Duration updatePeriod = Duration.ofDays(config.updatePeriodDays()); - DefaultZtsClient ztsClient = new DefaultZtsClient(URI.create(zoneConfig.ztsUrl()), bootstrapIdentity); - this.ztsClient = ztsClient; - this.keyProvider = keyProvider; - this.zoneConfig = zoneConfig; - this.currentKeyStore = initializeKeystore(configserverIdentity, keyProvider, ztsClient, zoneConfig, updatePeriod); - this.updatePeriod = updatePeriod; - this.configserverIdentity = configserverIdentity; - } - - private static KeyStoreAndPassword initializeKeystore(AthenzService configserverIdentity, - KeyProvider keyProvider, - ZtsClient ztsClient, - AthenzProviderServiceConfig.Zones keystoreCacheDirectory, - Duration updatePeriod) { - return tryReadKeystoreFile(configserverIdentity, updatePeriod) - .orElseGet(() -> downloadCertificate(configserverIdentity, keyProvider, ztsClient, keystoreCacheDirectory)); - } - - private static Optional<KeyStoreAndPassword> tryReadKeystoreFile(AthenzService configserverIdentity, - Duration updatePeriod) { - Optional<X509Certificate> certificate = SiaUtils.readCertificateFile(VESPA_SIA_DIRECTORY, configserverIdentity); - if (!certificate.isPresent()) return Optional.empty(); - Optional<PrivateKey> privateKey = SiaUtils.readPrivateKeyFile(VESPA_SIA_DIRECTORY, configserverIdentity); - if (!privateKey.isPresent()) return Optional.empty(); - Instant minimumExpiration = Instant.now().plus(updatePeriod).plus(EXPIRATION_MARGIN); - boolean isExpired = certificate.get().getNotAfter().toInstant().isBefore(minimumExpiration); - if (isExpired) return Optional.empty(); - char[] password = generateKeystorePassword(); - KeyStore keyStore = KeyStoreBuilder.withType(KeyStoreType.JKS) - .withKeyEntry(CERTIFICATE_ALIAS, privateKey.get(), password, certificate.get()) - .build(); - return Optional.of(new KeyStoreAndPassword(keyStore, password)); - } - - @Override - public void configure(SslKeyStoreContext sslKeyStoreContext) { - sslKeyStoreContext.updateKeyStore(currentKeyStore.keyStore, new String(currentKeyStore.password)); - scheduler.scheduleAtFixedRate(new AthenzCertificateUpdater(sslKeyStoreContext), - updatePeriod.toDays()/*initial delay*/, - updatePeriod.toDays(), - TimeUnit.DAYS); - } - - @Override - public void deconstruct() { - try { - scheduler.shutdownNow(); - scheduler.awaitTermination(30, TimeUnit.SECONDS); - ztsClient.close(); - } catch (InterruptedException e) { - throw new RuntimeException("Failed to shutdown Athenz certificate updater on time", e); - } - } - - Instant getCertificateExpiry() throws KeyStoreException { - return getCertificateExpiry(currentKeyStore.keyStore); - } - - private static Instant getCertificateExpiry(KeyStore keyStore) throws KeyStoreException { - X509Certificate certificate = (X509Certificate) keyStore.getCertificate(CERTIFICATE_ALIAS); - return certificate.getNotAfter().toInstant(); - } - - private static KeyStoreAndPassword downloadCertificate(AthenzService configserverIdentity, - KeyProvider keyProvider, - ZtsClient ztsClient, - AthenzProviderServiceConfig.Zones zoneConfig) { - PrivateKey privateKey = keyProvider.getPrivateKey(zoneConfig.secretVersion()); - PublicKey publicKey = KeyUtils.extractPublicKey(privateKey); - Identity serviceIdentity = ztsClient.getServiceIdentity(configserverIdentity, - Integer.toString(zoneConfig.secretVersion()), - new KeyPair(publicKey, privateKey), - zoneConfig.certDnsSuffix()); - X509Certificate certificate = serviceIdentity.certificate(); - writeCredentials(configserverIdentity, certificate, serviceIdentity.caCertificates(), privateKey); - Instant expirationTime = certificate.getNotAfter().toInstant(); - Duration expiry = Duration.between(certificate.getNotBefore().toInstant(), expirationTime); - log.log(LogLevel.INFO, String.format("Got Athenz x509 certificate with expiry %s (expires %s)", expiry, expirationTime)); - - char[] keystorePassword = generateKeystorePassword(); - KeyStore keyStore = KeyStoreBuilder.withType(KeyStoreType.JKS) - .withKeyEntry(CERTIFICATE_ALIAS, privateKey, keystorePassword, certificate) - .build(); - return new KeyStoreAndPassword(keyStore, keystorePassword); - } - - private static void writeCredentials(AthenzService configserverIdentity, - X509Certificate certificate, - List<X509Certificate> caCertificates, - PrivateKey privateKey) { - SiaUtils.writeCertificateFile(VESPA_SIA_DIRECTORY, configserverIdentity, certificate); - SiaUtils.writePrivateKeyFile(VESPA_SIA_DIRECTORY, configserverIdentity, privateKey); - } - - private static char[] generateKeystorePassword() { - return UUID.randomUUID().toString().toCharArray(); - } - - private class AthenzCertificateUpdater implements Runnable { - - private final SslKeyStoreContext sslKeyStoreContext; - - AthenzCertificateUpdater(SslKeyStoreContext sslKeyStoreContext) { - this.sslKeyStoreContext = sslKeyStoreContext; - } - - @Override - public void run() { - try { - log.log(LogLevel.INFO, "Updating Athenz certificate from ZTS"); - currentKeyStore = downloadCertificate(configserverIdentity, keyProvider, ztsClient, zoneConfig); - sslKeyStoreContext.updateKeyStore(currentKeyStore.keyStore, new String(currentKeyStore.password)); - log.log(LogLevel.INFO, "Athenz certificate reload successfully completed"); - } catch (Throwable e) { - log.log(LogLevel.ERROR, "Failed to update certificate from ZTS: " + e.getMessage(), e); - } - } - - } - - private static class KeyStoreAndPassword { - final KeyStore keyStore; - final char[] password; - - KeyStoreAndPassword(KeyStore keyStore, char[] password) { - this.keyStore = keyStore; - this.password = password; - } - } -} diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslTrustStoreConfigurator.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslTrustStoreConfigurator.java deleted file mode 100644 index da5fd430f1c..00000000000 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslTrustStoreConfigurator.java +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.athenz.instanceproviderservice; - -import com.google.inject.Inject; -import com.yahoo.jdisc.http.ssl.SslTrustStoreConfigurator; -import com.yahoo.jdisc.http.ssl.SslTrustStoreContext; -import com.yahoo.security.KeyStoreBuilder; -import com.yahoo.security.KeyStoreType; -import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig; - -import java.nio.file.Paths; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.cert.X509Certificate; -import java.time.Instant; - -/** - * Programmatic configuration of configserver's truststore - * - * @author bjorncs - */ -public class AthenzSslTrustStoreConfigurator implements SslTrustStoreConfigurator { - - private static final String CERTIFICATE_ALIAS = "cfgselfsigned"; - - private final KeyStore trustStore; - - @Inject - public AthenzSslTrustStoreConfigurator(AthenzProviderServiceConfig athenzProviderServiceConfig) { - this.trustStore = createTrustStore(athenzProviderServiceConfig); - } - - @Override - public void configure(SslTrustStoreContext sslTrustStoreContext) { - sslTrustStoreContext.updateTrustStore(trustStore); - } - - Instant getTrustStoreExpiry() throws KeyStoreException { - X509Certificate certificate = (X509Certificate) trustStore.getCertificate(CERTIFICATE_ALIAS); - return certificate.getNotAfter().toInstant(); - } - - private static KeyStore createTrustStore(AthenzProviderServiceConfig athenzProviderServiceConfig) { - try { - return KeyStoreBuilder.withType(KeyStoreType.JKS) - .fromFile(Paths.get(athenzProviderServiceConfig.athenzCaTrustStore())) - .build(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - -} diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/CertificateExpiryMetricUpdater.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/CertificateExpiryMetricUpdater.java index 2d80b15c7ec..cd69099ea80 100644 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/CertificateExpiryMetricUpdater.java +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/CertificateExpiryMetricUpdater.java @@ -1,12 +1,10 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.athenz.instanceproviderservice; +import com.google.inject.Inject; import com.yahoo.component.AbstractComponent; import com.yahoo.jdisc.Metric; -import com.google.inject.Inject; - -import java.security.KeyStoreException; import java.time.Duration; import java.time.Instant; import java.util.concurrent.Executors; @@ -21,23 +19,18 @@ import java.util.logging.Logger; public class CertificateExpiryMetricUpdater extends AbstractComponent { private static final Duration METRIC_REFRESH_PERIOD = Duration.ofMinutes(5); - private static final String NODE_CA_CERT_METRIC_NAME = "node-ca-cert.expiry.seconds"; private static final String ATHENZ_CONFIGSERVER_CERT_METRIC_NAME = "athenz-configserver-cert.expiry.seconds"; private final Logger logger = Logger.getLogger(CertificateExpiryMetricUpdater.class.getName()); private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); private final Metric metric; - private final AthenzSslKeyStoreConfigurator keyStoreConfigurator; - private final AthenzSslTrustStoreConfigurator trustStoreConfigurator; + private final ConfigserverSslContextFactoryProvider provider; @Inject public CertificateExpiryMetricUpdater(Metric metric, - AthenzSslKeyStoreConfigurator keyStoreConfigurator, - AthenzSslTrustStoreConfigurator trustStoreConfigurator) { + ConfigserverSslContextFactoryProvider provider) { this.metric = metric; - this.keyStoreConfigurator = keyStoreConfigurator; - this.trustStoreConfigurator = trustStoreConfigurator; - + this.provider = provider; scheduler.scheduleAtFixedRate(this::updateMetrics, 30/*initial delay*/, @@ -56,20 +49,11 @@ public class CertificateExpiryMetricUpdater extends AbstractComponent { } private void updateMetrics() { - Instant now = Instant.now(); - try { - Duration keyStoreExpiry = Duration.between(now, keyStoreConfigurator.getCertificateExpiry()); + Duration keyStoreExpiry = Duration.between(Instant.now(), provider.getCertificateNotAfter()); metric.set(ATHENZ_CONFIGSERVER_CERT_METRIC_NAME, keyStoreExpiry.getSeconds(), null); - } catch (KeyStoreException e) { - logger.log(Level.WARNING, "Failed to update key store expiry metric", e); - } - - try { - Duration trustStoreExpiry = Duration.between(now, trustStoreConfigurator.getTrustStoreExpiry()); - metric.set(NODE_CA_CERT_METRIC_NAME, trustStoreExpiry.getSeconds(), null); - } catch (KeyStoreException e) { - logger.log(Level.WARNING, "Failed to update trust store expiry metric", e); + } catch (Exception e) { + logger.log(Level.WARNING, "Failed to update key store expiry metric: " + e.getMessage(), e); } } } diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ConfigserverSslContextFactoryProvider.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ConfigserverSslContextFactoryProvider.java new file mode 100644 index 00000000000..6d06a36d332 --- /dev/null +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ConfigserverSslContextFactoryProvider.java @@ -0,0 +1,199 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.athenz.instanceproviderservice; + +import com.google.inject.Inject; +import com.yahoo.component.AbstractComponent; +import com.yahoo.config.provision.Zone; +import com.yahoo.jdisc.http.ssl.SslContextFactoryProvider; +import com.yahoo.log.LogLevel; +import com.yahoo.security.KeyStoreBuilder; +import com.yahoo.security.KeyStoreType; +import com.yahoo.security.KeyUtils; +import com.yahoo.vespa.athenz.api.AthenzService; +import com.yahoo.vespa.athenz.client.zts.DefaultZtsClient; +import com.yahoo.vespa.athenz.client.zts.Identity; +import com.yahoo.vespa.athenz.client.zts.ZtsClient; +import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider; +import com.yahoo.vespa.athenz.utils.SiaUtils; +import com.yahoo.vespa.defaults.Defaults; +import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.time.Instant; +import java.util.Arrays; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +import static com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.Utils.getZoneConfig; + +/** + * Configures the JDisc https connector with the configserver's Athenz provider certificate and private key. + * + * @author bjorncs + */ +public class ConfigserverSslContextFactoryProvider extends AbstractComponent implements SslContextFactoryProvider { + private static final String CERTIFICATE_ALIAS = "athenz"; + private static final Duration EXPIRATION_MARGIN = Duration.ofHours(6); + private static final Path VESPA_SIA_DIRECTORY = Paths.get(Defaults.getDefaults().underVespaHome("var/vespa/sia")); + + private static final Logger log = Logger.getLogger(ConfigserverSslContextFactoryProvider.class.getName()); + + private final SslContextFactory sslContextFactory; + private final ScheduledExecutorService scheduler = + Executors.newSingleThreadScheduledExecutor(runnable -> new Thread(runnable, "configserver-ssl-context-factory-provider")); + private final ZtsClient ztsClient; + private final KeyProvider keyProvider; + private final AthenzProviderServiceConfig.Zones zoneConfig; + private final AthenzService configserverIdentity; + + @Inject + public ConfigserverSslContextFactoryProvider(ServiceIdentityProvider bootstrapIdentity, + KeyProvider keyProvider, + AthenzProviderServiceConfig config, + Zone zone) { + this.zoneConfig = getZoneConfig(config, zone); + this.ztsClient = new DefaultZtsClient(URI.create(zoneConfig.ztsUrl()), bootstrapIdentity); + this.keyProvider = keyProvider; + this.configserverIdentity = new AthenzService(zoneConfig.domain(), zoneConfig.serviceName()); + + Duration updatePeriod = Duration.ofDays(config.updatePeriodDays()); + Path trustStoreFile = Paths.get(config.athenzCaTrustStore()); + this.sslContextFactory = initializeSslContextFactory(keyProvider, trustStoreFile, updatePeriod, configserverIdentity, ztsClient, zoneConfig); + scheduler.scheduleAtFixedRate(new KeystoreUpdater(sslContextFactory), + updatePeriod.toDays()/*initial delay*/, + updatePeriod.toDays(), + TimeUnit.DAYS); + } + + @Override + public SslContextFactory getInstance(String containerId, int port) { + return sslContextFactory; + } + + Instant getCertificateNotAfter() { + try { + X509Certificate certificate = (X509Certificate) sslContextFactory.getKeyStore().getCertificate(CERTIFICATE_ALIAS); + return certificate.getNotAfter().toInstant(); + } catch (GeneralSecurityException e) { + throw new IllegalStateException("Unable to find configserver certificate from keystore: " + e.getMessage(), e); + } + } + + @Override + public void deconstruct() { + try { + scheduler.shutdownNow(); + scheduler.awaitTermination(30, TimeUnit.SECONDS); + ztsClient.close(); + } catch (InterruptedException e) { + throw new RuntimeException("Failed to shutdown Athenz certificate updater on time", e); + } + } + + private static SslContextFactory initializeSslContextFactory(KeyProvider keyProvider, + Path trustStoreFile, + Duration updatePeriod, + AthenzService configserverIdentity, + ZtsClient ztsClient, + AthenzProviderServiceConfig.Zones zoneConfig) { + SslContextFactory factory = new SslContextFactory(); + + // Allow safe TLS_RSA* ciphers + String[] excludedCiphersWithoutTlsRsaExclusion = Arrays.stream(factory.getExcludeCipherSuites()) + .filter(cipher -> !cipher.equals("^TLS_RSA_.*$")) + .toArray(String[]::new); + factory.setExcludeCipherSuites(excludedCiphersWithoutTlsRsaExclusion); + + factory.setWantClientAuth(true); + + KeyStore trustStore = + KeyStoreBuilder.withType(KeyStoreType.JKS) + .fromFile(trustStoreFile) + .build(); + factory.setTrustStore(trustStore); + + KeyStore keyStore = + tryReadKeystoreFile(configserverIdentity, updatePeriod) + .orElseGet(() -> updateKeystore(configserverIdentity, generateKeystorePassword(), keyProvider, ztsClient, zoneConfig)); + factory.setKeyStore(keyStore); + return factory; + } + + private static Optional<KeyStore> tryReadKeystoreFile(AthenzService configserverIdentity, Duration updatePeriod) { + Optional<X509Certificate> certificate = SiaUtils.readCertificateFile(VESPA_SIA_DIRECTORY, configserverIdentity); + if (!certificate.isPresent()) return Optional.empty(); + Optional<PrivateKey> privateKey = SiaUtils.readPrivateKeyFile(VESPA_SIA_DIRECTORY, configserverIdentity); + if (!privateKey.isPresent()) return Optional.empty(); + Instant minimumExpiration = Instant.now().plus(updatePeriod).plus(EXPIRATION_MARGIN); + boolean isExpired = certificate.get().getNotAfter().toInstant().isBefore(minimumExpiration); + if (isExpired) return Optional.empty(); + KeyStore keyStore = KeyStoreBuilder.withType(KeyStoreType.JKS) + .withKeyEntry(CERTIFICATE_ALIAS, privateKey.get(), certificate.get()) + .build(); + return Optional.of(keyStore); + } + + private static KeyStore updateKeystore(AthenzService configserverIdentity, + char[] keystorePwd, + KeyProvider keyProvider, + ZtsClient ztsClient, + AthenzProviderServiceConfig.Zones zoneConfig) { + PrivateKey privateKey = keyProvider.getPrivateKey(zoneConfig.secretVersion()); + PublicKey publicKey = KeyUtils.extractPublicKey(privateKey); + Identity serviceIdentity = ztsClient.getServiceIdentity(configserverIdentity, + Integer.toString(zoneConfig.secretVersion()), + new KeyPair(publicKey, privateKey), + zoneConfig.certDnsSuffix()); + X509Certificate certificate = serviceIdentity.certificate(); + SiaUtils.writeCertificateFile(VESPA_SIA_DIRECTORY, configserverIdentity, certificate); + SiaUtils.writePrivateKeyFile(VESPA_SIA_DIRECTORY, configserverIdentity, privateKey); + Instant expirationTime = certificate.getNotAfter().toInstant(); + Duration expiry = Duration.between(certificate.getNotBefore().toInstant(), expirationTime); + log.log(LogLevel.INFO, String.format("Got Athenz x509 certificate with expiry %s (expires %s)", expiry, expirationTime)); + return KeyStoreBuilder.withType(KeyStoreType.JKS) + .withKeyEntry(CERTIFICATE_ALIAS, privateKey, keystorePwd, certificate) + .build(); + } + + private static char[] generateKeystorePassword() { + return UUID.randomUUID().toString().toCharArray(); + } + + private class KeystoreUpdater implements Runnable { + final SslContextFactory sslContextFactory; + + KeystoreUpdater(SslContextFactory sslContextFactory) { + this.sslContextFactory = sslContextFactory; + } + + @Override + public void run() { + try { + log.log(LogLevel.INFO, "Updating configserver provider certificate from ZTS"); + char[] keystorePwd = generateKeystorePassword(); + KeyStore keyStore = updateKeystore(configserverIdentity, keystorePwd, keyProvider, ztsClient, zoneConfig); + sslContextFactory.reload(scf -> { + scf.setKeyStore(keyStore); + scf.setKeyStorePassword(new String(keystorePwd)); + }); + log.log(LogLevel.INFO, "Certificate successfully updated"); + } catch (Throwable t) { + log.log(LogLevel.ERROR, "Failed to update certificate from ZTS: " + t.getMessage(), t); + } + } + } +} |