summaryrefslogtreecommitdiffstats
path: root/athenz-identity-provider-service
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2019-10-10 13:07:01 +0200
committerMartin Polden <mpolden@mpolden.no>2019-10-10 13:08:56 +0200
commitc1670f5b33c5bf3d2b8161b4a1b73f17eb685999 (patch)
tree1364aaf535aa7bfea6dc99163dec13487bbed3ad /athenz-identity-provider-service
parentc047a2364581b470d367fc345c8031a38aaf9af8 (diff)
Parse instance ID from DNS name
Diffstat (limited to 'athenz-identity-provider-service')
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/Certificates.java23
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/restapi/CertificateAuthorityApiHandler.java6
-rw-r--r--athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/ca/CertificateTester.java15
-rw-r--r--athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/ca/CertificatesTest.java10
-rw-r--r--athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/ca/restapi/CertificateAuthorityApiTest.java26
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-----") &&