diff options
Diffstat (limited to 'athenz-identity-provider-service')
3 files changed, 47 insertions, 19 deletions
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 4c01b0943e4..a1984557c31 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 @@ -38,6 +38,7 @@ import java.util.Objects; import java.util.Optional; import java.util.function.Function; import java.util.logging.Level; +import java.util.stream.Stream; /** * REST API for issuing and refreshing node certificates in a hosted Vespa system. @@ -113,7 +114,9 @@ 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.instanceIdFrom(instanceRefresh.csr()); - var athenzService = new AthenzService(request.getJDiscRequest().getUserPrincipal().getName()); + + var athenzService = getRequestAthenzService(request); + if (!instanceIdFromCsr.equals(instanceId)) { throw new IllegalArgumentException("Mismatch between instance ID in URL path and instance ID in CSR " + "[instanceId=" + instanceId + ",instanceIdFromCsr=" + instanceIdFromCsr + @@ -172,6 +175,16 @@ public class CertificateAuthorityApiHandler extends LoggingRequestHandler { .orElse(Collections.emptyList()); } + private AthenzService getRequestAthenzService(HttpRequest request) { + return getRequestCertificateChain(request).stream() + .findFirst() + .map(X509CertificateUtils::getSubjectCommonNames) + .map(List::stream) + .flatMap(Stream::findFirst) + .map(AthenzService::new) + .orElseThrow(() -> new RuntimeException("No certificate found")); + } + /** Returns CA private key from secret store */ private PrivateKey caPrivateKey() { return KeyUtils.fromPemEncodedPrivateKey(secretStore.getSecret(caPrivateKeySecretName)); 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 f24f731801d..9304354a011 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 @@ -56,8 +56,15 @@ public class CertificateTester { return createCsr(dnsNames, List.of()); } + public static Pkcs10Csr createCsr(String cn, List<String> dnsNames) { + return createCsr(cn, dnsNames, List.of()); + } + public static Pkcs10Csr createCsr(List<String> dnsNames, List<String> ipAddresses) { - X500Principal subject = new X500Principal("CN=subject"); + return createCsr("subject", dnsNames, ipAddresses); + } + public static Pkcs10Csr createCsr(String cn, List<String> dnsNames, List<String> ipAddresses) { + X500Principal subject = new X500Principal("CN=" + cn); KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); var builder = Pkcs10CsrBuilder.fromKeypair(subject, keyPair, SignatureAlgorithm.SHA512_WITH_ECDSA); for (var dnsName : dnsNames) { 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 88ee154dee8..c63ed3fceba 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 @@ -41,6 +41,9 @@ public class CertificateAuthorityApiTest extends ContainerTester { private static final String INVALID_INSTANCE_ID = "1.cluster1.default.otherapp.othertenant.us-north-1.prod.node"; private static final String INVALID_INSTANCE_ID_WITH_SUFFIX = INVALID_INSTANCE_ID + ".instanceid.athenz.dev-us-north-1.vespa.aws.oath.cloud"; + private static final String CONTAINER_IDENTITY = "vespa.external.tenant"; + private static final String HOST_IDENTITY = "vespa.external.tenant-host"; + @Before public void before() { setCaCertificateAndKey(); @@ -55,9 +58,9 @@ public class CertificateAuthorityApiTest extends ContainerTester { Request.Method.POST)); // POST instance registration with ZTS client - var ztsClient = new TestZtsClient(new AthenzPrincipal(new AthenzService("vespa.external.tenant-host")), null, URI.create("http://localhost:12345/ca/v1/"), SSLContext.getDefault()); + var ztsClient = new TestZtsClient(new AthenzPrincipal(new AthenzService(HOST_IDENTITY)), null, URI.create("http://localhost:12345/ca/v1/"), SSLContext.getDefault()); var instanceIdentity = ztsClient.registerInstance(new AthenzService("vespa.external", "provider_prod_us-north-1"), - new AthenzService("vespa.external", "tenant"), + new AthenzService(CONTAINER_IDENTITY), getAttestationData(), csr); assertEquals("CN=Vespa CA", instanceIdentity.certificate().getIssuerX500Principal().getName()); @@ -65,15 +68,15 @@ public class CertificateAuthorityApiTest extends ContainerTester { private X509Certificate registerInstance() throws Exception { // POST instance registration - var csr = CertificateTester.createCsr(List.of("node1.example.com", INSTANCE_ID_WITH_SUFFIX)); + var csr = CertificateTester.createCsr(CONTAINER_IDENTITY, List.of("node1.example.com", INSTANCE_ID_WITH_SUFFIX)); assertIdentityResponse(new Request("http://localhost:12345/ca/v1/instance/", instanceRegistrationJson(csr), Request.Method.POST)); // POST instance registration with ZTS client - var ztsClient = new TestZtsClient(new AthenzPrincipal(new AthenzService("vespa.external.tenant-host")), null, URI.create("http://localhost:12345/ca/v1/"), SSLContext.getDefault()); + var ztsClient = new TestZtsClient(new AthenzPrincipal(new AthenzService(HOST_IDENTITY)), null, URI.create("http://localhost:12345/ca/v1/"), SSLContext.getDefault()); var instanceIdentity = ztsClient.registerInstance(new AthenzService("vespa.external", "provider_prod_us-north-1"), - new AthenzService("vespa.external", "tenant"), + new AthenzService(CONTAINER_IDENTITY), getAttestationData(), csr); return instanceIdentity.certificate(); @@ -85,8 +88,8 @@ public class CertificateAuthorityApiTest extends ContainerTester { var certificate = registerInstance(); // POST instance refresh - var csr = CertificateTester.createCsr(List.of("node1.example.com", INSTANCE_ID_WITH_SUFFIX)); - var principal = new AthenzPrincipal(new AthenzService("vespa.external.tenant")); + var principal = new AthenzPrincipal(new AthenzService(CONTAINER_IDENTITY)); + var csr = CertificateTester.createCsr(principal.getIdentity().getFullName(), List.of("node1.example.com", INSTANCE_ID_WITH_SUFFIX)); var request = 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, @@ -97,7 +100,7 @@ public class CertificateAuthorityApiTest extends ContainerTester { // POST instance refresh with ZTS client var ztsClient = new TestZtsClient(principal, certificate, 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"), + new AthenzService(CONTAINER_IDENTITY), INSTANCE_ID, csr); assertEquals("CN=Vespa CA", instanceIdentity.certificate().getIssuerX500Principal().getName()); @@ -125,21 +128,26 @@ public class CertificateAuthorityApiTest extends ContainerTester { Request.Method.POST)); // POST instance refresh where instanceId does not match CSR dnsName - var principal = new AthenzPrincipal(new AthenzService("vespa.external.tenant")); - 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: Mismatch between instance ID in URL path and instance ID in CSR [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, - principal)); + var principal = new AthenzPrincipal(new AthenzService(CONTAINER_IDENTITY)); + var cert = CertificateTester.createCertificate(CONTAINER_IDENTITY, KeyUtils.generateKeypair(KeyAlgorithm.EC)); + csr = CertificateTester.createCsr(principal.getIdentity().getFullName(), List.of("node1.example.com", INSTANCE_ID_WITH_SUFFIX)); + request = new Request("http://localhost:12345/ca/v1/instance/vespa.external.provider_prod_us-north-1/vespa.external/tenant/foobar", + instanceRefreshJson(csr), + Request.Method.POST, + principal); + request.getAttributes().put(ServletRequest.JDISC_REQUEST_X509CERT, new X509Certificate[]{cert}); + 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: Mismatch between instance ID in URL path and instance ID in CSR [instanceId=foobar,instanceIdFromCsr=1.cluster1.default.app1.tenant1.us-north-1.prod.node]\"}", + request); // POST instance refresh using zts client where client cert does not contain instanceid var certificate = registerInstance(); var ztsClient = new TestZtsClient(principal, certificate, URI.create("http://localhost:12345/ca/v1/"), SSLContext.getDefault()); try { - var invalidCsr = CertificateTester.createCsr(List.of("node1.example.com", INVALID_INSTANCE_ID_WITH_SUFFIX)); + var invalidCsr = CertificateTester.createCsr(principal.getIdentity().getFullName(), List.of("node1.example.com", INVALID_INSTANCE_ID_WITH_SUFFIX)); var instanceIdentity = ztsClient.refreshInstance(new AthenzService("vespa.external", "provider_prod_us-north-1"), - new AthenzService("vespa.external", "tenant"), + new AthenzService(CONTAINER_IDENTITY), INSTANCE_ID, invalidCsr); fail("Refresh instance should have failed"); |