diff options
author | Bjørn Christian Seime <bjorncs@oath.com> | 2018-05-11 18:09:51 +0200 |
---|---|---|
committer | Bjørn Christian Seime <bjorncs@oath.com> | 2018-05-11 18:10:23 +0200 |
commit | 6e8c0dfb89d021d8e6e9d8c1f9a982feae5efb46 (patch) | |
tree | d39bbcb1b4006b1623c26554db08c8960a3b1bbd /vespa-athenz | |
parent | 1f00143d66ef24f1fad190d1028583fef5da29b3 (diff) |
Add methods to ZtsClient for retrieving role token and certificate
Diffstat (limited to 'vespa-athenz')
5 files changed, 257 insertions, 2 deletions
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/DefaultZtsClient.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/DefaultZtsClient.java index 71fa9b3d23d..3f1531ad991 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/DefaultZtsClient.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/DefaultZtsClient.java @@ -3,13 +3,21 @@ package com.yahoo.vespa.athenz.client.zts; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.yahoo.vespa.athenz.api.AthenzDomain; +import com.yahoo.vespa.athenz.api.AthenzIdentity; import com.yahoo.vespa.athenz.api.AthenzService; import com.yahoo.vespa.athenz.api.NToken; +import com.yahoo.vespa.athenz.api.ZToken; import com.yahoo.vespa.athenz.client.zts.bindings.InstanceIdentityCredentials; import com.yahoo.vespa.athenz.client.zts.bindings.InstanceRefreshInformation; import com.yahoo.vespa.athenz.client.zts.bindings.InstanceRegisterInformation; +import com.yahoo.vespa.athenz.client.zts.bindings.RoleCertificateRequestEntity; +import com.yahoo.vespa.athenz.client.zts.bindings.RoleCertificateResponseEntity; +import com.yahoo.vespa.athenz.client.zts.bindings.RoleTokenResponseEntity; import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider; import com.yahoo.vespa.athenz.tls.Pkcs10Csr; +import com.yahoo.vespa.athenz.tls.Pkcs10CsrBuilder; import org.apache.http.HttpResponse; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpUriRequest; @@ -23,13 +31,21 @@ import org.apache.http.util.EntityUtils; import org.eclipse.jetty.http.HttpStatus; import javax.net.ssl.SSLContext; +import javax.security.auth.x500.X500Principal; import java.io.IOException; import java.io.UncheckedIOException; import java.net.URI; +import java.security.KeyPair; +import java.security.cert.X509Certificate; +import java.time.Duration; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import static com.yahoo.vespa.athenz.tls.SignatureAlgorithm.SHA256_WITH_RSA; +import static com.yahoo.vespa.athenz.tls.SubjectAlternativeName.Type.DNS_NAME; +import static com.yahoo.vespa.athenz.tls.SubjectAlternativeName.Type.RFC822_NAME; + /** * Default implementation of {@link ZtsClient} * @@ -38,17 +54,19 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; */ public class DefaultZtsClient implements ZtsClient { - private static final ObjectMapper objectMapper = new ObjectMapper(); + private static final ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule()); private final URI ztsUrl; private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final AthenzIdentity identity; private final ServiceIdentityProvider identityProvider; private final ServiceIdentityProviderListener identityListener; private volatile CloseableHttpClient client; - public DefaultZtsClient(URI ztsUrl, SSLContext sslContext) { + public DefaultZtsClient(URI ztsUrl, AthenzIdentity identity, SSLContext sslContext) { this.ztsUrl = addTrailingSlash(ztsUrl); this.client = createHttpClient(sslContext); + this.identity = identity; this.identityProvider = null; this.identityListener = null; } @@ -56,6 +74,7 @@ public class DefaultZtsClient implements ZtsClient { public DefaultZtsClient(URI ztsUrl, ServiceIdentityProvider identityProvider) { this.ztsUrl = addTrailingSlash(ztsUrl); this.client = createHttpClient(identityProvider.getIdentitySslContext()); + this.identity = identityProvider.identity(); this.identityProvider = identityProvider; this.identityListener = new ServiceIdentityProviderListener(); identityProvider.addIdentityListener(this.identityListener); @@ -105,6 +124,60 @@ public class DefaultZtsClient implements ZtsClient { }); } + @Override + public ZToken getRoleToken(AthenzDomain domain) { + return getRoleToken(domain, null); + } + + @Override + public ZToken getRoleToken(AthenzDomain domain, String roleName) { + URI uri = ztsUrl.resolve(String.format("domain/%s/token", domain.getName())); + RequestBuilder requestBuilder = RequestBuilder.get(uri) + .addHeader("Content-Type", "application/json"); + if (roleName != null) { + requestBuilder.addParameter("role", roleName); + } + HttpUriRequest request = requestBuilder.build(); + return withClient(client -> { + try (CloseableHttpResponse response = client.execute(request)) { + RoleTokenResponseEntity roleTokenResponseEntity = readEntity(response, RoleTokenResponseEntity.class); + return roleTokenResponseEntity.token; + } + }); + } + + @Override + public X509Certificate getRoleCertificate(AthenzDomain domain, + String roleName, + Duration expiry, + KeyPair keyPair, + String cloud) { + X500Principal principal = new X500Principal(String.format("cn=%s:role.%s", domain.getName(), roleName)); + Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(principal, keyPair, SHA256_WITH_RSA) + .addSubjectAlternativeName(DNS_NAME, String.format("%s.%s.%s", identity.getName(), identity.getDomainName().replace('.', '-'), cloud)) + .addSubjectAlternativeName(RFC822_NAME, String.format("%s.%s@%s", identity.getDomainName(), identity.getName(), cloud)) + .build(); + RoleCertificateRequestEntity requestEntity = new RoleCertificateRequestEntity(csr, expiry); + URI uri = ztsUrl.resolve(String.format("domain/%s/role/%s/token", domain.getName(), roleName)); + HttpUriRequest request = RequestBuilder.post(uri) + .setEntity(toJsonStringEntity(requestEntity)) + .build(); + return withClient(client -> { + try (CloseableHttpResponse response = client.execute(request)) { + RoleCertificateResponseEntity responseEntity = readEntity(response, RoleCertificateResponseEntity.class); + return responseEntity.certificate; + } + }); + } + + @Override + public X509Certificate getRoleCertificate(AthenzDomain domain, + String roleName, + KeyPair keyPair, + String cloud) { + return getRoleCertificate(domain, roleName, null, keyPair, cloud); + } + private static InstanceIdentity getInstanceIdentity(HttpResponse response) throws IOException { InstanceIdentityCredentials entity = readEntity(response, InstanceIdentityCredentials.class); return entity.getServiceToken() != null diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/ZtsClient.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/ZtsClient.java index 3fdddbcd71f..9f3a3b33f25 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/ZtsClient.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/ZtsClient.java @@ -1,9 +1,15 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.athenz.client.zts; +import com.yahoo.vespa.athenz.api.AthenzDomain; import com.yahoo.vespa.athenz.api.AthenzService; +import com.yahoo.vespa.athenz.api.ZToken; import com.yahoo.vespa.athenz.tls.Pkcs10Csr; +import java.security.KeyPair; +import java.security.cert.X509Certificate; +import java.time.Duration; + /** * Interface for a ZTS client. * @@ -35,5 +41,52 @@ public interface ZtsClient extends AutoCloseable { boolean requestServiceToken, Pkcs10Csr csr); + /** + * Fetch a role token for the target domain + * + * @param domain Target domain + * @return A role token + */ + ZToken getRoleToken(AthenzDomain domain); + + /** + * Fetch a role token for the target domain and role + * + * @param domain Target domain + * @param roleName Target role + * @return A role token + */ + ZToken getRoleToken(AthenzDomain domain, String roleName); + + /** + * Fetch role certificate for the target domain and role + * + * @param domain Target domain + * @param roleName Target role + * @param expiry Certificate expiry + * @param keyPair Key pair which will be used to generate CSR (certificate signing request) + * @param cloud The cloud suffix used in DNS SAN entries + * @return A role certificate + */ + X509Certificate getRoleCertificate(AthenzDomain domain, + String roleName, + Duration expiry, + KeyPair keyPair, + String cloud); + + /** + * Fetch role certificate for the target domain and role + * + * @param domain Target domain + * @param roleName Target role + * @param keyPair Key pair which will be used to generate CSR (certificate signing request) + * @param cloud The cloud suffix used in DNS SAN entries + * @return A role certificate + */ + X509Certificate getRoleCertificate(AthenzDomain domain, + String roleName, + KeyPair keyPair, + String cloud); + void close(); } diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleCertificateRequestEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleCertificateRequestEntity.java new file mode 100644 index 00000000000..9c56e5a60d6 --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleCertificateRequestEntity.java @@ -0,0 +1,53 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.athenz.client.zts.bindings; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.yahoo.vespa.athenz.tls.Pkcs10Csr; +import com.yahoo.vespa.athenz.tls.Pkcs10CsrUtils; + +import java.io.IOException; +import java.time.Duration; + +/** + * @author bjorncs + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class RoleCertificateRequestEntity { + @JsonProperty("csr") + @JsonSerialize(using = CsrSerializer.class) + public final Pkcs10Csr csr; + + @JsonProperty("expiryTime") + @JsonSerialize(using = ExpirySerializer.class) + @JsonInclude(JsonInclude.Include.NON_NULL) + public final Duration expiryTime; + + public RoleCertificateRequestEntity(Pkcs10Csr csr, Duration expiryTime) { + this.csr = csr; + this.expiryTime = expiryTime; + } + + public static class CsrSerializer extends JsonSerializer<Pkcs10Csr> { + @Override + public void serialize(Pkcs10Csr csr, + JsonGenerator jsonGenerator, + SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeString(Pkcs10CsrUtils.toPem(csr)); + } + } + + public static class ExpirySerializer extends JsonSerializer<Duration> { + @Override + public void serialize(Duration duration, + JsonGenerator jsonGenerator, + SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeNumber(duration.getSeconds()); + } + } +} diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleCertificateResponseEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleCertificateResponseEntity.java new file mode 100644 index 00000000000..1b4bd463392 --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleCertificateResponseEntity.java @@ -0,0 +1,38 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.athenz.client.zts.bindings; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.yahoo.vespa.athenz.tls.X509CertificateUtils; + +import java.io.IOException; +import java.security.cert.X509Certificate; +import java.time.Instant; + +/** + * @author bjorncs + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class RoleCertificateResponseEntity { + public final X509Certificate certificate; + public final Instant expiry; + + @JsonCreator + public RoleCertificateResponseEntity(@JsonProperty("token") @JsonDeserialize(using = X509CertificateDeserializer.class) X509Certificate certificate, + @JsonProperty("expiryTime") Instant expiry) { + this.certificate = certificate; + this.expiry = expiry; + } + + public static class X509CertificateDeserializer extends JsonDeserializer<X509Certificate> { + @Override + public X509Certificate deserialize(JsonParser parser, DeserializationContext context) throws IOException { + return X509CertificateUtils.fromPem(parser.getValueAsString()); + } + } +} diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleTokenResponseEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleTokenResponseEntity.java new file mode 100644 index 00000000000..ec5cdc26ec1 --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleTokenResponseEntity.java @@ -0,0 +1,38 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.athenz.client.zts.bindings; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.yahoo.vespa.athenz.api.ZToken; + +import java.io.IOException; +import java.time.Instant; + +/** + * @author bjorncs + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class RoleTokenResponseEntity { + public final ZToken token; + public final Instant expiryTime; + + @JsonCreator + public RoleTokenResponseEntity(@JsonProperty("token") @JsonDeserialize(using = RoleTokenDeserializer.class) ZToken token, + @JsonProperty("expiryTime") Instant expiryTime) { + this.token = token; + this.expiryTime = expiryTime; + } + + public static class RoleTokenDeserializer extends JsonDeserializer<ZToken> { + @Override + public ZToken deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + return new ZToken(jsonParser.getValueAsString()); + } + } + +} |