diff options
author | Martin Polden <mpolden@mpolden.no> | 2019-10-10 13:07:01 +0200 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2019-10-10 13:08:56 +0200 |
commit | c1670f5b33c5bf3d2b8161b4a1b73f17eb685999 (patch) | |
tree | 1364aaf535aa7bfea6dc99163dec13487bbed3ad /athenz-identity-provider-service/src | |
parent | c047a2364581b470d367fc345c8031a38aaf9af8 (diff) |
Parse instance ID from DNS name
Diffstat (limited to 'athenz-identity-provider-service/src')
5 files changed, 59 insertions, 21 deletions
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/Certificates.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/Certificates.java index a4cf54063ec..308127e29c7 100644 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/Certificates.java +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/Certificates.java @@ -4,12 +4,14 @@ package com.yahoo.vespa.hosted.ca; import com.yahoo.security.Pkcs10Csr; import com.yahoo.security.SubjectAlternativeName; import com.yahoo.security.X509CertificateBuilder; +import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.time.Clock; import java.time.Duration; import java.util.Objects; +import java.util.Optional; import static com.yahoo.security.SignatureAlgorithm.SHA256_WITH_ECDSA; import static com.yahoo.security.SubjectAlternativeName.Type.DNS_NAME; @@ -22,6 +24,7 @@ import static com.yahoo.security.SubjectAlternativeName.Type.DNS_NAME; public class Certificates { private static final Duration CERTIFICATE_TTL = Duration.ofDays(30); + private static final String INSTANCE_ID_DELIMITER = ".instanceid.athenz."; private final Clock clock; @@ -48,13 +51,27 @@ public class Certificates { return builder.build(); } - /** Returns the DNS name field from Subject Alternative Names in given csr */ - public static String extractDnsName(Pkcs10Csr csr) { + /** Returns instance ID parsed from the Subject Alternative Names in given csr */ + public static String instanceIdFrom(Pkcs10Csr csr) { return csr.getSubjectAlternativeNames().stream() .filter(san -> san.getType() == DNS_NAME) .map(SubjectAlternativeName::getValue) + .map(Certificates::parseInstanceId) + .flatMap(Optional::stream) + .map(VespaUniqueInstanceId::asDottedString) .findFirst() - .orElseThrow(() -> new IllegalArgumentException("DNS name not found in CSR")); + .orElseThrow(() -> new IllegalArgumentException("No instance ID found in CSR")); + } + + private static Optional<VespaUniqueInstanceId> parseInstanceId(String dnsName) { + var delimiterStart = dnsName.indexOf(INSTANCE_ID_DELIMITER); + if (delimiterStart == -1) return Optional.empty(); + dnsName = dnsName.substring(0, delimiterStart); + try { + return Optional.of(VespaUniqueInstanceId.fromDottedString(dnsName)); + } catch (IllegalArgumentException e) { + return Optional.empty(); + } } } diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/restapi/CertificateAuthorityApiHandler.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/restapi/CertificateAuthorityApiHandler.java index ca1697c7bb1..89a3170744d 100644 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/restapi/CertificateAuthorityApiHandler.java +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/restapi/CertificateAuthorityApiHandler.java @@ -82,7 +82,7 @@ public class CertificateAuthorityApiHandler extends LoggingRequestHandler { private HttpResponse registerInstance(HttpRequest request) { var instanceRegistration = deserializeRequest(request, InstanceSerializer::registrationFromSlime); var certificate = certificates.create(instanceRegistration.csr(), caCertificate(), caPrivateKey()); - var instanceId = Certificates.extractDnsName(instanceRegistration.csr()); + var instanceId = Certificates.instanceIdFrom(instanceRegistration.csr()); var identity = new InstanceIdentity(instanceRegistration.provider(), instanceRegistration.service(), instanceId, Optional.of(certificate)); return new SlimeJsonResponse(InstanceSerializer.identityToSlime(identity)); @@ -90,10 +90,10 @@ public class CertificateAuthorityApiHandler extends LoggingRequestHandler { private HttpResponse refreshInstance(HttpRequest request, String provider, String service, String instanceId) { var instanceRefresh = deserializeRequest(request, InstanceSerializer::refreshFromSlime); - var instanceIdFromCsr = Certificates.extractDnsName(instanceRefresh.csr()); + var instanceIdFromCsr = Certificates.instanceIdFrom(instanceRefresh.csr()); if (!instanceIdFromCsr.equals(instanceId)) { throw new IllegalArgumentException("Mismatched instance ID and SAN DNS name [instanceId=" + instanceId + - ",dnsName=" + instanceIdFromCsr + "]"); + ",instanceIdFromCsr=" + instanceIdFromCsr + "]"); } var certificate = certificates.create(instanceRefresh.csr(), caCertificate(), caPrivateKey()); var identity = new InstanceIdentity(provider, service, instanceIdFromCsr, Optional.of(certificate)); 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 index 130a4ec5e66..f24f731801d 100644 --- 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 @@ -15,6 +15,7 @@ import java.security.KeyPair; import java.security.cert.X509Certificate; import java.time.Duration; import java.time.Instant; +import java.util.List; import static com.yahoo.security.SignatureAlgorithm.SHA256_WITH_ECDSA; @@ -44,14 +45,22 @@ public class CertificateTester { } public static Pkcs10Csr createCsr() { - return createCsr(null); + return createCsr(List.of(), List.of()); } - public static Pkcs10Csr createCsr(String dnsName, String... ipAddresses) { + public static Pkcs10Csr createCsr(String dnsName) { + return createCsr(List.of(dnsName), List.of()); + } + + public static Pkcs10Csr createCsr(List<String> dnsNames) { + return createCsr(dnsNames, List.of()); + } + + public static Pkcs10Csr createCsr(List<String> dnsNames, List<String> ipAddresses) { 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) { + for (var dnsName : dnsNames) { builder = builder.addSubjectAlternativeName(SubjectAlternativeName.Type.DNS_NAME, dnsName); } for (var ipAddress : ipAddresses) { diff --git a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/ca/CertificatesTest.java b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/ca/CertificatesTest.java index fa86979656d..9bb733787f1 100644 --- a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/ca/CertificatesTest.java +++ b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/ca/CertificatesTest.java @@ -41,7 +41,7 @@ public class CertificatesTest { var certificates = new Certificates(new ManualClock()); var dnsName = "host.example.com"; var ip = "192.0.2.42"; - var csr = CertificateTester.createCsr(dnsName, ip); + var csr = CertificateTester.createCsr(List.of(dnsName), List.of(ip)); var certificate = certificates.create(csr, caCertificate, keyPair.getPrivate()); assertNotNull(certificate.getSubjectAlternativeNames()); @@ -54,4 +54,12 @@ public class CertificatesTest { subjectAlternativeNames.get(1)); } + @Test + public void parse_instance_id() { + var instanceId = "1.cluster1.default.app1.tenant1.us-north-1.prod.node"; + var instanceIdWithSuffix = instanceId + ".instanceid.athenz.dev-us-north-1.vespa.aws.oath.cloud"; + var csr = CertificateTester.createCsr(List.of("foo", "bar", instanceIdWithSuffix)); + assertEquals(instanceId, Certificates.instanceIdFrom(csr)); + } + } diff --git a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/ca/restapi/CertificateAuthorityApiTest.java b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/ca/restapi/CertificateAuthorityApiTest.java index 8e4605499f7..21b1e392702 100644 --- a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/ca/restapi/CertificateAuthorityApiTest.java +++ b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/ca/restapi/CertificateAuthorityApiTest.java @@ -17,6 +17,7 @@ import org.junit.Test; import javax.net.ssl.SSLContext; import java.net.URI; import java.nio.charset.StandardCharsets; +import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -26,6 +27,9 @@ import static org.junit.Assert.assertTrue; */ public class CertificateAuthorityApiTest extends ContainerTester { + private static final String INSTANCE_ID = "1.cluster1.default.app1.tenant1.us-north-1.prod.node"; + private static final String INSTANCE_ID_WITH_SUFFIX = INSTANCE_ID + ".instanceid.athenz.dev-us-north-1.vespa.aws.oath.cloud"; + @Before public void before() { setCaCertificateAndKey(); @@ -34,7 +38,7 @@ public class CertificateAuthorityApiTest extends ContainerTester { @Test public void register_instance() throws Exception { // POST instance registration - var csr = CertificateTester.createCsr("node1.example.com"); + var csr = CertificateTester.createCsr(List.of("node1.example.com", INSTANCE_ID_WITH_SUFFIX)); assertIdentityResponse(new Request("http://localhost:12345/ca/v1/instance/", instanceRegistrationJson(csr), Request.Method.POST)); @@ -51,8 +55,8 @@ public class CertificateAuthorityApiTest extends ContainerTester { @Test public void refresh_instance() throws Exception { // POST instance refresh - var csr = CertificateTester.createCsr("node1.example.com"); - assertIdentityResponse(new Request("http://localhost:12345/ca/v1/instance/vespa.external.provider_prod_us-north-1/vespa.external/tenant/node1.example.com", + var csr = CertificateTester.createCsr(List.of("node1.example.com", INSTANCE_ID_WITH_SUFFIX)); + assertIdentityResponse(new Request("http://localhost:12345/ca/v1/instance/vespa.external.provider_prod_us-north-1/vespa.external/tenant/" + INSTANCE_ID, instanceRefreshJson(csr), Request.Method.POST)); @@ -60,7 +64,7 @@ public class CertificateAuthorityApiTest extends ContainerTester { var ztsClient = new DefaultZtsClient(URI.create("http://localhost:12345/ca/v1/"), SSLContext.getDefault()); var instanceIdentity = ztsClient.refreshInstance(new AthenzService("vespa.external", "provider_prod_us-north-1"), new AthenzService("vespa.external", "tenant"), - "node1.example.com", + INSTANCE_ID, csr); assertEquals("CN=Vespa CA", instanceIdentity.certificate().getIssuerX500Principal().getName()); } @@ -78,18 +82,18 @@ public class CertificateAuthorityApiTest extends ContainerTester { var request = new Request("http://localhost:12345/ca/v1/instance/", instanceRegistrationJson(csr), Request.Method.POST); - assertResponse(400, "{\"error-code\":\"BAD_REQUEST\",\"message\":\"POST http://localhost:12345/ca/v1/instance/ failed: DNS name not found in CSR\"}", request); + assertResponse(400, "{\"error-code\":\"BAD_REQUEST\",\"message\":\"POST http://localhost:12345/ca/v1/instance/ failed: No instance ID found in CSR\"}", request); // POST instance refresh with missing field - assertResponse(400, "{\"error-code\":\"BAD_REQUEST\",\"message\":\"POST http://localhost:12345/ca/v1/instance/vespa.external.provider_prod_us-north-1/vespa.external/tenant/node1.example.com failed: Missing required field 'csr'\"}", - new Request("http://localhost:12345/ca/v1/instance/vespa.external.provider_prod_us-north-1/vespa.external/tenant/node1.example.com", + assertResponse(400, "{\"error-code\":\"BAD_REQUEST\",\"message\":\"POST http://localhost:12345/ca/v1/instance/vespa.external.provider_prod_us-north-1/vespa.external/tenant/1.cluster1.default.app1.tenant1.us-north-1.prod.node failed: Missing required field 'csr'\"}", + new Request("http://localhost:12345/ca/v1/instance/vespa.external.provider_prod_us-north-1/vespa.external/tenant/" + INSTANCE_ID, new byte[0], Request.Method.POST)); // POST instance refresh where instanceId does not match CSR dnsName - csr = CertificateTester.createCsr("node1.example.com"); - assertResponse(400, "{\"error-code\":\"BAD_REQUEST\",\"message\":\"POST http://localhost:12345/ca/v1/instance/vespa.external.provider_prod_us-north-1/vespa.external/tenant/node2.example.com failed: Mismatched instance ID and SAN DNS name [instanceId=node2.example.com,dnsName=node1.example.com]\"}", - new Request("http://localhost:12345/ca/v1/instance/vespa.external.provider_prod_us-north-1/vespa.external/tenant/node2.example.com", + csr = CertificateTester.createCsr(List.of("node1.example.com", INSTANCE_ID_WITH_SUFFIX)); + assertResponse(400, "{\"error-code\":\"BAD_REQUEST\",\"message\":\"POST http://localhost:12345/ca/v1/instance/vespa.external.provider_prod_us-north-1/vespa.external/tenant/foobar failed: Mismatched instance ID and SAN DNS name [instanceId=foobar,instanceIdFromCsr=1.cluster1.default.app1.tenant1.us-north-1.prod.node]\"}", + new Request("http://localhost:12345/ca/v1/instance/vespa.external.provider_prod_us-north-1/vespa.external/tenant/foobar", instanceRefreshJson(csr), Request.Method.POST)); } @@ -108,7 +112,7 @@ public class CertificateAuthorityApiTest extends ContainerTester { var root = slime.get(); assertEquals("vespa.external.provider_prod_us-north-1", root.field("provider").asString()); assertEquals("tenant", root.field("service").asString()); - assertEquals("node1.example.com", root.field("instanceId").asString()); + assertEquals(INSTANCE_ID, root.field("instanceId").asString()); var pemEncodedCertificate = root.field("x509Certificate").asString(); assertTrue("Response contains PEM certificate", pemEncodedCertificate.startsWith("-----BEGIN CERTIFICATE-----") && |