summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorncs@verizonmedia.com>2020-05-14 16:58:07 +0200
committerBjørn Christian Seime <bjorncs@verizonmedia.com>2020-05-18 13:21:38 +0200
commitcf8af6d6ce0be3dd565b1f7a14f0648d482b3e42 (patch)
treec2ee7c98daf6f35a988f9e45cb48b78f84b761f9
parent73fa58e682c278b190ad89f7c74919b5e70b1d24 (diff)
Expose underlying certificate and private key from SiaIdentityProvider
Extend ServiceIdentityProvider interface with new methods. Add class that bundles certificate with private key. Use Path instead of File for better compatibility with mocked file system in unit tests.
-rw-r--r--security-utils/src/main/java/com/yahoo/security/X509CertificateWithKey.java33
-rw-r--r--security-utils/src/main/java/com/yahoo/security/tls/AutoReloadingX509KeyManager.java10
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identity/ServiceIdentityProvider.java10
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identity/SiaIdentityProvider.java32
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImpl.java17
-rw-r--r--vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identity/SiaIdentityProviderTest.java12
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());