diff options
4 files changed, 77 insertions, 18 deletions
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/AuthorizationFilter.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/AuthorizationFilter.java index ccc09aad24a..3899cdaecdc 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/AuthorizationFilter.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/AuthorizationFilter.java @@ -27,6 +27,7 @@ import java.util.stream.Stream; * Authorization filter for all paths in config server. * * @author mpolden + * @author bjorncs */ public class AuthorizationFilter implements SecurityRequestFilter { diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/HostAuthenticator.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/HostAuthenticator.java index de8d117de11..977ff97c4ca 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/HostAuthenticator.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/HostAuthenticator.java @@ -39,22 +39,18 @@ class HostAuthenticator { .findFirst() .orElseThrow(() -> new AuthenticationException("Certificate subject common name is missing!")); if (isAthenzIssued(clientCertificate)) { - String hostname; List<SubjectAlternativeName> sans = X509CertificateUtils.getSubjectAlternativeNames(clientCertificate); switch (subjectCommonName) { case TENANT_DOCKER_HOST_IDENTITY: - hostname = getHostFromCalypsoCertificate(sans); - break; + return NodePrincipal.withAthenzIdentity(subjectCommonName, getHostFromCalypsoCertificate(sans), certificateChain); case TENANT_DOCKER_CONTAINER_IDENTITY: - hostname = getHostFromVespaCertificate(sans); - break; + return NodePrincipal.withAthenzIdentity(subjectCommonName, getHostFromVespaCertificate(sans), certificateChain); default: - throw new AuthenticationException("Untrusted common name in subject: " + subjectCommonName); + return NodePrincipal.withAthenzIdentity(subjectCommonName, certificateChain); } - return new NodePrincipal(hostname, certificateChain); } else { // self-signed where common name is hostname // TODO Remove this branch once self-signed certificates are gone - return new NodePrincipal(subjectCommonName, certificateChain); + return NodePrincipal.withLegacyIdentity(subjectCommonName, certificateChain); } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/NodePrincipal.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/NodePrincipal.java index dbff2b0da34..cc95945c495 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/NodePrincipal.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/NodePrincipal.java @@ -4,30 +4,66 @@ package com.yahoo.vespa.hosted.provision.restapi.v2.filter; import java.security.Principal; import java.security.cert.X509Certificate; import java.util.List; +import java.util.Optional; /** + * Represents the identity of a hosted Vespa node + * * @author bjorncs */ public class NodePrincipal implements Principal { - private final String hostIdentity; + private final String identityName; + private final String hostname; private final List<X509Certificate> clientCertificateChain; + private final Type type; - public NodePrincipal(String hostIdentity, List<X509Certificate> clientCertificateChain) { - this.hostIdentity = hostIdentity; + public static NodePrincipal withAthenzIdentity(String identityName, + List<X509Certificate> clientCertificateChain) { + return withAthenzIdentity(identityName, null, clientCertificateChain); + } + + public static NodePrincipal withAthenzIdentity(String identityName, + String hostname, + List<X509Certificate> clientCertificateChain) { + return new NodePrincipal(identityName, hostname, clientCertificateChain, Type.ATHENZ); + } + + public static NodePrincipal withLegacyIdentity(String hostname, + List<X509Certificate> clientCertificateChain) { + return new NodePrincipal(hostname, hostname, clientCertificateChain, Type.LEGACY); + } + + private NodePrincipal(String identityName, + String hostname, + List<X509Certificate> clientCertificateChain, + Type type) { + this.identityName = identityName; + this.hostname = hostname; this.clientCertificateChain = clientCertificateChain; + this.type = type; } public String getHostIdentityName() { - return hostIdentity; + return identityName; + } + + public Optional<String> getHostname() { + return Optional.ofNullable(hostname); } public List<X509Certificate> getClientCertificateChain() { return clientCertificateChain; } + public Type getType() { + return type; + } + @Override public String getName() { - return hostIdentity; + return identityName; } + public enum Type { ATHENZ, LEGACY } + } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/HostAuthenticatorTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/HostAuthenticatorTest.java index e301d8a80f9..fa9bc47c2d5 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/HostAuthenticatorTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/HostAuthenticatorTest.java @@ -34,6 +34,8 @@ import static com.yahoo.vespa.athenz.tls.SignatureAlgorithm.SHA256_WITH_RSA; import static java.util.Collections.emptySet; import static java.util.Collections.singleton; import static java.util.Collections.singletonList; +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertTrue; import static org.junit.Assert.assertEquals; /** @@ -56,7 +58,9 @@ public class HostAuthenticatorTest { .build(); HostAuthenticator authenticator = new HostAuthenticator(ZONE, nodeRepositoryDummy.nodeRepository()); NodePrincipal identity = authenticator.authenticate(singletonList(certificate)); - assertEquals(HOSTNAME, identity.getName()); + assertTrue(identity.getHostname().isPresent()); + assertEquals(HOSTNAME, identity.getHostname().get()); + assertEquals(HOSTNAME, identity.getHostIdentityName()); } @Test @@ -64,8 +68,9 @@ public class HostAuthenticatorTest { NodeRepositoryTester nodeRepositoryDummy = new NodeRepositoryTester(); nodeRepositoryDummy.addNode(OPENSTACK_ID, HOSTNAME, INSTANCE_ID, NodeType.host); nodeRepositoryDummy.setNodeState(HOSTNAME, Node.State.active); + String identityName = "vespa.vespa.tenant-host"; Pkcs10Csr csr = Pkcs10CsrBuilder - .fromKeypair(new X500Principal("CN=vespa.vespa.tenant-host"), KEYPAIR, SHA256_WITH_RSA) + .fromKeypair(new X500Principal("CN=" + identityName), KEYPAIR, SHA256_WITH_RSA) .build(); X509Certificate certificate = X509CertificateBuilder .fromCsr(csr, ATHENZ_CA_DUMMY.getSubjectX500Principal(), Instant.EPOCH, Instant.EPOCH.plusSeconds(60), KEYPAIR.getPrivate(), SHA256_WITH_RSA, 1) @@ -73,7 +78,9 @@ public class HostAuthenticatorTest { .build(); HostAuthenticator authenticator = new HostAuthenticator(ZONE, nodeRepositoryDummy.nodeRepository()); NodePrincipal identity = authenticator.authenticate(singletonList(certificate)); - assertEquals(HOSTNAME, identity.getName()); + assertTrue(identity.getHostname().isPresent()); + assertEquals(HOSTNAME, identity.getHostname().get()); + assertEquals(identityName, identity.getHostIdentityName()); } @Test @@ -87,8 +94,9 @@ public class HostAuthenticatorTest { NodeRepositoryTester nodeRepositoryDummy = new NodeRepositoryTester(); Node node = createNode(clusterId, clusterIndex, tenant, application); nodeRepositoryDummy.nodeRepository().addDockerNodes(singletonList(node)); + String identityName = "vespa.vespa.tenant"; Pkcs10Csr csr = Pkcs10CsrBuilder - .fromKeypair(new X500Principal("CN=vespa.vespa.tenant"), KEYPAIR, SHA256_WITH_RSA) + .fromKeypair(new X500Principal("CN=" + identityName), KEYPAIR, SHA256_WITH_RSA) .build(); VespaUniqueInstanceId vespaUniqueInstanceId = new VespaUniqueInstanceId(clusterIndex, clusterId, INSTANCE_ID, application, tenant, region, environment); X509Certificate certificate = X509CertificateBuilder @@ -97,7 +105,25 @@ public class HostAuthenticatorTest { .build(); HostAuthenticator authenticator = new HostAuthenticator(ZONE, nodeRepositoryDummy.nodeRepository()); NodePrincipal identity = authenticator.authenticate(singletonList(certificate)); - assertEquals(HOSTNAME, identity.getName()); + assertTrue(identity.getHostname().isPresent()); + assertEquals(HOSTNAME, identity.getHostname().get()); + assertEquals(identityName, identity.getHostIdentityName()); + } + + @Test + public void accepts_controller_certificate() { + NodeRepositoryTester nodeRepositoryDummy = new NodeRepositoryTester(); + String identityName = "vespa.vespa.hosting"; + Pkcs10Csr csr = Pkcs10CsrBuilder + .fromKeypair(new X500Principal("CN=" + identityName), KEYPAIR, SHA256_WITH_RSA) + .build(); + X509Certificate certificate = X509CertificateBuilder + .fromCsr(csr, ATHENZ_CA_DUMMY.getSubjectX500Principal(), Instant.EPOCH, Instant.EPOCH.plusSeconds(60), KEYPAIR.getPrivate(), SHA256_WITH_RSA, 1) + .build(); + HostAuthenticator authenticator = new HostAuthenticator(ZONE, nodeRepositoryDummy.nodeRepository()); + NodePrincipal identity = authenticator.authenticate(singletonList(certificate)); + assertFalse(identity.getHostname().isPresent()); + assertEquals(identityName, identity.getHostIdentityName()); } private static Node createNode(String clusterId, int clusterIndex, String tenant, String application) { |