diff options
author | Morten Tokle <morten.tokle@gmail.com> | 2017-12-13 11:11:00 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-12-13 11:11:00 +0100 |
commit | d0a2b2f3aec7286e8504278f52135f737b106a3b (patch) | |
tree | ab7fceac2d9b8e97e155de9cb4f2b539a02d36dc /controller-server | |
parent | 6db82ba420e68ebc11e04ad691506f6bd0309d45 (diff) | |
parent | ffc9abecd79e60587f72893890cef28047239904 (diff) |
Merge pull request #4430 from vespa-engine/bjorncs/athenz-ssl-context
Bjorncs/athenz ssl context
Diffstat (limited to 'controller-server')
5 files changed, 223 insertions, 9 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java index 540cdb39630..a91604f937b 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java @@ -18,7 +18,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.security.KeyService; import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig; import java.security.PrivateKey; -import java.util.concurrent.TimeUnit; +import java.time.Duration; import static com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUtils.USER_PRINCIPAL_DOMAIN; @@ -51,7 +51,7 @@ public class AthenzClientFactoryImpl implements AthenzClientFactory { */ @Override public ZtsClient createZtsClientWithServicePrincipal() { - return new ZtsClientImpl(new ZTSClient(config.ztsUrl(), createServicePrincipal()), config); + return new ZtsClientImpl(new ZTSClient(config.ztsUrl(), createServicePrincipal()), getServicePrivateKey(), config); } /** @@ -75,8 +75,12 @@ public class AthenzClientFactoryImpl implements AthenzClientFactory { // TODO bjorncs: Cache principal token SimpleServiceIdentityProvider identityProvider = new SimpleServiceIdentityProvider( - athenzPrincipalAuthority, config.domain(), service.name(), - getServicePrivateKey(), service.publicKeyId(), /*tokenTimeout*/TimeUnit.HOURS.toSeconds(1)); + athenzPrincipalAuthority, + config.domain(), + service.name(), + getServicePrivateKey(), + service.publicKeyId(), + Duration.ofMinutes(service.credentialsExpiryMinutes()).getSeconds()); return identityProvider.getIdentity(config.domain(), service.name()); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzSslContextProviderImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzSslContextProviderImpl.java new file mode 100644 index 00000000000..3a7a72ac8ae --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzSslContextProviderImpl.java @@ -0,0 +1,87 @@ +// 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.controller.athenz.impl; + +import com.google.inject.Inject; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentityCertificate; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzSslContextProvider; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZtsClient; +import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; + +/** + * @author bjorncs + */ +public class AthenzSslContextProviderImpl implements AthenzSslContextProvider { + + private final AthenzClientFactory clientFactory; + private final AthenzConfig config; + + @Inject + public AthenzSslContextProviderImpl(AthenzClientFactory clientFactory, AthenzConfig config) { + this.clientFactory = clientFactory; + this.config = config; + } + + @Override + public SSLContext get() { + return createSslContext(); + } + + private SSLContext createSslContext() { + try { + SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); + sslContext.init(createKeyManagersWithServiceCertificate(clientFactory.createZtsClientWithServicePrincipal()), + createTrustManagersWithAthenzCa(config), + null); + return sslContext; + } catch (NoSuchAlgorithmException | KeyManagementException e) { + throw new RuntimeException(e); + } + } + + private static KeyManager[] createKeyManagersWithServiceCertificate(ZtsClient ztsClient) { + try { + AthenzIdentityCertificate identityCertificate = ztsClient.getIdentityCertificate(); + KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(null); + keyStore.setKeyEntry("athenz-controller-key", + identityCertificate.getPrivateKey(), + new char[0], + new Certificate[]{identityCertificate.getCertificate()}); + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, new char[0]); + return keyManagerFactory.getKeyManagers(); + } catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException | CertificateException | IOException e) { + throw new RuntimeException(e); + } + } + + private static TrustManager[] createTrustManagersWithAthenzCa(AthenzConfig config) { + try { + KeyStore trustStore = KeyStore.getInstance("JKS"); + try (FileInputStream in = new FileInputStream(config.athenzCaTrustStore())) { + trustStore.load(in, "changeit".toCharArray()); + } + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(trustStore); + return trustManagerFactory.getTrustManagers(); + } catch (CertificateException | IOException | KeyStoreException | NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZtsClientImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZtsClientImpl.java index 76c000936e3..a29f2e81fba 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZtsClientImpl.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZtsClientImpl.java @@ -1,18 +1,27 @@ // 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.controller.athenz.impl; +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.zts.InstanceRefreshRequest; +import com.yahoo.athenz.zts.RoleCertificateRequest; import com.yahoo.athenz.zts.TenantDomains; import com.yahoo.athenz.zts.ZTSClient; import com.yahoo.athenz.zts.ZTSClientException; import com.yahoo.log.LogLevel; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentity; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentityCertificate; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzRoleCertificate; import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzService; import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZtsClient; import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZtsException; import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.time.Duration; import java.util.List; +import java.util.function.Supplier; import java.util.logging.Logger; import static java.util.stream.Collectors.toList; @@ -26,24 +35,78 @@ public class ZtsClientImpl implements ZtsClient { private final ZTSClient ztsClient; private final AthenzService service; + private final PrivateKey privateKey; + private final String certificateDnsDomain; + private final Duration certExpiry; - public ZtsClientImpl(ZTSClient ztsClient, AthenzConfig config) { + public ZtsClientImpl(ZTSClient ztsClient, PrivateKey privateKey, AthenzConfig config) { this.ztsClient = ztsClient; this.service = new AthenzService(config.domain(), config.service().name()); + this.privateKey = privateKey; + this.certificateDnsDomain = config.certDnsDomain(); + this.certExpiry = Duration.ofMinutes(config.service().credentialsExpiryMinutes()); } @Override public List<AthenzDomain> getTenantDomainsForUser(AthenzIdentity identity) { - log.log(LogLevel.DEBUG, String.format( - "getTenantDomains(domain=%s, identity=%s, rolename=admin, service=%s)", - service.getDomain().id(), identity.getFullName(), service.getFullName())); - try { + return getOrThrow(() -> { + log.log(LogLevel.DEBUG, String.format( + "getTenantDomains(domain=%s, identity=%s, rolename=admin, service=%s)", + service.getDomain().id(), identity.getFullName(), service.getFullName())); TenantDomains domains = ztsClient.getTenantDomains( service.getDomain().id(), identity.getFullName(), "admin", service.getName()); return domains.getTenantDomainNames().stream() .map(AthenzDomain::new) .collect(toList()); + }); + } + + @Override + public AthenzIdentityCertificate getIdentityCertificate() { + return getOrThrow(() -> { + log.log(LogLevel.DEBUG, + String.format("postInstanceRefreshRequest(service=%s)", service.getFullName())); + InstanceRefreshRequest req = + ZTSClient.generateInstanceRefreshRequest( + service.getDomain().id(), + service.getName(), + privateKey, + certificateDnsDomain, + (int) certExpiry.getSeconds()); + X509Certificate certificate = Crypto.loadX509Certificate( + ztsClient.postInstanceRefreshRequest(service.getDomain().id(), service.getName(), req) + .getCertificate()); + return new AthenzIdentityCertificate(certificate, privateKey); + }); + } + + @Override + public AthenzRoleCertificate getRoleCertificate(AthenzDomain roleDomain, String roleName) { + return getOrThrow(() -> { + log.log(LogLevel.DEBUG, + String.format("postRoleCertificateRequest(service=%s, roleDomain=%s, roleName=%s)", + service.getFullName(), roleDomain.id(), roleName)); + RoleCertificateRequest req = + ZTSClient.generateRoleCertificateRequest( + service.getDomain().id(), + service.getName(), + roleDomain.id(), + roleName, + privateKey, + certificateDnsDomain, + (int)certExpiry.getSeconds()); + X509Certificate roleCertificate = Crypto.loadX509Certificate( + ztsClient.postRoleCertificateRequest(roleDomain.id(), roleName, req) + .getToken()); + return new AthenzRoleCertificate(roleCertificate, privateKey); + }); + } + + private static <T> T getOrThrow(Supplier<T> wrappedCode) { + try { + return wrappedCode.get(); } catch (ZTSClientException e) { + log.warning("Error from Athenz: " + e.getMessage()); throw new ZtsException(e.getCode(), e); } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZtsClientMock.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZtsClientMock.java index ee43181a358..d778fb550ed 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZtsClientMock.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZtsClientMock.java @@ -1,10 +1,21 @@ // 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.controller.athenz.mock; +import com.yahoo.athenz.auth.util.Crypto; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentity; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentityCertificate; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzRoleCertificate; import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZtsClient; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import java.io.IOException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.cert.X509Certificate; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -31,4 +42,44 @@ public class ZtsClientMock implements ZtsClient { .map(domain -> domain.name) .collect(toList()); } + + @Override + public AthenzIdentityCertificate getIdentityCertificate() { + log.log(Level.INFO, "getIdentityCertificate()"); + try { + KeyPair keyPair = createKeyPair(); + String subject = "CN=controller"; + return new AthenzIdentityCertificate(createCertificate(keyPair, subject), keyPair.getPrivate()); + } catch (NoSuchAlgorithmException | OperatorCreationException | IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public AthenzRoleCertificate getRoleCertificate(AthenzDomain roleDomain, String roleName) { + log.log(Level.INFO, + String.format("getRoleCertificate(roleDomain=%s, roleName=%s)", roleDomain.id(), roleDomain)); + try { + KeyPair keyPair = createKeyPair(); + String subject = String.format("CN=%s:role.%s", roleDomain.id(), roleName); + return new AthenzRoleCertificate(createCertificate(keyPair, subject), keyPair.getPrivate()); + } catch (NoSuchAlgorithmException | OperatorCreationException | IOException e) { + throw new RuntimeException(e); + } + } + + private static X509Certificate createCertificate(KeyPair keyPair, String subject) throws + OperatorCreationException, IOException { + PKCS10CertificationRequest csr = + Crypto.getPKCS10CertRequest( + Crypto.generateX509CSR(keyPair.getPrivate(), subject, null)); + return Crypto.generateX509Certificate(csr, keyPair.getPrivate(), new X500Name(subject), 3600, false); + } + + private static KeyPair createKeyPair() throws NoSuchAlgorithmException { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(512); + return keyGen.genKeyPair(); + } + } diff --git a/controller-server/src/main/resources/configdefinitions/athenz.def b/controller-server/src/main/resources/configdefinitions/athenz.def index 6d10f3dee28..068b1d353ba 100644 --- a/controller-server/src/main/resources/configdefinitions/athenz.def +++ b/controller-server/src/main/resources/configdefinitions/athenz.def @@ -17,6 +17,12 @@ domain string userAuthenticationPassThruAttribute string # TODO Remove once migrated to Okta +# Path to Athenz CA JKS trust store +athenzCaTrustStore string + +# Certificate DNS domain +certDnsDomain string + # Athenz service name for controller identity service.name string @@ -28,3 +34,6 @@ service.privateKeyVersion int # Name of Athenz service private key secret service.privateKeySecretName string + +# Expiry of service principal token and certificate +service.credentialsExpiryMinutes int default=43200 # 30 days |