From 93736dace106d7a0ae4ee2508393a16cdc7c2f5c Mon Sep 17 00:00:00 2001 From: Bjørn Christian Seime Date: Tue, 5 Jun 2018 14:04:16 +0200 Subject: Stop using a fixed keystore password --- .../AthenzSslKeyStoreConfigurator.java | 98 +++++++++++++++------- 1 file changed, 67 insertions(+), 31 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 index 4af64286e7c..f1fc938d3ea 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 @@ -15,13 +15,15 @@ import com.yahoo.vespa.defaults.Defaults; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.AthenzCertificateClient; -import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.PrivateKey; +import java.security.SecureRandom; import java.security.cert.X509Certificate; import java.time.Duration; import java.time.Instant; @@ -35,13 +37,16 @@ import static com.yahoo.vespa.athenz.tls.KeyStoreUtils.writeKeyStoreToFile; 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 SecureRandom secureRandom = new SecureRandom(); private static final String CERTIFICATE_ALIAS = "athenz"; - private static final String CERTIFICATE_PASSWORD = "athenz"; private static final Duration EXPIRATION_MARGIN = Duration.ofHours(6); private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); @@ -49,8 +54,8 @@ public class AthenzSslKeyStoreConfigurator extends AbstractComponent implements private final KeyProvider keyProvider; private final AthenzProviderServiceConfig.Zones zoneConfig; private final Duration updatePeriod; - private final Path keystoreCachePath; - private volatile KeyStore currentKeyStore; + private final Path keystoreDirectory; + private volatile KeyStoreAndPassword currentKeyStore; @Inject public AthenzSslKeyStoreConfigurator(ServiceIdentityProvider bootstrapIdentity, @@ -59,51 +64,60 @@ public class AthenzSslKeyStoreConfigurator extends AbstractComponent implements Zone zone, ConfigserverConfig configserverConfig) { AthenzProviderServiceConfig.Zones zoneConfig = getZoneConfig(config, zone); - Path keystoreCachePath = createKeystoreCachePath(configserverConfig); + Path keystoreDirectory = createKeystoreCacheDirectory(configserverConfig); AthenzCertificateClient certificateClient = new AthenzCertificateClient(bootstrapIdentity, zoneConfig); Duration updatePeriod = Duration.ofDays(config.updatePeriodDays()); this.certificateClient = certificateClient; this.keyProvider = keyProvider; this.zoneConfig = zoneConfig; - this.currentKeyStore = initializeKeystore(keyProvider, certificateClient, zoneConfig, keystoreCachePath, updatePeriod); + this.currentKeyStore = initializeKeystore(keyProvider, certificateClient, zoneConfig, keystoreDirectory, updatePeriod); this.updatePeriod = updatePeriod; - this.keystoreCachePath = keystoreCachePath; + this.keystoreDirectory = keystoreDirectory; } - private static KeyStore initializeKeystore(KeyProvider keyProvider, + private static KeyStoreAndPassword initializeKeystore(KeyProvider keyProvider, AthenzCertificateClient certificateClient, AthenzProviderServiceConfig.Zones zoneConfig, - Path keystoreCachePath, + Path keystoreCacheDirectory, Duration updatePeriod) { - return tryReadKeystoreFile(keystoreCachePath.toFile(), updatePeriod) - .orElseGet(() -> downloadCertificate(keyProvider, certificateClient, zoneConfig, keystoreCachePath)); + return tryReadKeystoreFile(keystoreCacheDirectory, updatePeriod) + .orElseGet(() -> downloadCertificate(keyProvider, certificateClient, zoneConfig, keystoreCacheDirectory)); } - private static Optional tryReadKeystoreFile(File certificateFile, Duration updatePeriod) { + private static Optional tryReadKeystoreFile(Path keystoreDirectory, Duration updatePeriod) { try { - if (!certificateFile.exists()) return Optional.empty(); + Path keystoreFile = keystoreFile(keystoreDirectory); + Path keystoreSecretFile = keystoreSecretFile(keystoreDirectory); + if (!Files.exists(keystoreFile) || !Files.exists(keystoreSecretFile)) return Optional.empty(); KeyStore keyStore = KeyStoreBuilder.withType(KeyStoreType.JKS) - .fromFile(certificateFile) + .fromFile(keystoreFile.toFile()) .build(); Instant minimumExpiration = Instant.now().plus(updatePeriod).plus(EXPIRATION_MARGIN); boolean isExpired = getCertificateExpiry(keyStore).isBefore(minimumExpiration); if (isExpired) return Optional.empty(); - return Optional.of(keyStore); - } catch (GeneralSecurityException e) { + char[] pwd = new String(Files.readAllBytes(keystoreSecretFile)).toCharArray(); + return Optional.of(new KeyStoreAndPassword(keyStore, pwd)); + } catch (GeneralSecurityException | IOException e) { log.log(LogLevel.ERROR, "Failed to read keystore from disk: " + e.getMessage(), e); return Optional.empty(); } } - private static Path createKeystoreCachePath(ConfigserverConfig configserverConfig) { - return Paths.get( - Defaults.getDefaults().underVespaHome(configserverConfig.configServerDBDir()), - "server-x509-athenz-cert.jks"); + private static Path createKeystoreCacheDirectory(ConfigserverConfig configserverConfig) { + return Paths.get(Defaults.getDefaults().underVespaHome(configserverConfig.configServerDBDir())); + } + + private static Path keystoreFile(Path keystoreDirectory) { + return keystoreDirectory.resolve("server-x509-athenz-cert.jks"); + } + + private static Path keystoreSecretFile(Path keystoreDirectory) { + return keystoreDirectory.resolve("keystore-secret"); } @Override public void configure(SslKeyStoreContext sslKeyStoreContext) { - sslKeyStoreContext.updateKeyStore(currentKeyStore, CERTIFICATE_PASSWORD); + sslKeyStoreContext.updateKeyStore(currentKeyStore.keyStore, new String(currentKeyStore.password)); scheduler.scheduleAtFixedRate(new AthenzCertificateUpdater(sslKeyStoreContext), updatePeriod.toDays()/*initial delay*/, updatePeriod.toDays(), @@ -121,7 +135,7 @@ public class AthenzSslKeyStoreConfigurator extends AbstractComponent implements } Instant getCertificateExpiry() throws KeyStoreException { - return getCertificateExpiry(currentKeyStore); + return getCertificateExpiry(currentKeyStore.keyStore); } private static Instant getCertificateExpiry(KeyStore keyStore) throws KeyStoreException { @@ -129,31 +143,43 @@ public class AthenzSslKeyStoreConfigurator extends AbstractComponent implements return certificate.getNotAfter().toInstant(); } - private static KeyStore downloadCertificate(KeyProvider keyProvider, + private static KeyStoreAndPassword downloadCertificate(KeyProvider keyProvider, AthenzCertificateClient certificateClient, AthenzProviderServiceConfig.Zones zoneConfig, - Path keystoreCachePath) { + Path keystoreDirectory) { PrivateKey privateKey = keyProvider.getPrivateKey(zoneConfig.secretVersion()); X509Certificate certificate = certificateClient.updateCertificate(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, CERTIFICATE_PASSWORD.toCharArray(), certificate) + .withKeyEntry(CERTIFICATE_ALIAS, privateKey, keystorePassword, certificate) .build(); - tryWriteKeystore(keyStore, keystoreCachePath); - return keyStore; + KeyStoreAndPassword keyStoreAndPassword = new KeyStoreAndPassword(keyStore, keystorePassword); + tryWriteKeystore(keyStoreAndPassword, keystoreDirectory); + return keyStoreAndPassword; } - private static void tryWriteKeystore(KeyStore keyStore, Path keystoreCachePath) { + private static void tryWriteKeystore(KeyStoreAndPassword keyStore, Path keystoreDirectory) { try { - writeKeyStoreToFile(keyStore, keystoreCachePath.toFile()); + writeKeyStoreToFile(keyStore.keyStore, keystoreFile(keystoreDirectory).toFile()); + Files.write(keystoreSecretFile(keystoreDirectory), new String(keyStore.password).getBytes()); } catch (Exception e) { log.log(LogLevel.ERROR, "Failed to write keystore to disk: " + e.getMessage(), e); } } + private static char[] generateKeystorePassword() { + int length = 128; + char[] pwd = new char[length]; + for (int i = 0; i < length; i++) { + pwd[i] = (char) secureRandom.nextInt(); + } + return pwd; + } + private class AthenzCertificateUpdater implements Runnable { private final SslKeyStoreContext sslKeyStoreContext; @@ -166,8 +192,8 @@ public class AthenzSslKeyStoreConfigurator extends AbstractComponent implements public void run() { try { log.log(LogLevel.INFO, "Updating Athenz certificate from ZTS"); - currentKeyStore = downloadCertificate(keyProvider, certificateClient, zoneConfig, keystoreCachePath); - sslKeyStoreContext.updateKeyStore(currentKeyStore, CERTIFICATE_PASSWORD); + currentKeyStore = downloadCertificate(keyProvider, certificateClient, zoneConfig, keystoreDirectory); + 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); @@ -175,4 +201,14 @@ public class AthenzSslKeyStoreConfigurator extends AbstractComponent implements } } + + private static class KeyStoreAndPassword { + final KeyStore keyStore; + final char[] password; + + KeyStoreAndPassword(KeyStore keyStore, char[] password) { + this.keyStore = keyStore; + this.password = password; + } + } } -- cgit v1.2.3