diff options
author | Morten Tokle <morten.tokle@gmail.com> | 2017-11-17 07:46:10 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-11-17 07:46:10 +0100 |
commit | d0bd2079ca613bc4d7ad7de89869064aecf4ca80 (patch) | |
tree | 4928549bf8f9012fc7e9833587fbcb004c2fa04e | |
parent | 161fb5d1145d712678e3e40a8397f9ca792fa52b (diff) | |
parent | e5d1f5da6124f5e1487c9edad0e3a621440051b8 (diff) |
Merge pull request #4156 from vespa-engine/bjorncs/athenz-ssl-keystore-configurator
Add Athenz implementation of SslKeyStoreConfigurator
2 files changed, 126 insertions, 0 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 new file mode 100644 index 00000000000..67f07875243 --- /dev/null +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslKeyStoreConfigurator.java @@ -0,0 +1,118 @@ +// Copyright 2017 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.SystemName; +import com.yahoo.config.provision.Zone; +import com.yahoo.jdisc.http.SecretStore; +import com.yahoo.jdisc.http.ssl.SslKeyStoreConfigurator; +import com.yahoo.jdisc.http.ssl.SslKeyStoreContext; +import com.yahoo.log.LogLevel; +import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig; +import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.AthenzCertificateClient; +import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.SecretStoreKeyProvider; + +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.time.Instant; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Logger; + +import static com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.Utils.getZoneConfig; + +/** + * @author bjorncs + */ +// TODO Cache certificate on disk +public class AthenzSslKeyStoreConfigurator extends AbstractComponent implements SslKeyStoreConfigurator { + private static final Logger log = Logger.getLogger(AthenzSslKeyStoreConfigurator.class.getName()); + // TODO Make expiry and update frequency configurable parameters + private static final Duration CERTIFICATE_EXPIRY_TIME = Duration.ofDays(30); + private static final Duration CERTIFICATE_UPDATE_PERIOD = Duration.ofDays(7); + + private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + private final AthenzCertificateClient certificateClient; + private final SecretStoreKeyProvider keyProvider; + private final AthenzProviderServiceConfig.Zones zoneConfig; + private final AtomicBoolean alreadyConfigured = new AtomicBoolean(); + private final Zone zone; + + @Inject + public AthenzSslKeyStoreConfigurator(SecretStore secretStore, + AthenzProviderServiceConfig config, + Zone zone) { + AthenzProviderServiceConfig.Zones zoneConfig = getZoneConfig(config, zone); + this.certificateClient = new AthenzCertificateClient(config, zoneConfig); + this.keyProvider = new SecretStoreKeyProvider(secretStore, zoneConfig.secretName()); + this.zoneConfig = zoneConfig; + this.zone = zone; + } + + @Override + public void configure(SslKeyStoreContext sslKeyStoreContext) { + // TODO Remove this when main is ready + if (zone.system() != SystemName.cd) { + return; + } + if (alreadyConfigured.getAndSet(true)) { // For debugging purpose of SslKeyStoreConfigurator interface + throw new IllegalStateException("Already configured. configure() can only be called once."); + } + AthenzCertificateUpdater updater = new AthenzCertificateUpdater(sslKeyStoreContext); + scheduler.scheduleAtFixedRate(updater, /*initialDelay*/0, CERTIFICATE_UPDATE_PERIOD.toMinutes(), TimeUnit.MINUTES); + } + + @Override + public void deconstruct() { + try { + scheduler.shutdownNow(); + scheduler.awaitTermination(30, TimeUnit.SECONDS); + } catch (InterruptedException e) { + throw new RuntimeException("Failed to shutdown Athenz certificate updater on time", e); + } + } + + 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"); + PrivateKey privateKey = keyProvider.getPrivateKey(zoneConfig.secretVersion()); + X509Certificate certificate = certificateClient.updateCertificate(privateKey, CERTIFICATE_EXPIRY_TIME); + verifyActualExperiy(certificate); + + String dummyPassword = "athenz"; + KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(null); + keyStore.setKeyEntry("athenz", privateKey, dummyPassword.toCharArray(), new Certificate[]{certificate}); + sslKeyStoreContext.updateKeyStore(keyStore, dummyPassword); + 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 void verifyActualExperiy(X509Certificate certificate) { + Instant notAfter = certificate.getNotAfter().toInstant(); + Instant notBefore = certificate.getNotBefore().toInstant(); + if (!notBefore.plus(CERTIFICATE_EXPIRY_TIME).equals(notAfter)) { + Duration actualExpiry = Duration.between(notBefore, notAfter); + log.log(LogLevel.WARNING, + String.format("Expected expiry %s, got %s", CERTIFICATE_EXPIRY_TIME, actualExpiry)); + } + } + } +} diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/Utils.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/Utils.java index d81ec183fd4..ad54aa341bf 100644 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/Utils.java +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/Utils.java @@ -3,6 +3,8 @@ package com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.yahoo.config.provision.Zone; +import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig; /** * @author bjorncs @@ -20,4 +22,10 @@ public class Utils { mapper.registerModule(new JavaTimeModule()); return mapper; } + + public static AthenzProviderServiceConfig.Zones getZoneConfig(AthenzProviderServiceConfig config, Zone zone) { + String key = zone.environment().value() + "." + zone.region().value(); + return config.zones(key); + } + } |