diff options
author | Bjørn Christian Seime <bjorncs@oath.com> | 2018-05-02 12:51:40 +0200 |
---|---|---|
committer | Bjørn Christian Seime <bjorncs@oath.com> | 2018-05-03 13:09:47 +0200 |
commit | b668043461ca16db770691a23ee711d9a8861d0b (patch) | |
tree | 50f564307237b75786bde4040f68421c2831c1e4 /vespa-athenz | |
parent | 9ba951190c3174546fb65943814ea6367a1adfc1 (diff) |
Add new ZtsClient implementation
Diffstat (limited to 'vespa-athenz')
8 files changed, 347 insertions, 0 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 new file mode 100644 index 00000000000..d1b0c08d4ca --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/DefaultZtsClient.java @@ -0,0 +1,125 @@ +// 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.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.yahoo.vespa.athenz.api.AthenzService; +import com.yahoo.vespa.athenz.api.NToken; +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.tls.Pkcs10Csr; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.DefaultHttpRequestRetryHandler; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; +import org.eclipse.jetty.http.HttpStatus; + +import javax.net.ssl.SSLContext; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URI; + +/** + * Default implementation of {@link ZtsClient} + * + * @author bjorncs + * @author mortent + */ +public class DefaultZtsClient implements ZtsClient { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + private final URI ztsUrl; + private final CloseableHttpClient client; + + public DefaultZtsClient(URI ztsUrl, SSLContext sslContext) { + this.ztsUrl = ztsUrl; + this.client = HttpClientBuilder.create() + .setRetryHandler(new DefaultHttpRequestRetryHandler(3, /*requestSentRetryEnabled*/true)) + .setSslcontext(sslContext) + .build(); + } + + @Override + public InstanceIdentity registerInstance(AthenzService providerIdentity, + AthenzService instanceIdentity, + String instanceId, + String attestationData, + boolean requestServiceToken, + Pkcs10Csr csr) { + InstanceRegisterInformation payload = + new InstanceRegisterInformation(providerIdentity, instanceIdentity, attestationData, csr, requestServiceToken); + HttpUriRequest request = RequestBuilder.post() + .setUri(ztsUrl.resolve("instance/")) + .setEntity(toJsonStringEntity(payload)) + .build(); + try (CloseableHttpResponse response = client.execute(request)) { + return getInstanceIdentity(response); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public InstanceIdentity refreshInstance(AthenzService providerIdentity, + AthenzService instanceIdentity, + String instanceId, + boolean requestServiceToken, + Pkcs10Csr csr) { + InstanceRefreshInformation payload = new InstanceRefreshInformation(csr, requestServiceToken); + URI uri = ztsUrl.resolve( + String.format("instance/%s/%s/%s/%s", + providerIdentity.getFullName(), + instanceIdentity.getDomain().getName(), + instanceIdentity.getName(), + instanceId)); + HttpUriRequest request = RequestBuilder.post() + .setUri(uri) + .setEntity(toJsonStringEntity(payload)) + .build(); + try (CloseableHttpResponse response = client.execute(request)) { + return getInstanceIdentity(response); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static InstanceIdentity getInstanceIdentity(HttpResponse response) throws IOException { + if (HttpStatus.isSuccess(response.getStatusLine().getStatusCode())) { + InstanceIdentityCredentials entity = + objectMapper.readValue(response.getEntity().getContent(), InstanceIdentityCredentials.class); + return entity.getServiceToken() != null + ? new InstanceIdentity(entity.getX509Certificate(), new NToken(entity.getServiceToken())) + : new InstanceIdentity(entity.getX509Certificate()); + } else { + String message = EntityUtils.toString(response.getEntity()); + throw new ZtsClientException( + String.format("Unable to get identity. http code/message: %d/%s", + response.getStatusLine().getStatusCode(), message)); + } + } + + private static StringEntity toJsonStringEntity(Object entity) { + try { + return new StringEntity(objectMapper.writeValueAsString(entity), ContentType.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public void close() { + try { + client.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/InstanceIdentity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/InstanceIdentity.java new file mode 100644 index 00000000000..34548351b23 --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/InstanceIdentity.java @@ -0,0 +1,34 @@ +// 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.NToken; + +import java.security.cert.X509Certificate; +import java.util.Optional; + +/** + * The identity of an instance of a launched service. + * + * @author bjorncs + */ +public class InstanceIdentity { + private final X509Certificate certificate; + private final NToken nToken; + + public InstanceIdentity(X509Certificate certificate) { + this(certificate, null); + } + + public InstanceIdentity(X509Certificate certificate, NToken nToken) { + this.certificate = certificate; + this.nToken = nToken; + } + + public X509Certificate certificate() { + return certificate; + } + + public Optional<NToken> nToken() { + return Optional.ofNullable(nToken); + } +} 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 new file mode 100644 index 00000000000..83041289451 --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/ZtsClient.java @@ -0,0 +1,39 @@ +// 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.AthenzService; +import com.yahoo.vespa.athenz.tls.Pkcs10Csr; + +import javax.net.ssl.SSLContext; + +/** + * Interface for a ZTS client. + * + * @author bjorncs + */ +public interface ZtsClient extends AutoCloseable { + + /** + * Register an instance using the specified provider. + * + * @param attestationData The signed identity documented serialized to a string. + * @return A x509 certificate + service token (optional) + */ + InstanceIdentity registerInstance(AthenzService providerIdentity, + AthenzService instanceIdentity, + String instanceId, + String attestationData, + boolean requestServiceToken, + Pkcs10Csr csr); + + /** + * Refresh an existing instance + * + * @return A x509 certificate + service token (optional) + */ + InstanceIdentity refreshInstance(AthenzService providerIdentity, + AthenzService instanceIdentity, + String instanceId, + boolean requestServiceToken, + Pkcs10Csr csr); +} diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/ZtsClientException.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/ZtsClientException.java new file mode 100644 index 00000000000..3d3696ad870 --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/ZtsClientException.java @@ -0,0 +1,18 @@ +// 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; + +/** + * An exception that can be thrown by {@link ZtsClient} implementations. + * + * @author bjorncs + */ +public class ZtsClientException extends RuntimeException { + + public ZtsClientException(String message) { + super(message); + } + + public ZtsClientException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceIdentityCredentials.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceIdentityCredentials.java new file mode 100644 index 00000000000..5c265f14813 --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceIdentityCredentials.java @@ -0,0 +1,49 @@ +// 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.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; + +/** + * Used for deserializing response from ZTS + * + * @author mortent + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class InstanceIdentityCredentials { + @JsonProperty("x509Certificate") private final X509Certificate x509Certificate; + @JsonProperty("serviceToken") private final String serviceToken; + + public InstanceIdentityCredentials( + @JsonProperty("x509Certificate") @JsonDeserialize(using = X509CertificateDeserializer.class) X509Certificate x509Certificate, + @JsonProperty("serviceToken") String serviceToken) { + this.x509Certificate = x509Certificate; + this.serviceToken = serviceToken; + } + + public X509Certificate getX509Certificate() { + return x509Certificate; + } + + public String getServiceToken() { + return serviceToken; + } + + 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/InstanceRefreshInformation.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceRefreshInformation.java new file mode 100644 index 00000000000..6c956ddb410 --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceRefreshInformation.java @@ -0,0 +1,27 @@ +// 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.yahoo.vespa.athenz.tls.Pkcs10Csr; +import com.yahoo.vespa.athenz.tls.Pkcs10CsrUtils; + +/** + * @author bjorncs + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class InstanceRefreshInformation { + + @JsonProperty("csr") + private final String csr; + @JsonProperty("token") + private final boolean requestServiceToken; + + public InstanceRefreshInformation(Pkcs10Csr csr, + boolean requestServiceToken) { + this.csr = Pkcs10CsrUtils.toPem(csr); + this.requestServiceToken = requestServiceToken; + } +} diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceRegisterInformation.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceRegisterInformation.java new file mode 100644 index 00000000000..a70083f9556 --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceRegisterInformation.java @@ -0,0 +1,47 @@ +// 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.yahoo.vespa.athenz.api.AthenzService; +import com.yahoo.vespa.athenz.tls.Pkcs10Csr; +import com.yahoo.vespa.athenz.tls.Pkcs10CsrUtils; + +/** + * Used for serializing request to ZTS + * + * @author mortent + * @author bjorncs + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class InstanceRegisterInformation { + @JsonProperty("provider") + private final String provider; + @JsonProperty("domain") + private final String domain; + @JsonProperty("service") + private final String service; + @JsonProperty("attestationData") + private final String attestationData; + @JsonProperty("ssh") + private final String ssh = null; // Not needed + @JsonProperty("csr") + private final String csr; + @JsonProperty("token") + private final boolean token; + + public InstanceRegisterInformation(AthenzService providerIdentity, + AthenzService instanceIdentity, + String attestationData, + Pkcs10Csr csr, + boolean requestServiceToken) { + this.provider = providerIdentity.getFullName(); + this.domain = instanceIdentity.getDomain().getName(); + this.service = instanceIdentity.getName(); + this.attestationData = attestationData; + this.csr = Pkcs10CsrUtils.toPem(csr); + this.token = requestServiceToken; + } +} diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/package-info.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/package-info.java new file mode 100644 index 00000000000..cffe22ae1c0 --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/package-info.java @@ -0,0 +1,8 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +/** + * @author bjorncs + */ +@ExportPackage +package com.yahoo.vespa.athenz.client.zts; + +import com.yahoo.osgi.annotation.ExportPackage;
\ No newline at end of file |