summaryrefslogtreecommitdiffstats
path: root/athenz-identity-provider-service
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2019-09-20 09:49:39 +0200
committerMartin Polden <mpolden@mpolden.no>2019-09-20 15:08:41 +0200
commiteb50e5195dab5662a5a9011c0b7ca69ecf8b64c4 (patch)
treed0cf255e8a5d38372f8165b0bb29c2c9540eb37e /athenz-identity-provider-service
parentfd05ebf80a64b12a2241c15967e4926ad5ca7100 (diff)
Serialization of instance types
Diffstat (limited to 'athenz-identity-provider-service')
-rw-r--r--athenz-identity-provider-service/pom.xml6
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/instance/InstanceIdentity.java64
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/instance/InstanceRegistration.java81
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/restapi/InstanceSerializer.java54
-rw-r--r--athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/ca/CertificateTester.java60
-rw-r--r--athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/ca/restapi/InstanceSerializerTest.java66
6 files changed, 331 insertions, 0 deletions
diff --git a/athenz-identity-provider-service/pom.xml b/athenz-identity-provider-service/pom.xml
index 501904a1d62..bdfebdf6169 100644
--- a/athenz-identity-provider-service/pom.xml
+++ b/athenz-identity-provider-service/pom.xml
@@ -134,6 +134,12 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>testutil</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/instance/InstanceIdentity.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/instance/InstanceIdentity.java
new file mode 100644
index 00000000000..b499debcc47
--- /dev/null
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/instance/InstanceIdentity.java
@@ -0,0 +1,64 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.ca.instance;
+
+import java.security.cert.X509Certificate;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * A signed instance identity object that includes a client certificate. This is the result of a successful
+ * {@link InstanceRegistration}.
+ *
+ * @author mpolden
+ */
+public class InstanceIdentity {
+
+ private final String provider;
+ private final String service;
+ private final String instanceId;
+ private final Optional<X509Certificate> x509Certificate;
+
+ public InstanceIdentity(String provider, String service, String instanceId, Optional<X509Certificate> x509Certificate) {
+ this.provider = Objects.requireNonNull(provider, "provider must be non-null");
+ this.service = Objects.requireNonNull(service, "service must be non-null");
+ this.instanceId = Objects.requireNonNull(instanceId, "instanceId must be non-null");
+ this.x509Certificate = Objects.requireNonNull(x509Certificate, "x509Certificate must be non-null");
+ }
+
+ /** Same as {@link InstanceRegistration#domain()} */
+ public String provider() {
+ return provider;
+ }
+
+ /** Same as {@link InstanceRegistration#service()} ()} */
+ public String service() {
+ return service;
+ }
+
+ /** A unique identifier of the instance to which the certificate is issued */
+ public String instanceId() {
+ return instanceId;
+ }
+
+ /** The issued certificate */
+ public Optional<X509Certificate> x509Certificate() {
+ return x509Certificate;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ InstanceIdentity that = (InstanceIdentity) o;
+ return provider.equals(that.provider) &&
+ service.equals(that.service) &&
+ instanceId.equals(that.instanceId) &&
+ x509Certificate.equals(that.x509Certificate);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(provider, service, instanceId, x509Certificate);
+ }
+
+}
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/instance/InstanceRegistration.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/instance/InstanceRegistration.java
new file mode 100644
index 00000000000..7a9ec74e075
--- /dev/null
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/instance/InstanceRegistration.java
@@ -0,0 +1,81 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.ca.instance;
+
+import com.yahoo.security.Pkcs10Csr;
+
+import java.util.Objects;
+
+/**
+ * Information for registering a new instance in the system. This is similar to the InstanceRegisterInformation type in
+ * ZTS.
+ *
+ * @author mpolden
+ */
+public class InstanceRegistration {
+
+ private final String provider;
+ private final String domain;
+ private final String service;
+ private final String attestationData;
+ private final Pkcs10Csr csr;
+
+ public InstanceRegistration(String provider, String domain, String service, String attestationData, Pkcs10Csr csr) {
+ this.provider = Objects.requireNonNull(provider, "provider must be non-null");
+ this.domain = Objects.requireNonNull(domain, "domain must be non-null");
+ this.service = Objects.requireNonNull(service, "service must be non-null");
+ this.attestationData = Objects.requireNonNull(attestationData, "attestationData must be non-null");
+ this.csr = Objects.requireNonNull(csr, "csr must be non-null");
+ }
+
+ /** The provider which issued the attestation data contained in this */
+ public String provider() {
+ return provider;
+ }
+
+ /** Athenz domain of the instance */
+ public String domain() {
+ return domain;
+ }
+
+ /** Athenz service of the instance */
+ public String service() {
+ return service;
+ }
+
+ /** Host document describing this instance (received from config server) */
+ public String attestationData() {
+ return attestationData;
+ }
+
+ public Pkcs10Csr csr() {
+ return csr;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ InstanceRegistration that = (InstanceRegistration) o;
+ return provider.equals(that.provider) &&
+ domain.equals(that.domain) &&
+ service.equals(that.service) &&
+ attestationData.equals(that.attestationData) &&
+ csr.equals(that.csr);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(provider, domain, service, attestationData, csr);
+ }
+
+ @Override
+ public String toString() {
+ return "InstanceRegistration{" +
+ "provider='" + provider + '\'' +
+ ", domain='" + domain + '\'' +
+ ", service='" + service + '\'' +
+ ", attestationData='" + attestationData + '\'' +
+ ", csr=" + csr +
+ '}';
+ }
+}
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/restapi/InstanceSerializer.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/restapi/InstanceSerializer.java
new file mode 100644
index 00000000000..46a09e9c6f2
--- /dev/null
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/restapi/InstanceSerializer.java
@@ -0,0 +1,54 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.ca.restapi;
+
+import com.yahoo.security.Pkcs10CsrUtils;
+import com.yahoo.security.X509CertificateUtils;
+import com.yahoo.slime.Cursor;
+import com.yahoo.slime.Slime;
+import com.yahoo.vespa.hosted.ca.instance.InstanceIdentity;
+import com.yahoo.vespa.hosted.ca.instance.InstanceRegistration;
+
+/**
+ * @author mpolden
+ */
+public class InstanceSerializer {
+
+ private static final String PROVIDER_FIELD = "provider";
+ private static final String DOMAIN_FIELD = "domain";
+ private static final String SERVICE_FIELD = "service";
+ private static final String ATTESTATION_DATA_FIELD = "attestationData";
+ private static final String CSR_FIELD = "csr";
+ private static final String NAME_FIELD = "service";
+ private static final String INSTANCE_ID_FIELD = "instanceId";
+ private static final String X509_CERTIFICATE_FIELD = "x509Certificate";
+
+ private InstanceSerializer() {}
+
+ public static InstanceRegistration registrationFromSlime(Slime slime) {
+ Cursor root = slime.get();
+ return new InstanceRegistration(requireField(PROVIDER_FIELD, root).asString(),
+ requireField(DOMAIN_FIELD, root).asString(),
+ requireField(SERVICE_FIELD, root).asString(),
+ requireField(ATTESTATION_DATA_FIELD, root).asString(),
+ Pkcs10CsrUtils.fromPem(requireField(CSR_FIELD, root).asString()));
+ }
+
+ public static Slime identityToSlime(InstanceIdentity identity) {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ root.setString(PROVIDER_FIELD, identity.provider());
+ root.setString(NAME_FIELD, identity.service());
+ root.setString(INSTANCE_ID_FIELD, identity.instanceId());
+ identity.x509Certificate()
+ .map(X509CertificateUtils::toPem)
+ .ifPresent(pem -> root.setString(X509_CERTIFICATE_FIELD, pem));
+ return slime;
+ }
+
+ private static Cursor requireField(String fieldName, Cursor root) {
+ var field = root.field(fieldName);
+ if (!field.valid()) throw new IllegalArgumentException("Missing required field '" + fieldName + "'");
+ return field;
+ }
+
+}
diff --git a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/ca/CertificateTester.java b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/ca/CertificateTester.java
new file mode 100644
index 00000000000..4946de93f6d
--- /dev/null
+++ b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/ca/CertificateTester.java
@@ -0,0 +1,60 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.ca;
+
+import com.yahoo.security.KeyAlgorithm;
+import com.yahoo.security.KeyUtils;
+import com.yahoo.security.Pkcs10Csr;
+import com.yahoo.security.Pkcs10CsrBuilder;
+import com.yahoo.security.SignatureAlgorithm;
+import com.yahoo.security.SubjectAlternativeName;
+import com.yahoo.security.X509CertificateBuilder;
+
+import javax.security.auth.x500.X500Principal;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+import java.time.Duration;
+import java.time.Instant;
+
+import static com.yahoo.security.SignatureAlgorithm.SHA256_WITH_ECDSA;
+
+/**
+ * Helper class for creating certificates, CSRs etc. for testing purposes.
+ *
+ * @author mpolden
+ */
+public class CertificateTester {
+
+ private CertificateTester() {}
+
+ public static X509Certificate createCertificate() {
+ var keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256);
+ return createCertificate("subject", keyPair);
+ }
+
+ public static X509Certificate createCertificate(String cn, KeyPair keyPair) {
+ var subject = new X500Principal("CN=" + cn);
+ return X509CertificateBuilder.fromKeypair(keyPair,
+ subject,
+ Instant.EPOCH,
+ Instant.EPOCH.plus(Duration.ofMinutes(1)),
+ SHA256_WITH_ECDSA,
+ BigInteger.ONE)
+ .build();
+ }
+
+ public static Pkcs10Csr createCsr() {
+ return createCsr(null);
+ }
+
+ public static Pkcs10Csr createCsr(String dnsName) {
+ X500Principal subject = new X500Principal("CN=subject");
+ KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256);
+ var builder = Pkcs10CsrBuilder.fromKeypair(subject, keyPair, SignatureAlgorithm.SHA512_WITH_ECDSA);
+ if (dnsName != null) {
+ builder = builder.addSubjectAlternativeName(SubjectAlternativeName.Type.DNS_NAME, dnsName);
+ }
+ return builder.build();
+ }
+
+}
diff --git a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/ca/restapi/InstanceSerializerTest.java b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/ca/restapi/InstanceSerializerTest.java
new file mode 100644
index 00000000000..51010422b6d
--- /dev/null
+++ b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/ca/restapi/InstanceSerializerTest.java
@@ -0,0 +1,66 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.ca.restapi;
+
+import com.yahoo.security.Pkcs10CsrUtils;
+import com.yahoo.security.X509CertificateUtils;
+import com.yahoo.slime.Slime;
+import com.yahoo.vespa.config.SlimeUtils;
+import com.yahoo.vespa.hosted.ca.CertificateTester;
+import com.yahoo.vespa.hosted.ca.instance.InstanceIdentity;
+import com.yahoo.vespa.hosted.ca.instance.InstanceRegistration;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author mpolden
+ */
+public class InstanceSerializerTest {
+
+ @Test
+ public void deserialize_instance_registration() {
+ var csr = CertificateTester.createCsr();
+ var csrPem = Pkcs10CsrUtils.toPem(csr);
+ var json = "{\n" +
+ " \"provider\": \"provider_prod_us-north-1\",\n" +
+ " \"domain\": \"vespa.external\",\n" +
+ " \"service\": \"tenant\",\n" +
+ " \"attestationData\": \"identity document from configserevr\",\n" +
+ " \"csr\": \"" + csrPem + "\"\n" +
+ "}";
+ var instanceRegistration = new InstanceRegistration("provider_prod_us-north-1", "vespa.external",
+ "tenant", "identity document from configserevr",
+ csr);
+ var deserialized = InstanceSerializer.registrationFromSlime(SlimeUtils.jsonToSlime(json));
+ assertEquals(instanceRegistration, deserialized);
+ }
+
+ @Test
+ public void serialize_instance_identity() {
+ var certificate = CertificateTester.createCertificate();
+ var pem = X509CertificateUtils.toPem(certificate);
+ var identity = new InstanceIdentity("provider_prod_us-north-1", "tenant", "node1.example.com",
+ Optional.of(certificate));
+ var json = "{" +
+ "\"provider\":\"provider_prod_us-north-1\"," +
+ "\"service\":\"tenant\"," +
+ "\"instanceId\":\"node1.example.com\"," +
+ "\"x509Certificate\":\"" + pem.replace("\n", "\\n") + "\"" +
+ "}";
+ assertEquals(json, asJsonString(InstanceSerializer.identityToSlime(identity)));
+ }
+
+ private static String asJsonString(Slime slime) {
+ try {
+ return new String(SlimeUtils.toJsonBytes(slime), StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+}