diff options
Diffstat (limited to 'vespa-athenz/src')
6 files changed, 122 insertions, 63 deletions
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identity/ServiceIdentityProvider.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identity/ServiceIdentityProvider.java index e5ed885b316..f6888acb018 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identity/ServiceIdentityProvider.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identity/ServiceIdentityProvider.java @@ -2,18 +2,56 @@ package com.yahoo.vespa.athenz.identity; import com.yahoo.container.jdisc.athenz.AthenzIdentityProvider; +import com.yahoo.security.X509CertificateWithKey; import com.yahoo.vespa.athenz.api.AthenzIdentity; -import com.yahoo.vespa.athenz.api.AthenzService; import javax.net.ssl.SSLContext; +import java.nio.file.Path; /** - * A interface for types that provides a service identity. - * Some similarities to {@link AthenzIdentityProvider}, but this type is not public api and intended for internal use. + * A interface for types that provides the Athenz service identity (SIA) from the environment. + * Some similarities to {@link AthenzIdentityProvider}, but this type is not public API and intended for internal use. * * @author bjorncs */ public interface ServiceIdentityProvider { + /** + * + * @return The Athenz identity of the environment + */ AthenzIdentity identity(); + + /** + * @return {@link SSLContext} that is automatically updated. + */ SSLContext getIdentitySslContext(); + + /** + * @return Current certificate and private key. Unlike {@link #getIdentitySslContext()} underlying credentials are not automatically updated. + */ + X509CertificateWithKey getIdentityCertificateWithKey(); + + /** + * @return Path to X.509 certificate in PEM format + */ + Path certificatePath(); + + /** + * @return Path to private key in PEM format + */ + Path privateKeyPath(); + + /** + * @return Path to Athenz truststore in PEM format + */ + Path athenzTruststorePath(); + + /** + * The client truststore contains the Athenz certificates from {@link #athenzTruststorePath()} + * and additional certificate authorities that issues trusted server certificates. + * + * @return Path to client truststore in PEM format + */ + Path clientTruststorePath(); + } diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identity/SiaIdentityProvider.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identity/SiaIdentityProvider.java index 4981b80998f..a2a773ac751 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identity/SiaIdentityProvider.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identity/SiaIdentityProvider.java @@ -3,15 +3,14 @@ package com.yahoo.vespa.athenz.identity; import com.google.inject.Inject; import com.yahoo.component.AbstractComponent; -import com.yahoo.security.KeyStoreType; import com.yahoo.security.SslContextBuilder; +import com.yahoo.security.X509CertificateWithKey; import com.yahoo.security.tls.AutoReloadingX509KeyManager; import com.yahoo.vespa.athenz.api.AthenzIdentity; import com.yahoo.vespa.athenz.api.AthenzService; import com.yahoo.vespa.athenz.utils.SiaUtils; import javax.net.ssl.SSLContext; -import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; @@ -26,34 +25,43 @@ public class SiaIdentityProvider extends AbstractComponent implements ServiceIde private final AutoReloadingX509KeyManager keyManager; private final SSLContext sslContext; private final AthenzIdentity service; + private final Path certificateFile; + private final Path privateKeyFile; + private final Path clientTruststoreFile; + private final Path athenzTruststoreFile; @Inject public SiaIdentityProvider(SiaProviderConfig config) { this(new AthenzService(config.athenzDomain(), config.athenzService()), - SiaUtils.getPrivateKeyFile(Paths.get(config.keyPathPrefix()), new AthenzService(config.athenzDomain(), config.athenzService())).toFile(), - SiaUtils.getCertificateFile(Paths.get(config.keyPathPrefix()), new AthenzService(config.athenzDomain(), config.athenzService())).toFile(), - new File(config.trustStorePath()), - config.trustStoreType()); + SiaUtils.getPrivateKeyFile(Paths.get(config.keyPathPrefix()), new AthenzService(config.athenzDomain(), config.athenzService())), + SiaUtils.getCertificateFile(Paths.get(config.keyPathPrefix()), new AthenzService(config.athenzDomain(), config.athenzService())), + Paths.get(config.trustStorePath()), + Paths.get(config.athenzTruststorePath())); } public SiaIdentityProvider(AthenzIdentity service, Path siaPath, - File trustStoreFile) { + Path clientTruststoreFile, + Path athenzTruststoreFile) { this(service, - SiaUtils.getPrivateKeyFile(siaPath, service).toFile(), - SiaUtils.getCertificateFile(siaPath, service).toFile(), - trustStoreFile, - SiaProviderConfig.TrustStoreType.Enum.jks); + SiaUtils.getPrivateKeyFile(siaPath, service), + SiaUtils.getCertificateFile(siaPath, service), + athenzTruststoreFile, + clientTruststoreFile); } public SiaIdentityProvider(AthenzIdentity service, - File privateKeyFile, - File certificateFile, - File trustStoreFile, - SiaProviderConfig.TrustStoreType.Enum trustStoreType) { + Path privateKeyFile, + Path certificateFile, + Path athenzTruststoreFile, + Path clientTruststoreFile) { this.service = service; - this.keyManager = AutoReloadingX509KeyManager.fromPemFiles(privateKeyFile.toPath(), certificateFile.toPath()); - this.sslContext = createIdentitySslContext(keyManager, trustStoreFile.toPath(), trustStoreType); + this.keyManager = AutoReloadingX509KeyManager.fromPemFiles(privateKeyFile, certificateFile); + this.sslContext = createIdentitySslContext(keyManager, clientTruststoreFile); + this.certificateFile = certificateFile; + this.privateKeyFile = privateKeyFile; + this.athenzTruststoreFile = athenzTruststoreFile; + this.clientTruststoreFile = clientTruststoreFile; } @Override @@ -66,17 +74,17 @@ public class SiaIdentityProvider extends AbstractComponent implements ServiceIde return sslContext; } - private static SSLContext createIdentitySslContext(AutoReloadingX509KeyManager keyManager, Path trustStoreFile, - SiaProviderConfig.TrustStoreType.Enum trustStoreType) { - var builder = new SslContextBuilder(); - if (trustStoreType == SiaProviderConfig.TrustStoreType.Enum.pem) { - builder = builder.withTrustStore(trustStoreFile); - } else if (trustStoreType == SiaProviderConfig.TrustStoreType.Enum.jks) { - builder = builder.withTrustStore(trustStoreFile, KeyStoreType.JKS); - } else { - throw new IllegalArgumentException("Unsupported trust store type: " + trustStoreType); - } - return builder.withKeyManager(keyManager).build(); + @Override public X509CertificateWithKey getIdentityCertificateWithKey() { return keyManager.getCurrentCertificateWithKey(); } + @Override public Path certificatePath() { return certificateFile; } + @Override public Path privateKeyPath() { return privateKeyFile; } + @Override public Path athenzTruststorePath() { return athenzTruststoreFile; } + @Override public Path clientTruststorePath() { return clientTruststoreFile; } + + private static SSLContext createIdentitySslContext(AutoReloadingX509KeyManager keyManager, Path trustStoreFile) { + return new SslContextBuilder() + .withTrustStore(trustStoreFile) + .withKeyManager(keyManager) + .build(); } @Override diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzCredentialsService.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzCredentialsService.java index 3b733e05708..8e029906c30 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzCredentialsService.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzCredentialsService.java @@ -64,6 +64,9 @@ class AthenzCredentialsService { this.clock = clock; } + Path certificatePath() { return SiaUtils.getCertificateFile(VESPA_SIA_DIRECTORY, tenantIdentity); } + Path privateKeyPath() { return SiaUtils.getPrivateKeyFile(VESPA_SIA_DIRECTORY, tenantIdentity); } + AthenzCredentials registerInstance() { Optional<AthenzCredentials> athenzCredentialsFromDisk = tryReadCredentialsFromDisk(); if (athenzCredentialsFromDisk.isPresent()) { diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImpl.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImpl.java index b816acfad38..62c5820c927 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImpl.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImpl.java @@ -11,9 +11,9 @@ import com.yahoo.container.jdisc.athenz.AthenzIdentityProvider; import com.yahoo.container.jdisc.athenz.AthenzIdentityProviderException; import com.yahoo.jdisc.Metric; import com.yahoo.security.KeyStoreBuilder; -import com.yahoo.security.KeyStoreType; import com.yahoo.security.Pkcs10Csr; import com.yahoo.security.SslContextBuilder; +import com.yahoo.security.X509CertificateWithKey; import com.yahoo.security.tls.MutableX509KeyManager; import com.yahoo.vespa.athenz.api.AthenzAccessToken; import com.yahoo.vespa.athenz.api.AthenzDomain; @@ -47,7 +47,6 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; -import static com.yahoo.security.KeyStoreType.JKS; import static com.yahoo.security.KeyStoreType.PKCS12; /** @@ -56,6 +55,8 @@ import static com.yahoo.security.KeyStoreType.PKCS12; * @author mortent * @author bjorncs */ +// This class should probably not implement ServiceIdentityProvider, +// as that interface is intended for providing the node's identity, not the tenant's application identity. public final class AthenzIdentityProviderImpl extends AbstractComponent implements AthenzIdentityProvider, ServiceIdentityProvider { private static final Logger log = Logger.getLogger(AthenzIdentityProviderImpl.class.getName()); @@ -67,8 +68,9 @@ public final class AthenzIdentityProviderImpl extends AbstractComponent implemen private final static Duration ROLE_SSL_CONTEXT_EXPIRY = Duration.ofHours(24); private final static Duration ROLE_TOKEN_EXPIRY = Duration.ofMinutes(30); - // TODO Make path to trust store config - private static final Path DEFAULT_TRUST_STORE = Paths.get("/opt/yahoo/share/ssl/certs/yahoo_certificate_bundle.jks"); + // TODO Make path to trust store paths config + private static final Path CLIENT_TRUST_STORE = Paths.get("/opt/yahoo/share/ssl/certs/yahoo_certificate_bundle.pem"); + private static final Path ATHENZ_TRUST_STORE = Paths.get("/opt/yahoo/share/ssl/certs/athenz_certificate_bundle.pem"); public static final String CERTIFICATE_EXPIRY_METRIC_NAME = "athenz-tenant-cert.expiry.seconds"; @@ -94,9 +96,9 @@ public final class AthenzIdentityProviderImpl extends AbstractComponent implemen public AthenzIdentityProviderImpl(IdentityConfig config, Metric metric) { this(config, metric, - DEFAULT_TRUST_STORE, + CLIENT_TRUST_STORE, new AthenzCredentialsService(config, - createNodeIdentityProvider(config, DEFAULT_TRUST_STORE), + createNodeIdentityProvider(config), Defaults.getDefaults().vespaHostname(), Clock.systemUTC()), new ScheduledThreadPoolExecutor(1), @@ -142,7 +144,7 @@ public final class AthenzIdentityProviderImpl extends AbstractComponent implemen private static SSLContext createIdentitySslContext(X509ExtendedKeyManager keyManager, Path trustStore) { return new SslContextBuilder() .withKeyManager(keyManager) - .withTrustStore(trustStore, JKS) + .withTrustStore(trustStore) .build(); } @@ -177,6 +179,20 @@ public final class AthenzIdentityProviderImpl extends AbstractComponent implemen } @Override + public X509CertificateWithKey getIdentityCertificateWithKey() { + AthenzCredentials copy = this.credentials; + return new X509CertificateWithKey(copy.getCertificate(), copy.getKeyPair().getPrivate()); + } + + @Override public Path certificatePath() { return athenzCredentialsService.certificatePath(); } + + @Override public Path privateKeyPath() { return athenzCredentialsService.privateKeyPath(); } + + @Override public Path athenzTruststorePath() { return ATHENZ_TRUST_STORE; } + + @Override public Path clientTruststorePath() { return CLIENT_TRUST_STORE; } + + @Override public SSLContext getRoleSslContext(String domain, String role) { // This ssl context should ideally be cached as it is quite expensive to create. try { @@ -255,7 +271,7 @@ public final class AthenzIdentityProviderImpl extends AbstractComponent implemen X509Certificate roleCertificate = client.getRoleCertificate(role, csr); return new SslContextBuilder() .withKeyStore(credentials.getKeyPair().getPrivate(), roleCertificate) - .withTrustStore(trustStore, KeyStoreType.JKS) + .withTrustStore(trustStore) .build(); } } @@ -298,9 +314,9 @@ public final class AthenzIdentityProviderImpl extends AbstractComponent implemen } } - private static SiaIdentityProvider createNodeIdentityProvider(IdentityConfig config, Path trustStore) { + private static SiaIdentityProvider createNodeIdentityProvider(IdentityConfig config) { return new SiaIdentityProvider( - new AthenzService(config.nodeIdentityName()), SiaUtils.DEFAULT_SIA_DIRECTORY, trustStore.toFile()); + new AthenzService(config.nodeIdentityName()), SiaUtils.DEFAULT_SIA_DIRECTORY, CLIENT_TRUST_STORE, ATHENZ_TRUST_STORE); } private boolean isExpired(AthenzCredentials credentials) { diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/utils/SiaUtils.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/utils/SiaUtils.java index 12a8c3f911e..894c04df233 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/utils/SiaUtils.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/utils/SiaUtils.java @@ -53,10 +53,11 @@ public class SiaUtils { } public static Path getCaCertificatesFile() { - // The contents of this is the same as /opt/yahoo/share/ssl/certs/athenz_certificate_bundle.pem installed - // by the yahoo_certificates_bundle RPM package, except the latter also contains a textual description - // (decoded) of the certificates. - return DEFAULT_SIA_DIRECTORY.resolve("certs").resolve("ca.cert.pem"); + return getCaCertificatesFile(DEFAULT_SIA_DIRECTORY); + } + + public static Path getCaCertificatesFile(Path root) { + return root.resolve("certs").resolve("ca.cert.pem"); } public static Optional<PrivateKey> readPrivateKeyFile(AthenzIdentity service) { diff --git a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identity/SiaIdentityProviderTest.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identity/SiaIdentityProviderTest.java index ce02860cc78..b7db502b1d0 100644 --- a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identity/SiaIdentityProviderTest.java +++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identity/SiaIdentityProviderTest.java @@ -2,15 +2,11 @@ package com.yahoo.vespa.athenz.identity; import com.yahoo.security.KeyAlgorithm; -import com.yahoo.security.KeyStoreBuilder; -import com.yahoo.security.KeyStoreType; -import com.yahoo.security.KeyStoreUtils; import com.yahoo.security.KeyUtils; import com.yahoo.security.SignatureAlgorithm; import com.yahoo.security.X509CertificateBuilder; import com.yahoo.security.X509CertificateUtils; import com.yahoo.vespa.athenz.api.AthenzService; -import com.yahoo.yolean.Exceptions; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -21,11 +17,11 @@ import java.io.IOException; import java.math.BigInteger; import java.nio.file.Files; import java.security.KeyPair; -import java.security.KeyStore; import java.security.cert.X509Certificate; import java.time.Duration; import java.time.Instant; +import static com.yahoo.yolean.Exceptions.uncheck; import static org.junit.Assert.assertNotNull; /** @@ -52,10 +48,10 @@ public class SiaIdentityProviderTest { SiaIdentityProvider provider = new SiaIdentityProvider( new AthenzService("domain", "service-name"), - keyFile, - certificateFile, - trustStoreFile, - SiaProviderConfig.TrustStoreType.Enum.jks); + keyFile.toPath(), + certificateFile.toPath(), + trustStoreFile.toPath(), + trustStoreFile.toPath()); assertNotNull(provider.getIdentitySslContext()); } @@ -76,10 +72,10 @@ public class SiaIdentityProviderTest { SiaIdentityProvider provider = new SiaIdentityProvider( new AthenzService("domain", "service-name"), - keyFile, - certificateFile, - trustStoreFile, - SiaProviderConfig.TrustStoreType.Enum.pem); + keyFile.toPath(), + certificateFile.toPath(), + trustStoreFile.toPath(), + trustStoreFile.toPath()); assertNotNull(provider.getIdentitySslContext()); } @@ -109,14 +105,11 @@ public class SiaIdentityProviderTest { private void createPemTrustStoreFile(X509Certificate certificate, File trustStoreFile) { var pemEncoded = X509CertificateUtils.toPem(certificate); - Exceptions.uncheck(() -> Files.writeString(trustStoreFile.toPath(), pemEncoded)); + uncheck(() -> Files.writeString(trustStoreFile.toPath(), pemEncoded)); } private void createTrustStoreFile(X509Certificate certificate, File trustStoreFile) { - KeyStore keystore = KeyStoreBuilder.withType(KeyStoreType.JKS) - .withCertificateEntry("dummy-cert", certificate) - .build(); - KeyStoreUtils.writeKeyStoreToFile(keystore, trustStoreFile.toPath()); + uncheck(() -> Files.writeString(trustStoreFile.toPath(), X509CertificateUtils.toPem(certificate))); } } |