diff options
author | Morten Tokle <morten.tokle@gmail.com> | 2017-10-23 13:18:50 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-10-23 13:18:50 +0200 |
commit | 79c1773d3353863451e0d75243e280c40b6658b0 (patch) | |
tree | e9c83538e92b63afabc418003cbccc754f21ba9d /container-disc/src/main | |
parent | 7f93a3bd50c534c92d3d7b37a4a5a47e512ff0d7 (diff) | |
parent | 0cf7be3cc88be1211c48cfb520e0c13920639c0b (diff) |
Merge pull request #3830 from vespa-engine/bjorncs/athenz-identity-provider-service
Initial Athenz copper argos implementation
Diffstat (limited to 'container-disc/src/main')
5 files changed, 307 insertions, 0 deletions
diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/AthenzIdentityProvider.java b/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/AthenzIdentityProvider.java new file mode 100644 index 00000000000..f67afdfc458 --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/AthenzIdentityProvider.java @@ -0,0 +1,121 @@ +package com.yahoo.container.jdisc.athenz; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.inject.Inject; +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.cloud.config.ConfigserverConfig; +import com.yahoo.component.AbstractComponent; +import com.yahoo.container.core.identity.IdentityConfig; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.operator.OperatorCreationException; + +import java.io.IOException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; + +/** + * @author mortent + */ +public final class AthenzIdentityProvider extends AbstractComponent { + + private InstanceIdentity instanceIdentity; + + private final String athenzUrl; + + private final String dnsSuffix; + private final String providerUniqueId; + private final String providerServiceName; + + @Inject + public AthenzIdentityProvider(IdentityConfig config, ConfigserverConfig configserverConfig) throws IOException { + this(config, new ServiceProviderApi(configserverConfig.serviceProviderEndpoint()), new AthenzService()); + } + + // Test only + public AthenzIdentityProvider(IdentityConfig config, ServiceProviderApi serviceProviderApi, AthenzService athenzService) throws IOException { + KeyPair keyPair = createKeyPair(); + String signedIdentityDocument = serviceProviderApi.getSignedIdentityDocument(); + this.athenzUrl = getZtsEndpoint(signedIdentityDocument); + dnsSuffix = getDnsSuffix(signedIdentityDocument); + providerUniqueId = getProviderUniqueId(signedIdentityDocument); + providerServiceName = getProviderServiceName(signedIdentityDocument); + + InstanceRegisterInformation instanceRegisterInformation = new InstanceRegisterInformation( + providerServiceName, + config.domain(), + config.serviceName(), + signedIdentityDocument, + createCSR(keyPair, config), + true + ); + instanceIdentity = athenzService.sendInstanceRegisterRequest(instanceRegisterInformation, athenzUrl); + } + + private String getProviderUniqueId(String signedIdentityDocument) throws IOException { + return getJsonNode(signedIdentityDocument, "provider-unique-id"); + } + + private String getDnsSuffix(String signedIdentityDocument) throws IOException { + return getJsonNode(signedIdentityDocument, "dns-suffix"); + } + + private String getProviderServiceName(String signedIdentityDocument) throws IOException { + return getJsonNode(signedIdentityDocument, "provider-service"); + } + + private String getZtsEndpoint(String signedIdentityDocument) throws IOException { + return getJsonNode(signedIdentityDocument, "zts-endpoint"); + } + + private String getJsonNode(String jsonString, String path) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + JsonNode jsonNode = mapper.readTree(jsonString); + return jsonNode.get(path).asText(); + } + + private KeyPair createKeyPair() { + try { + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + return kpg.generateKeyPair(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + private String createCSR(KeyPair keyPair, IdentityConfig identityConfig) throws IOException { + + try { + // Add SAN dnsname <service>.<domain-with-dashes>.<provider-dnsname-suffix> + // and SAN dnsname <provider-unique-instance-id>.instanceid.athenz.<provider-dnsname-suffix> + GeneralName[] sanDnsNames = new GeneralName[]{ + new GeneralName(GeneralName.dNSName, String.format("%s.%s.%s", + identityConfig.serviceName(), + identityConfig.domain().replace(".", "-"), + dnsSuffix)), + new GeneralName(GeneralName.dNSName, String.format("%s.instanceid.athenz.%s", + providerUniqueId, + dnsSuffix)) + }; + + return Crypto.generateX509CSR(keyPair.getPrivate(), + keyPair.getPublic(), + String.format("CN=%s.%s", identityConfig.domain(), identityConfig.serviceName()), + sanDnsNames); + } catch (OperatorCreationException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + public String getNToken() { + return instanceIdentity.getServiceToken(); + } + + public String getX509Cert() { + return instanceIdentity.getX509Certificate(); + } +} + diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/AthenzService.java b/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/AthenzService.java new file mode 100644 index 00000000000..cc5fa6a889b --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/AthenzService.java @@ -0,0 +1,46 @@ +package com.yahoo.container.jdisc.athenz; + +import com.fasterxml.jackson.databind.ObjectMapper; +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.HttpClientBuilder; +import org.apache.http.util.EntityUtils; +import org.eclipse.jetty.http.HttpStatus; + +import java.io.IOException; + +/** + * @author mortent + */ +public class AthenzService { + + /** + * Send instance register request to ZTS, get InstanceIdentity + * + * @param instanceRegisterInformation + */ + InstanceIdentity sendInstanceRegisterRequest(InstanceRegisterInformation instanceRegisterInformation, String athenzUrl) { + try(CloseableHttpClient client = HttpClientBuilder.create().build()) { + ObjectMapper objectMapper = new ObjectMapper(); + System.out.println(objectMapper.writeValueAsString(instanceRegisterInformation)); + HttpUriRequest postRequest = RequestBuilder.post() + .setUri(athenzUrl + "/instance") + .setEntity(new StringEntity(objectMapper.writeValueAsString(instanceRegisterInformation), ContentType.APPLICATION_JSON)) + .build(); + CloseableHttpResponse response = client.execute(postRequest); + if(HttpStatus.isSuccess(response.getStatusLine().getStatusCode())) { + return objectMapper.readValue(response.getEntity().getContent(), InstanceIdentity.class); + } else { + String s = EntityUtils.toString(response.getEntity()); + System.out.println("s = " + s); + throw new RuntimeException(response.toString()); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/InstanceIdentity.java b/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/InstanceIdentity.java new file mode 100644 index 00000000000..45ef4c68d8e --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/InstanceIdentity.java @@ -0,0 +1,55 @@ +package com.yahoo.container.jdisc.athenz; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Map; + +/** + * Used for deserializing response from ZTS + * + * @author mortent + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +class InstanceIdentity { + @JsonProperty("attributes") private final Map<String, String> attributes; + @JsonProperty("provider") private final String provider; + @JsonProperty("name") private final String name; + @JsonProperty("instanceId") private final String instanceId; + @JsonProperty("x509Certificate") private final String x509Certificate; + @JsonProperty("x509CertificateSigner") private final String x509CertificateSigner; + @JsonProperty("sshCertificate") private final String sshCertificate; + @JsonProperty("sshCertificateSigner") private final String sshCertificateSigner; + @JsonProperty("serviceToken") private final String serviceToken; + + public InstanceIdentity( + @JsonProperty("attributes") Map<String, String> attributes, + @JsonProperty("provider") String provider, + @JsonProperty("name") String name, + @JsonProperty("instanceId") String instanceId, + @JsonProperty("x509Certificate") String x509Certificate, + @JsonProperty("x509CertificateSigner") String x509CertificateSigner, + @JsonProperty("sshCertificate") String sshCertificate, + @JsonProperty("sshCertificateSigner") String sshCertificateSigner, + @JsonProperty("serviceToken") String serviceToken) { + this.attributes = attributes; + this.provider = provider; + this.name = name; + this.instanceId = instanceId; + this.x509Certificate = x509Certificate; + this.x509CertificateSigner = x509CertificateSigner; + this.sshCertificate = sshCertificate; + this.sshCertificateSigner = sshCertificateSigner; + this.serviceToken = serviceToken; + } + + String getX509Certificate() { + return x509Certificate; + } + + String getServiceToken() { + return serviceToken; + } +} diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/InstanceRegisterInformation.java b/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/InstanceRegisterInformation.java new file mode 100644 index 00000000000..2fa3c598c58 --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/InstanceRegisterInformation.java @@ -0,0 +1,38 @@ +package com.yahoo.container.jdisc.athenz; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Used for serializing request to ZTS + * + * @author mortent + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +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(String provider, String domain, String service, String attestationData, String csr, boolean token) { + this.provider = provider; + this.domain = domain; + this.service = service; + this.attestationData = attestationData; + this.csr = csr; + this.token = token; + } +} diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/ServiceProviderApi.java b/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/ServiceProviderApi.java new file mode 100644 index 00000000000..0b417a4d440 --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/ServiceProviderApi.java @@ -0,0 +1,47 @@ +package com.yahoo.container.jdisc.athenz; + +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; +import org.eclipse.jetty.http.HttpStatus; + +import java.io.IOException; + +/** + * @author mortent + */ +public class ServiceProviderApi { + + private final String providerEndpoint; + + public ServiceProviderApi(String providerEndpoint) { + this.providerEndpoint = providerEndpoint; + } + + + /** + * Get signed identity document from config server + * + * @return + */ + String getSignedIdentityDocument() { + + // TODO Use client side auth to establish trusted secure channel + try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { + + CloseableHttpResponse idDocResponse = httpClient.execute(RequestBuilder.get().setUri(providerEndpoint + "/identity-document").build()); + if (HttpStatus.isSuccess(idDocResponse.getStatusLine().getStatusCode())) { + return EntityUtils.toString(idDocResponse.getEntity()); + } else { + // make sure we have retried a few times (AND logged) before giving up + throw new RuntimeException("Failed to initialize Athenz instance provider"); + } + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + +} |