diff options
author | Martin Polden <mpolden@mpolden.no> | 2019-09-20 09:49:39 +0200 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2019-09-20 15:08:41 +0200 |
commit | eb50e5195dab5662a5a9011c0b7ca69ecf8b64c4 (patch) | |
tree | d0cf255e8a5d38372f8165b0bb29c2c9540eb37e /athenz-identity-provider-service | |
parent | fd05ebf80a64b12a2241c15967e4926ad5ca7100 (diff) |
Serialization of instance types
Diffstat (limited to 'athenz-identity-provider-service')
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); + } + } + +} |