diff options
Diffstat (limited to 'athenz-identity-provider-service')
8 files changed, 172 insertions, 35 deletions
diff --git a/athenz-identity-provider-service/pom.xml b/athenz-identity-provider-service/pom.xml index c87589d7be2..bfd02d54d43 100644 --- a/athenz-identity-provider-service/pom.xml +++ b/athenz-identity-provider-service/pom.xml @@ -11,6 +11,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <dependencies> <!-- COMPILE --> 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 index 7910650ed5e..706f797cd2c 100644 --- 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 @@ -3,7 +3,6 @@ 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.ssl.SslKeyStoreConfigurator; import com.yahoo.jdisc.http.ssl.SslKeyStoreContext; @@ -11,12 +10,15 @@ import com.yahoo.log.LogLevel; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.AthenzCertificateClient; +import java.io.IOException; import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.Certificate; +import java.security.cert.CertificateException; 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; @@ -35,13 +37,14 @@ public class AthenzSslKeyStoreConfigurator extends AbstractComponent implements // 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 static final String DUMMY_PASSWORD = "athenz"; private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); private final AthenzCertificateClient certificateClient; private final KeyProvider keyProvider; private final AthenzProviderServiceConfig.Zones zoneConfig; private final AtomicBoolean alreadyConfigured = new AtomicBoolean(); - private final Zone zone; + private KeyStore initialKeyStore; @Inject public AthenzSslKeyStoreConfigurator(KeyProvider keyProvider, @@ -51,20 +54,20 @@ public class AthenzSslKeyStoreConfigurator extends AbstractComponent implements this.certificateClient = new AthenzCertificateClient(config, zoneConfig); this.keyProvider = keyProvider; this.zoneConfig = zoneConfig; - this.zone = zone; + this.initialKeyStore = downloadCertificate(keyProvider, certificateClient, zoneConfig); } @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); + sslKeyStoreContext.updateKeyStore(initialKeyStore, DUMMY_PASSWORD); + initialKeyStore = null; + scheduler.scheduleAtFixedRate(new AthenzCertificateUpdater(sslKeyStoreContext), + CERTIFICATE_UPDATE_PERIOD.toMinutes()/*initial delay*/, + CERTIFICATE_UPDATE_PERIOD.toMinutes(), + TimeUnit.MINUTES); } @Override @@ -77,6 +80,32 @@ public class AthenzSslKeyStoreConfigurator extends AbstractComponent implements } } + private static KeyStore downloadCertificate(KeyProvider keyProvider, + AthenzCertificateClient certificateClient, + AthenzProviderServiceConfig.Zones zoneConfig) { + try { + PrivateKey privateKey = keyProvider.getPrivateKey(zoneConfig.secretVersion()); + X509Certificate certificate = certificateClient.updateCertificate(privateKey, CERTIFICATE_EXPIRY_TIME); + verifyActualExpiry(certificate); + + KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(null); + keyStore.setKeyEntry("athenz", privateKey, DUMMY_PASSWORD.toCharArray(), new Certificate[]{certificate}); + return keyStore; + } catch (IOException | NoSuchAlgorithmException | CertificateException | KeyStoreException e) { + throw new RuntimeException(e); + } + } + + private static void verifyActualExpiry(X509Certificate certificate) { + Duration actualExpiry = + Duration.between(certificate.getNotBefore().toInstant(), certificate.getNotAfter().toInstant()); + if (CERTIFICATE_EXPIRY_TIME.compareTo(actualExpiry) > 0) { + log.log(LogLevel.WARNING, + String.format("Expected expiry %s, got %s", CERTIFICATE_EXPIRY_TIME, actualExpiry)); + } + } + private class AthenzCertificateUpdater implements Runnable { private final SslKeyStoreContext sslKeyStoreContext; @@ -89,29 +118,13 @@ public class AthenzSslKeyStoreConfigurator extends AbstractComponent implements 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); + KeyStore keyStore = downloadCertificate(keyProvider, certificateClient, zoneConfig); + sslKeyStoreContext.updateKeyStore(keyStore, DUMMY_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 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/AthenzSslTrustStoreConfigurator.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslTrustStoreConfigurator.java new file mode 100644 index 00000000000..8c8b5de2a30 --- /dev/null +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslTrustStoreConfigurator.java @@ -0,0 +1,110 @@ +// 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.cloud.config.ConfigserverConfig; +import com.yahoo.jdisc.http.ssl.SslTrustStoreConfigurator; +import com.yahoo.jdisc.http.ssl.SslTrustStoreContext; +import com.yahoo.log.LogLevel; +import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; + +import java.io.FileInputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.Provider; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.time.Instant; +import java.util.Date; +import java.util.logging.Logger; + +/** + * @author bjorncs + */ +public class AthenzSslTrustStoreConfigurator implements SslTrustStoreConfigurator { + + private static final Logger log = Logger.getLogger(AthenzSslTrustStoreConfigurator.class.getName()); + + private static final Provider provider = new BouncyCastleProvider(); + private final KeyStore trustStore; + + @Inject + public AthenzSslTrustStoreConfigurator(KeyProvider keyProvider, + ConfigserverConfig configserverConfig, + AthenzProviderServiceConfig athenzProviderServiceConfig) { + this.trustStore = createTrustStore(keyProvider, configserverConfig, athenzProviderServiceConfig); + } + + @Override + public void configure(SslTrustStoreContext sslTrustStoreContext) { + sslTrustStoreContext.updateTrustStore(trustStore); + log.log(LogLevel.INFO, "Configured JDisc trust store with self-signed certificate"); + } + + private static KeyStore createTrustStore(KeyProvider keyProvider, + ConfigserverConfig configserverConfig, + AthenzProviderServiceConfig athenzProviderServiceConfig) { + try { + KeyPair keyPair = getKeyPair(keyProvider, configserverConfig, athenzProviderServiceConfig); + X509Certificate selfSignedCertificate = createSelfSignedCertificate(keyPair, configserverConfig); + log.log(LogLevel.FINE, "Generated self-signed certificate: " + selfSignedCertificate); + KeyStore trustStore = KeyStore.getInstance("JKS"); + try (FileInputStream in = new FileInputStream(athenzProviderServiceConfig.athenzCaTrustStore())) { + trustStore.load(in, "changeit".toCharArray()); + } + trustStore.setCertificateEntry("cfgselfsigned", selfSignedCertificate); + return trustStore; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static KeyPair getKeyPair(KeyProvider keyProvider, + ConfigserverConfig configserverConfig, + AthenzProviderServiceConfig athenzProviderServiceConfig) { + String key = configserverConfig.environment() + "." + configserverConfig.region(); + AthenzProviderServiceConfig.Zones zoneConfig = athenzProviderServiceConfig.zones(key); + return keyProvider.getKeyPair(zoneConfig.secretVersion()); + } + + private static X509Certificate createSelfSignedCertificate(KeyPair keyPair, ConfigserverConfig config) + throws IOException, CertificateException, OperatorCreationException { + ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256WithRSA").build(keyPair.getPrivate()); + X500Name x500Name = new X500Name("CN="+ config.loadBalancerAddress()); + Instant now = Instant.now(); + Date notBefore = Date.from(now); + Date notAfter = Date.from(now.plus(Duration.ofDays(30))); + + GeneralNames generalNames = new GeneralNames( + config.zookeeperserver().stream() + .map(server -> new GeneralName(GeneralName.dNSName, server.hostname())) + .toArray(GeneralName[]::new)); + + X509v3CertificateBuilder certificateBuilder = + new JcaX509v3CertificateBuilder( + x500Name, BigInteger.valueOf(now.toEpochMilli()), notBefore, notAfter, x500Name, keyPair.getPublic() + ) + .addExtension(Extension.basicConstraints, true, new BasicConstraints(true)) + .addExtension(Extension.subjectAlternativeName, false, generalNames); + + return new JcaX509CertificateConverter() + .setProvider(provider) + .getCertificate(certificateBuilder.build(contentSigner)); + } + +} diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/KeyProvider.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/KeyProvider.java index a72a2fcbc6c..1d141099428 100644 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/KeyProvider.java +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/KeyProvider.java @@ -1,6 +1,7 @@ // 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 java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; @@ -11,4 +12,8 @@ public interface KeyProvider { PrivateKey getPrivateKey(int version); PublicKey getPublicKey(int version); + + default KeyPair getKeyPair(int version) { + return new KeyPair(getPublicKey(version), getPrivateKey(version)); + } } diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSigner.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSigner.java index 2dc3f24664c..1014fc4afdf 100644 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSigner.java +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSigner.java @@ -2,9 +2,9 @@ package com.yahoo.vespa.hosted.athenz.instanceproviderservice.ca; import com.google.common.collect.ImmutableList; import com.google.inject.Inject; +import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.config.provision.Zone; import com.yahoo.log.LogLevel; -import com.yahoo.net.HostName; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.KeyProvider; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig; import org.bouncycastle.asn1.ASN1ObjectIdentifier; @@ -68,13 +68,16 @@ public class CertificateSigner { private final Clock clock; @Inject - public CertificateSigner(KeyProvider keyProvider, AthenzProviderServiceConfig config, Zone zone) { - this(getPrivateKey(keyProvider, config, zone), HostName.getLocalhost(), Clock.systemUTC()); + public CertificateSigner(KeyProvider keyProvider, + ConfigserverConfig configserverConfig, + AthenzProviderServiceConfig config, + Zone zone) { + this(getPrivateKey(keyProvider, config, zone), configserverConfig.loadBalancerAddress(), Clock.systemUTC()); } - CertificateSigner(PrivateKey caPrivateKey, String configServerHostname, Clock clock) { + CertificateSigner(PrivateKey caPrivateKey, String loadBalancerAddress, Clock clock) { this.caPrivateKey = caPrivateKey; - this.issuer = new X500Name("CN=" + configServerHostname); + this.issuer = new X500Name("CN=" + loadBalancerAddress); this.clock = clock; } diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/SecretStoreKeyProvider.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/SecretStoreKeyProvider.java index e66131b6cf7..ac8c0eabf31 100644 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/SecretStoreKeyProvider.java +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/SecretStoreKeyProvider.java @@ -45,7 +45,8 @@ public class SecretStoreKeyProvider implements KeyProvider { return getKeyPair(version).getPublic(); } - private KeyPair getKeyPair(int version) { + @Override + public KeyPair getKeyPair(int version) { synchronized (secrets) { KeyPair keyPair = secrets.get(version); if (keyPair == null) { diff --git a/athenz-identity-provider-service/src/main/resources/configdefinitions/athenz-provider-service.def b/athenz-identity-provider-service/src/main/resources/configdefinitions/athenz-provider-service.def index 13cc78b0bd0..21f2aea6ab0 100644 --- a/athenz-identity-provider-service/src/main/resources/configdefinitions/athenz-provider-service.def +++ b/athenz-identity-provider-service/src/main/resources/configdefinitions/athenz-provider-service.def @@ -21,3 +21,6 @@ ztsUrl string # Certificate DNS suffix certDnsSuffix string + +# Path to Athenz CA JKS trust store +athenzCaTrustStore string diff --git a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/TestUtils.java b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/TestUtils.java index c09a9fb1740..da2bf929e82 100644 --- a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/TestUtils.java +++ b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/TestUtils.java @@ -25,7 +25,8 @@ public class TestUtils { .zones(ImmutableMap.of(zone.environment().value() + "." + zone.region().value(), zoneConfig)) .certDnsSuffix(dnsSuffix) .ztsUrl("localhost/zts") - .athenzPrincipalHeaderName("Athenz-Principal-Auth")); + .athenzPrincipalHeaderName("Athenz-Principal-Auth") + .athenzCaTrustStore("/dummy/path/to/athenz-ca.jks")); } } |