summaryrefslogtreecommitdiffstats
path: root/container-disc
diff options
context:
space:
mode:
authorMorten Tokle <morten.tokle@gmail.com>2017-10-23 13:18:50 +0200
committerGitHub <noreply@github.com>2017-10-23 13:18:50 +0200
commit79c1773d3353863451e0d75243e280c40b6658b0 (patch)
treee9c83538e92b63afabc418003cbccc754f21ba9d /container-disc
parent7f93a3bd50c534c92d3d7b37a4a5a47e512ff0d7 (diff)
parent0cf7be3cc88be1211c48cfb520e0c13920639c0b (diff)
Merge pull request #3830 from vespa-engine/bjorncs/athenz-identity-provider-service
Initial Athenz copper argos implementation
Diffstat (limited to 'container-disc')
-rw-r--r--container-disc/pom.xml11
-rw-r--r--container-disc/src/main/java/com/yahoo/container/jdisc/athenz/AthenzIdentityProvider.java121
-rw-r--r--container-disc/src/main/java/com/yahoo/container/jdisc/athenz/AthenzService.java46
-rw-r--r--container-disc/src/main/java/com/yahoo/container/jdisc/athenz/InstanceIdentity.java55
-rw-r--r--container-disc/src/main/java/com/yahoo/container/jdisc/athenz/InstanceRegisterInformation.java38
-rw-r--r--container-disc/src/main/java/com/yahoo/container/jdisc/athenz/ServiceProviderApi.java47
-rw-r--r--container-disc/src/test/java/com/yahoo/container/jdisc/athenz/AthenzIdentityProviderTest.java47
7 files changed, 365 insertions, 0 deletions
diff --git a/container-disc/pom.xml b/container-disc/pom.xml
index 44afe74459d..5409d9b1a40 100644
--- a/container-disc/pom.xml
+++ b/container-disc/pom.xml
@@ -130,6 +130,17 @@
<scope>provided</scope>
</dependency>
<!-- end WARNING -->
+ <!-- Athenz -->
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.athenz</groupId>
+ <artifactId>athenz-auth-core</artifactId>
+ <version>${athenz.version}</version>
+ </dependency>
</dependencies>
<properties>
<!-- These versions must be the ones used by the current jersey version (see vespa_jersey2/target/dependency).
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);
+ }
+ }
+
+}
diff --git a/container-disc/src/test/java/com/yahoo/container/jdisc/athenz/AthenzIdentityProviderTest.java b/container-disc/src/test/java/com/yahoo/container/jdisc/athenz/AthenzIdentityProviderTest.java
new file mode 100644
index 00000000000..4b351f1d2c0
--- /dev/null
+++ b/container-disc/src/test/java/com/yahoo/container/jdisc/athenz/AthenzIdentityProviderTest.java
@@ -0,0 +1,47 @@
+package com.yahoo.container.jdisc.athenz;
+
+import com.yahoo.container.core.identity.IdentityConfig;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author mortent
+ */
+public class AthenzIdentityProviderTest {
+
+ @Test
+ public void ntoken_fetched_on_init() throws IOException {
+ IdentityConfig config = new IdentityConfig(new IdentityConfig.Builder().serviceName("tenantService").domain("tenantDomain"));
+ ServiceProviderApi serviceProviderApi = mock(ServiceProviderApi.class);
+ AthenzService athenzService = mock(AthenzService.class);
+
+ when(serviceProviderApi.getSignedIdentityDocument()).thenReturn(getIdentityDocument());
+ when(athenzService.sendInstanceRegisterRequest(any(), anyString())).thenReturn(
+ new InstanceIdentity(null,null,null,null,null,null, null, null, "TOKEN"));
+
+ AthenzIdentityProvider identityProvider = new AthenzIdentityProvider(config, serviceProviderApi, athenzService);
+
+ Assert.assertEquals("TOKEN", identityProvider.getNToken());
+ }
+
+ private String getIdentityDocument() {
+ return "{\n" +
+ " \"identity-document\": \"eyJwcm92aWRlci11bmlxdWUtaWQiOnsidGVuYW50IjoidGVuYW50IiwiYXBwbGljYXRpb24iOiJhcHBsaWNhdGlvbiIsImVudmlyb25tZW50IjoiZGV2IiwicmVnaW9uIjoidXMtbm9ydGgtMSIsImluc3RhbmNlIjoiZGVmYXVsdCIsImNsdXN0ZXItaWQiOiJkZWZhdWx0IiwiY2x1c3Rlci1pbmRleCI6MH0sImNvbmZpZ3NlcnZlci1ob3N0bmFtZSI6ImxvY2FsaG9zdCIsImluc3RhbmNlLWhvc3RuYW1lIjoieC55LmNvbSIsImNyZWF0ZWQtYXQiOjE1MDg3NDgyODUuNzQyMDAwMDAwfQ==\",\n" +
+ " \"signature\": \"kkEJB/98cy1FeXxzSjtvGH2a6BFgZu/9/kzCcAqRMZjENxnw5jyO1/bjZVzw2Sz4YHPsWSx2uxb32hiQ0U8rMP0zfA9nERIalSP0jB/hMU8laezGhdpk6VKZPJRC6YKAB9Bsv2qUIfMsSxkMqf66GUvjZAGaYsnNa2yHc1jIYHOGMeJO+HNPYJjGv26xPfAOPIKQzs3RmKrc3FoweTCsIwm5oblqekdJvVWYe0obwlOSB5uwc1zpq3Ie1QBFtJRuCGMVHg1pDPxXKBHLClGIrEvzLmICy6IRdHszSO5qiwujUD7sbrbM0sB/u0cYucxbcsGRUmBvme3UAw2mW9POVQ==\",\n" +
+ " \"signing-key-version\": 0,\n" +
+ " \"provider-unique-id\": \"tenant.application.dev.us-north-1.default.default.0\",\n" +
+ " \"dns-suffix\": \"dnsSuffix\",\n" +
+ " \"provider-service\": \"service\",\n" +
+ " \"zts-endpoint\": \"localhost/zts\", \n" +
+ " \"document-version\": 1\n" +
+ "}";
+
+ }
+}