diff options
6 files changed, 90 insertions, 24 deletions
diff --git a/security-utils/src/main/java/com/yahoo/security/X509CertificateWithKey.java b/security-utils/src/main/java/com/yahoo/security/X509CertificateWithKey.java new file mode 100644 index 00000000000..4772de5c1fb --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/X509CertificateWithKey.java @@ -0,0 +1,33 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.List; + +/** + * Wraps a {@link java.security.cert.X509Certificate} with its {@link java.security.PrivateKey}. + * Primary motivation is APIs where the callee must correctly observe an atomic update of both certificate and key. + * + * @author bjorncs + */ +public class X509CertificateWithKey { + + private final List<X509Certificate> certificate; + private final PrivateKey privateKey; + + public X509CertificateWithKey(X509Certificate certificate, PrivateKey privateKey) { + this(Collections.singletonList(certificate), privateKey); + } + + public X509CertificateWithKey(List<X509Certificate> certificate, PrivateKey privateKey) { + if (certificate.isEmpty()) throw new IllegalArgumentException(); + this.certificate = certificate; + this.privateKey = privateKey; + } + + public X509Certificate certificate() { return certificate.get(0); } + public List<X509Certificate> certificateWithIntermediates() { return certificate; } + public PrivateKey privateKey() { return privateKey; } +} diff --git a/security-utils/src/main/java/com/yahoo/security/tls/AutoReloadingX509KeyManager.java b/security-utils/src/main/java/com/yahoo/security/tls/AutoReloadingX509KeyManager.java index 18764f51dc5..d4e74e22e40 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/AutoReloadingX509KeyManager.java +++ b/security-utils/src/main/java/com/yahoo/security/tls/AutoReloadingX509KeyManager.java @@ -5,19 +5,20 @@ import com.yahoo.security.KeyStoreBuilder; import com.yahoo.security.KeyStoreType; import com.yahoo.security.KeyUtils; import com.yahoo.security.X509CertificateUtils; +import com.yahoo.security.X509CertificateWithKey; import javax.net.ssl.SSLEngine; import javax.net.ssl.X509ExtendedKeyManager; import java.io.IOException; import java.io.UncheckedIOException; import java.net.Socket; -import java.nio.file.Files; import java.nio.file.Path; import java.security.KeyStore; import java.security.Principal; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.time.Duration; +import java.util.Arrays; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -59,6 +60,13 @@ public class AutoReloadingX509KeyManager extends X509ExtendedKeyManager implemen return new AutoReloadingX509KeyManager(privateKeyFile, certificatesFile); } + public X509CertificateWithKey getCurrentCertificateWithKey() { + X509ExtendedKeyManager manager = mutableX509KeyManager.currentManager(); + X509Certificate[] certificateChain = manager.getCertificateChain(CERTIFICATE_ALIAS); + PrivateKey privateKey = manager.getPrivateKey(CERTIFICATE_ALIAS); + return new X509CertificateWithKey(Arrays.asList(certificateChain), privateKey); + } + private static KeyStore createKeystore(Path privateKey, Path certificateChain) { try { return KeyStoreBuilder.withType(KeyStoreType.PKCS12) 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..180d052c8dc 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,22 @@ 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 { AthenzIdentity identity(); SSLContext getIdentitySslContext(); + X509CertificateWithKey getIdentityCertificateWithKey(); + Path certificatePath(); + Path privateKeyPath(); } 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..082925048cb 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 @@ -5,13 +5,13 @@ 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 +26,38 @@ 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; @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()), + 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()), config.trustStoreType()); } public SiaIdentityProvider(AthenzIdentity service, Path siaPath, - File trustStoreFile) { + Path trustStoreFile) { this(service, - SiaUtils.getPrivateKeyFile(siaPath, service).toFile(), - SiaUtils.getCertificateFile(siaPath, service).toFile(), + SiaUtils.getPrivateKeyFile(siaPath, service), + SiaUtils.getCertificateFile(siaPath, service), trustStoreFile, SiaProviderConfig.TrustStoreType.Enum.jks); } public SiaIdentityProvider(AthenzIdentity service, - File privateKeyFile, - File certificateFile, - File trustStoreFile, + Path privateKeyFile, + Path certificateFile, + Path trustStoreFile, SiaProviderConfig.TrustStoreType.Enum trustStoreType) { 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, trustStoreFile, trustStoreType); + this.certificateFile = certificateFile; + this.privateKeyFile = privateKeyFile; } @Override @@ -66,6 +70,10 @@ public class SiaIdentityProvider extends AbstractComponent implements ServiceIde return sslContext; } + @Override public X509CertificateWithKey getIdentityCertificateWithKey() { return keyManager.getCurrentCertificateWithKey(); } + @Override public Path certificatePath() { return certificateFile; } + @Override public Path privateKeyPath() { return privateKeyFile; } + private static SSLContext createIdentitySslContext(AutoReloadingX509KeyManager keyManager, Path trustStoreFile, SiaProviderConfig.TrustStoreType.Enum trustStoreType) { var builder = new SslContextBuilder(); 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 71a4c1a9954..a52ad159fdc 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 @@ -10,11 +10,11 @@ import com.yahoo.container.core.identity.IdentityConfig; import com.yahoo.container.jdisc.athenz.AthenzIdentityProvider; import com.yahoo.container.jdisc.athenz.AthenzIdentityProviderException; import com.yahoo.jdisc.Metric; -import java.util.logging.Level; 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; @@ -44,6 +44,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.function.Function; +import java.util.logging.Level; import java.util.logging.Logger; import static com.yahoo.security.KeyStoreType.JKS; @@ -55,6 +56,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()); @@ -176,6 +179,16 @@ public final class AthenzIdentityProviderImpl extends AbstractComponent implemen } @Override + public X509CertificateWithKey getIdentityCertificateWithKey() { + AthenzCredentials copy = this.credentials; + return new X509CertificateWithKey(copy.getCertificate(), copy.getKeyPair().getPrivate()); + } + + // The files should ideally not be used directly, must be implemented later if necessary + @Override public Path certificatePath() { throw new UnsupportedOperationException(); } + @Override public Path privateKeyPath() { throw new UnsupportedOperationException(); } + + @Override public SSLContext getRoleSslContext(String domain, String role) { // This ssl context should ideally be cached as it is quite expensive to create. try { @@ -288,7 +301,7 @@ public final class AthenzIdentityProviderImpl extends AbstractComponent implemen private static SiaIdentityProvider createNodeIdentityProvider(IdentityConfig config, Path trustStore) { return new SiaIdentityProvider( - new AthenzService(config.nodeIdentityName()), SiaUtils.DEFAULT_SIA_DIRECTORY, trustStore.toFile()); + new AthenzService(config.nodeIdentityName()), SiaUtils.DEFAULT_SIA_DIRECTORY, trustStore); } private boolean isExpired(AthenzCredentials credentials) { 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..1fe32561f82 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 @@ -52,9 +52,9 @@ public class SiaIdentityProviderTest { SiaIdentityProvider provider = new SiaIdentityProvider( new AthenzService("domain", "service-name"), - keyFile, - certificateFile, - trustStoreFile, + keyFile.toPath(), + certificateFile.toPath(), + trustStoreFile.toPath(), SiaProviderConfig.TrustStoreType.Enum.jks); assertNotNull(provider.getIdentitySslContext()); @@ -76,9 +76,9 @@ public class SiaIdentityProviderTest { SiaIdentityProvider provider = new SiaIdentityProvider( new AthenzService("domain", "service-name"), - keyFile, - certificateFile, - trustStoreFile, + keyFile.toPath(), + certificateFile.toPath(), + trustStoreFile.toPath(), SiaProviderConfig.TrustStoreType.Enum.pem); assertNotNull(provider.getIdentitySslContext()); |