diff options
4 files changed, 132 insertions, 7 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZtsClient.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZtsClient.java index eefd11bdeec..381896c11cf 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZtsClient.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZtsClient.java @@ -3,6 +3,8 @@ package com.yahoo.vespa.hosted.controller.api.integration.athenz; import com.yahoo.vespa.athenz.api.AthenzDomain; import com.yahoo.vespa.athenz.api.AthenzIdentity; +import com.yahoo.vespa.athenz.api.AthenzIdentityCertificate; +import com.yahoo.vespa.athenz.api.AthenzRoleCertificate; import java.util.List; @@ -13,4 +15,8 @@ public interface ZtsClient { List<AthenzDomain> getTenantDomainsForUser(AthenzIdentity principal); + AthenzIdentityCertificate getIdentityCertificate(); + + AthenzRoleCertificate getRoleCertificate(AthenzDomain roleDomain, String roleName); + } 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 893dafa5885..c3471a40234 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 @@ -5,12 +5,12 @@ import com.google.inject.Inject; import com.yahoo.athenz.auth.Principal; import com.yahoo.athenz.auth.impl.PrincipalAuthority; import com.yahoo.athenz.auth.impl.SimplePrincipal; +import com.yahoo.athenz.auth.impl.SimpleServiceIdentityProvider; import com.yahoo.athenz.auth.token.PrincipalToken; import com.yahoo.athenz.auth.util.Crypto; import com.yahoo.athenz.zms.ZMSClient; import com.yahoo.athenz.zts.ZTSClient; import com.yahoo.container.jdisc.Ckms; -import com.yahoo.container.jdisc.athenz.AthenzIdentityProvider; import com.yahoo.vespa.athenz.api.NToken; import com.yahoo.vespa.athenz.utils.AthenzIdentities; import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory; @@ -19,21 +19,21 @@ import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZtsClient; import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig; import java.security.PrivateKey; +import java.time.Duration; /** * @author bjorncs */ +// TODO Use SiaIdentityProvider public class AthenzClientFactoryImpl implements AthenzClientFactory { private final Ckms ckms; private final AthenzConfig config; private final AthenzPrincipalAuthority athenzPrincipalAuthority; - private final AthenzIdentityProvider identityProvider; @Inject - public AthenzClientFactoryImpl(Ckms ckms, AthenzIdentityProvider identityProvider, AthenzConfig config) { + public AthenzClientFactoryImpl(Ckms ckms, AthenzConfig config) { this.ckms = ckms; - this.identityProvider = identityProvider; this.config = config; this.athenzPrincipalAuthority = new AthenzPrincipalAuthority(config.principalHeaderName()); } @@ -43,7 +43,7 @@ public class AthenzClientFactoryImpl implements AthenzClientFactory { */ @Override public ZmsClient createZmsClientWithServicePrincipal() { - return new ZmsClientImpl(new ZMSClient(config.zmsUrl(), identityProvider.getIdentitySslContext()), config); + return new ZmsClientImpl(new ZMSClient(config.zmsUrl(), createServicePrincipal()), config); } /** @@ -51,7 +51,7 @@ public class AthenzClientFactoryImpl implements AthenzClientFactory { */ @Override public ZtsClient createZtsClientWithServicePrincipal() { - return new ZtsClientImpl(new ZTSClient(config.ztsUrl(), identityProvider.getIdentitySslContext()), config); + return new ZtsClientImpl(new ZTSClient(config.ztsUrl(), createServicePrincipal()), getServicePrivateKey(), config); } /** @@ -70,6 +70,20 @@ public class AthenzClientFactoryImpl implements AthenzClientFactory { } + private Principal createServicePrincipal() { + AthenzConfig.Service service = config.service(); + // TODO bjorncs: Cache principal token + SimpleServiceIdentityProvider identityProvider = + new SimpleServiceIdentityProvider( + athenzPrincipalAuthority, + config.domain(), + service.name(), + getServicePrivateKey(), + service.publicKeyId(), + Duration.ofMinutes(service.credentialsExpiryMinutes()).getSeconds()); + return identityProvider.getIdentity(config.domain(), service.name()); + } + private PrivateKey getServicePrivateKey() { AthenzConfig.Service service = config.service(); String privateKey = ckms.getSecret(service.privateKeySecretName(), service.privateKeyVersion()).trim(); 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 c3ed8f0a99f..0166c02db2e 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,17 +1,25 @@ // 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.athenz.api.AthenzDomain; import com.yahoo.vespa.athenz.api.AthenzIdentity; +import com.yahoo.vespa.athenz.api.AthenzIdentityCertificate; +import com.yahoo.vespa.athenz.api.AthenzRoleCertificate; import com.yahoo.vespa.athenz.api.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; @@ -27,10 +35,16 @@ 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 @@ -47,6 +61,47 @@ public class ZtsClientImpl implements ZtsClient { }); } + @Override + public AthenzIdentityCertificate getIdentityCertificate() { + return getOrThrow(() -> { + log.log(LogLevel.DEBUG, + String.format("postInstanceRefreshRequest(service=%s)", service.getFullName())); + InstanceRefreshRequest req = + ZTSClient.generateInstanceRefreshRequest( + service.getDomain().getName(), + service.getName(), + privateKey, + certificateDnsDomain, + (int) certExpiry.getSeconds()); + X509Certificate certificate = Crypto.loadX509Certificate( + ztsClient.postInstanceRefreshRequest(service.getDomain().getName(), 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.getName(), roleName)); + RoleCertificateRequest req = + ZTSClient.generateRoleCertificateRequest( + service.getDomain().getName(), + service.getName(), + roleDomain.getName(), + roleName, + privateKey, + certificateDnsDomain, + (int)certExpiry.getSeconds()); + X509Certificate roleCertificate = Crypto.loadX509Certificate( + ztsClient.postRoleCertificateRequest(roleDomain.getName(), roleName, req) + .getToken()); + return new AthenzRoleCertificate(roleCertificate, privateKey); + }); + } + private static <T> T getOrThrow(Supplier<T> wrappedCode) { try { return wrappedCode.get(); 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 4aa1c2b93a5..7aea79a93c6 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.athenz.api.AthenzDomain; import com.yahoo.vespa.athenz.api.AthenzIdentity; +import com.yahoo.vespa.athenz.api.AthenzIdentityCertificate; +import com.yahoo.vespa.athenz.api.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; @@ -32,4 +43,43 @@ public class ZtsClientMock implements ZtsClient { .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.getName(), roleDomain)); + try { + KeyPair keyPair = createKeyPair(); + String subject = String.format("CN=%s:role.%s", roleDomain.getName(), 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(); + } + } |