aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorncs@verizonmedia.com>2019-05-09 16:25:32 +0200
committerBjørn Christian Seime <bjorncs@verizonmedia.com>2019-05-13 16:03:05 +0200
commit375b9af98972da226b273f56ff47869b88b7d43a (patch)
tree58757209f0eebeeea3e55452382682a6e00bf67b
parent204208538f871c93b18fe6d88216412756f63b21 (diff)
Remove security filters from node-repository
-rw-r--r--node-repository/pom.xml12
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/AuthorizationFilter.java72
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/Authorizer.java211
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/FilterUtils.java35
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/NodeIdentifier.java137
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/NodeIdentifierFilter.java51
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/NodePrincipal.java105
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/package-info.java8
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/AuthorizationFilterTest.java38
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/AuthorizerTest.java196
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/FilterTester.java155
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/NodeIdentifierTest.java272
12 files changed, 0 insertions, 1292 deletions
diff --git a/node-repository/pom.xml b/node-repository/pom.xml
index 6349993a3dc..86a56c14c52 100644
--- a/node-repository/pom.xml
+++ b/node-repository/pom.xml
@@ -79,18 +79,6 @@
</dependency>
<dependency>
<groupId>com.yahoo.vespa</groupId>
- <artifactId>vespa-athenz</artifactId>
- <version>${project.version}</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>com.yahoo.vespa</groupId>
- <artifactId>jdisc-security-filters</artifactId>
- <version>${project.version}</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>com.yahoo.vespa</groupId>
<artifactId>flags</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
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
deleted file mode 100644
index 9934c343092..00000000000
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/AuthorizationFilter.java
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.provision.restapi.v2.filter;
-
-import com.google.inject.Inject;
-import com.yahoo.config.provisioning.ConfigServerSecurityConfig;
-import com.yahoo.jdisc.handler.ResponseHandler;
-import com.yahoo.jdisc.http.filter.DiscFilterRequest;
-import com.yahoo.jdisc.http.filter.SecurityRequestFilter;
-import com.yahoo.vespa.hosted.provision.NodeRepository;
-import com.yahoo.vespa.hosted.provision.restapi.v2.ErrorResponse;
-import com.yahoo.yolean.chain.After;
-
-import java.net.URI;
-import java.util.Optional;
-import java.util.function.BiConsumer;
-import java.util.function.BiPredicate;
-import java.util.logging.Logger;
-
-/**
- * Authorization filter for all paths in config server. It assumes that {@link NodeIdentifierFilter} is part of filter chain.
- *
- * @author mpolden
- * @author bjorncs
- */
-@After("NodeIdentifierFilter")
-public class AuthorizationFilter implements SecurityRequestFilter {
-
- private static final Logger log = Logger.getLogger(AuthorizationFilter.class.getName());
-
- private final BiPredicate<NodePrincipal, URI> authorizer;
- private final BiConsumer<ErrorResponse, ResponseHandler> rejectAction;
-
- @Inject
- public AuthorizationFilter(NodeRepository nodeRepository, ConfigServerSecurityConfig securityConfig) {
- this.authorizer = new Authorizer(nodeRepository, securityConfig);
- this.rejectAction = AuthorizationFilter::logAndReject;
- }
-
- @Override
- public void filter(DiscFilterRequest request, ResponseHandler handler) {
- validateAccess(request)
- .ifPresent(errorResponse -> rejectAction.accept(errorResponse, handler));
- }
-
- private Optional<ErrorResponse> validateAccess(DiscFilterRequest request) {
- try {
- NodePrincipal hostIdentity = (NodePrincipal) request.getUserPrincipal();
- if (hostIdentity == null)
- return Optional.of(ErrorResponse.internalServerError(createErrorMessage(request, "Principal is missing. NodeIdentifierFilter has not been applied.")));
- if (!authorizer.test(hostIdentity, request.getUri()))
- return Optional.of(ErrorResponse.forbidden(createErrorMessage(request, "Invalid credentials: " + hostIdentity.toString())));
- request.setUserPrincipal(hostIdentity);
- return Optional.empty();
- } catch (NodeIdentifier.NodeIdentifierException e) {
- return Optional.of(ErrorResponse.forbidden(createErrorMessage(request, "Invalid credentials: " + e.getMessage())));
- }
- }
-
- private static String createErrorMessage(DiscFilterRequest request, String message) {
- return String.format("%s %s denied for %s: %s",
- request.getMethod(),
- request.getUri().getPath(),
- request.getRemoteAddr(),
- message);
- }
-
- private static void logAndReject(ErrorResponse response, ResponseHandler handler) {
- log.warning(response.message());
- FilterUtils.write(response, handler);
- }
-
-}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/Authorizer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/Authorizer.java
deleted file mode 100644
index 062a9c32afb..00000000000
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/Authorizer.java
+++ /dev/null
@@ -1,211 +0,0 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.provision.restapi.v2.filter;
-
-import com.yahoo.config.provision.NodeType;
-import com.yahoo.config.provisioning.ConfigServerSecurityConfig;
-import com.yahoo.vespa.athenz.api.AthenzIdentity;
-import com.yahoo.vespa.athenz.utils.AthenzIdentities;
-import com.yahoo.vespa.hosted.provision.Node;
-import com.yahoo.vespa.hosted.provision.NodeRepository;
-import org.apache.http.NameValuePair;
-import org.apache.http.client.utils.URLEncodedUtils;
-
-import java.net.URI;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import java.util.function.BiPredicate;
-import java.util.stream.Collectors;
-
-/**
- * Authorizer for config server REST APIs. This contains the rules for all API paths where the authorization process
- * may require information from the node-repository to make a decision
- *
- * @author mpolden
- * @author bjorncs
- */
-public class Authorizer implements BiPredicate<NodePrincipal, URI> {
- private final NodeRepository nodeRepository;
- private final String athenzProviderHostname;
- private final AthenzIdentity controllerHostIdentity;
- private final Set<AthenzIdentity> trustedIdentities;
- private final Set<AthenzIdentity> hostAdminIdentities;
-
- Authorizer(NodeRepository nodeRepository, ConfigServerSecurityConfig securityConfig) {
- AthenzIdentity configServerHostIdentity = AthenzIdentities.from(securityConfig.configServerHostIdentity());
-
- this.nodeRepository = nodeRepository;
- this.athenzProviderHostname = securityConfig.athenzProviderHostname();
- this.controllerHostIdentity = AthenzIdentities.from(securityConfig.controllerHostIdentity());
- this.trustedIdentities = Set.of(controllerHostIdentity, configServerHostIdentity);
- this.hostAdminIdentities = Set.of(
- controllerHostIdentity,
- configServerHostIdentity,
- AthenzIdentities.from(securityConfig.tenantHostIdentity()),
- AthenzIdentities.from(securityConfig.proxyHostIdentity()));
- }
-
- /** Returns whether principal is authorized to access given URI */
- @Override
- public boolean test(NodePrincipal principal, URI uri) {
- if (principal.getAthenzIdentityName().isPresent()) {
- // All host admins can retrieve flags data
- if (uri.getPath().equals("/flags/v1/data") || uri.getPath().equals("/flags/v1/data/")) {
- return hostAdminIdentities.contains(principal.getAthenzIdentityName().get());
- }
-
- // Only controller can access everything else in flags
- if (uri.getPath().startsWith("/flags/v1/")) {
- return principal.getAthenzIdentityName().get().equals(controllerHostIdentity);
- }
-
- // Trusted services can access everything
- if (trustedIdentities.contains(principal.getAthenzIdentityName().get())) {
- return true;
- }
- }
-
- if (principal.getHostname().isPresent()) {
- String hostname = principal.getHostname().get();
- if (isAthenzProviderApi(uri)) {
- return athenzProviderHostname.equals(hostname);
- }
-
- // Individual nodes can only access their own resources
- if (canAccessAll(hostnamesFrom(uri), principal, this::isSelfOrParent)) {
- return true;
- }
-
- // Nodes can access this resource if its type matches any of the valid node types
- if (canAccessAny(nodeTypesFor(uri), principal, this::isNodeType)) {
- return true;
- }
- }
- return false;
- }
-
- private static boolean isAthenzProviderApi(URI uri) {
- return "/athenz/v1/provider/instance".equals(uri.getPath()) ||
- "/athenz/v1/provider/refresh".equals(uri.getPath());
- }
-
- /** Returns whether principal is the node itself or the parent of the node */
- private boolean isSelfOrParent(String hostname, NodePrincipal principal) {
- // Node can always access itself
- if (principal.getHostname().get().equals(hostname)) {
- return true;
- }
-
- // Parent node can access its children
- return getNode(hostname).flatMap(Node::parentHostname)
- .map(parentHostname -> principal.getHostname().get().equals(parentHostname))
- .orElse(false);
- }
-
- /** Returns whether principal is a node of the given node type */
- private boolean isNodeType(NodeType type, NodePrincipal principal) {
- return getNode(principal.getHostname().get()).map(node -> node.type() == type)
- .orElse(false);
- }
-
- /** Returns whether principal can access all given resources */
- private <T> boolean canAccessAll(List<T> resources, NodePrincipal principal, BiPredicate<T, NodePrincipal> predicate) {
- return !resources.isEmpty() && resources.stream().allMatch(resource -> predicate.test(resource, principal));
- }
-
- /** Returns whether principal can access any of the given resources */
- private <T> boolean canAccessAny(List<T> resources, NodePrincipal principal, BiPredicate<T, NodePrincipal> predicate) {
- return !resources.isEmpty() && resources.stream().anyMatch(resource -> predicate.test(resource, principal));
- }
-
- private Optional<Node> getNode(String hostname) {
- // Ignore potential path traversal. Node repository happily passes arguments unsanitized all the way down to
- // curator...
- if (hostname.chars().allMatch(c -> c == '.')) {
- return Optional.empty();
- }
- return nodeRepository.getNode(hostname);
- }
-
- /** Returns hostnames contained in query parameters of given URI */
- private static List<String> hostnamesFromQuery(URI uri) {
- return URLEncodedUtils.parse(uri, StandardCharsets.UTF_8.name())
- .stream()
- .filter(pair -> "hostname".equals(pair.getName()) ||
- "parentHost".equals(pair.getName()))
- .map(NameValuePair::getValue)
- .filter(hostname -> !hostname.isEmpty())
- .collect(Collectors.toList());
- }
-
- /** Returns hostnames from a URI if any, e.g. /nodes/v2/node/node1.fqdn */
- private static List<String> hostnamesFrom(URI uri) {
- if (isChildOf("/nodes/v2/acl/", uri.getPath()) ||
- isChildOf("/nodes/v2/node/", uri.getPath()) ||
- isChildOf("/nodes/v2/state/", uri.getPath())) {
- return Collections.singletonList(lastChildOf(uri.getPath()));
- }
- if (isChildOf("/orchestrator/v1/hosts/", uri.getPath())) {
- return firstChildOf("/orchestrator/v1/hosts/", uri.getPath())
- .map(Collections::singletonList)
- .orElseGet(Collections::emptyList);
- }
- if (isChildOf("/orchestrator/v1/suspensions/hosts/", uri.getPath())) {
- List<String> hostnames = new ArrayList<>();
- hostnames.add(lastChildOf(uri.getPath()));
- hostnames.addAll(hostnamesFromQuery(uri));
- return hostnames;
- }
- if (isChildOf("/nodes/v2/command/", uri.getPath()) ||
- "/nodes/v2/node/".equals(uri.getPath())) {
- return hostnamesFromQuery(uri);
- }
- if (isChildOf("/athenz/v1/provider/identity-document", uri.getPath())) {
- return Collections.singletonList(lastChildOf(uri.getPath()));
- }
- return Collections.emptyList();
- }
-
- /** Returns node types which can access given URI */
- private static List<NodeType> nodeTypesFor(URI uri) {
- if (isChildOf("/routing/v1/", uri.getPath())) {
- return Arrays.asList(NodeType.proxy, NodeType.proxyhost);
- }
- return Collections.emptyList();
- }
-
- /** Returns whether child is a sub-path of parent */
- private static boolean isChildOf(String parent, String child) {
- return child.startsWith(parent) && child.length() > parent.length();
- }
-
- /** Returns the first component of path relative to root */
- private static Optional<String> firstChildOf(String root, String path) {
- if (!isChildOf(root, path)) {
- return Optional.empty();
- }
- path = path.substring(root.length());
- int firstSeparator = path.indexOf('/');
- if (firstSeparator == -1) {
- return Optional.of(path);
- }
- return Optional.of(path.substring(0, firstSeparator));
- }
-
- /** Returns the last component of the given path */
- private static String lastChildOf(String path) {
- if (path.endsWith("/")) {
- path = path.substring(0, path.length() - 1);
- }
- int lastSeparator = path.lastIndexOf("/");
- if (lastSeparator == -1) {
- return path;
- }
- return path.substring(lastSeparator + 1);
- }
-
-}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/FilterUtils.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/FilterUtils.java
deleted file mode 100644
index 82d82f4694e..00000000000
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/FilterUtils.java
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.provision.restapi.v2.filter;
-
-import com.yahoo.container.jdisc.HttpResponse;
-import com.yahoo.jdisc.handler.FastContentWriter;
-import com.yahoo.jdisc.handler.ResponseDispatch;
-import com.yahoo.jdisc.handler.ResponseHandler;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.UncheckedIOException;
-
-/**
- * @author mpolden
- */
-public class FilterUtils {
-
- private FilterUtils() {}
-
- /** Write HTTP response using given handler */
- public static void write(HttpResponse response, ResponseHandler handler) {
- response.headers().put("Content-Type", response.getContentType());
- try (FastContentWriter writer = ResponseDispatch.newInstance(response.getJdiscResponse())
- .connectFastWriter(handler)) {
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- try {
- response.render(out);
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- writer.write(out.toByteArray());
- }
- }
-
-}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/NodeIdentifier.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/NodeIdentifier.java
deleted file mode 100644
index ecc3f84f86c..00000000000
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/NodeIdentifier.java
+++ /dev/null
@@ -1,137 +0,0 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.provision.restapi.v2.filter;
-
-import com.google.common.base.Supplier;
-import com.google.common.base.Suppliers;
-import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.Zone;
-import com.yahoo.config.provisioning.ConfigServerSecurityConfig;
-import com.yahoo.security.SubjectAlternativeName;
-import com.yahoo.security.X509CertificateUtils;
-import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId;
-import com.yahoo.vespa.hosted.provision.Node;
-import com.yahoo.vespa.hosted.provision.NodeRepository;
-
-import java.security.cert.X509Certificate;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
-
-import static com.yahoo.security.SubjectAlternativeName.Type.DNS_NAME;
-
-/**
- * Resolve node from various types of x509 identity certificates.
- *
- * @author bjorncs
- */
-class NodeIdentifier {
-
- private static final String INSTANCE_ID_DELIMITER = ".instanceid.athenz.";
-
- private final Zone zone;
- private final NodeRepository nodeRepository;
- private final String athenzProviderHostname;
- private final Set<String> configServerLikeIdentities;
- private final Set<String> tenantAndProxyHostIndentities;
- private final String tenantIdentity;
-
- private final Supplier<List<Node>> nodeCache;
-
- NodeIdentifier(Zone zone, NodeRepository nodeRepository, ConfigServerSecurityConfig securityConfig) {
- this.zone = zone;
- this.nodeRepository = nodeRepository;
- this.athenzProviderHostname = securityConfig.athenzProviderHostname();
- this.configServerLikeIdentities = Set.of(securityConfig.controllerHostIdentity(), securityConfig.configServerHostIdentity());
- this.tenantAndProxyHostIndentities = Set.of(securityConfig.tenantHostIdentity(), securityConfig.proxyHostIdentity());
- this.tenantIdentity = securityConfig.tenantIdentity();
- nodeCache = Suppliers.memoizeWithExpiration(nodeRepository::getNodes, 1, TimeUnit.MINUTES);
- }
-
- NodePrincipal resolveNode(List<X509Certificate> certificateChain) throws NodeIdentifierException {
- X509Certificate clientCertificate = certificateChain.get(0);
- String subjectCommonName = X509CertificateUtils.getSubjectCommonNames(clientCertificate).stream()
- .findFirst()
- .orElseThrow(() -> new NodeIdentifierException("Certificate subject common name is missing!"));
- if (isAthenzIssued(clientCertificate)) {
- List<SubjectAlternativeName> sans = X509CertificateUtils.getSubjectAlternativeNames(clientCertificate);
- if (configServerLikeIdentities.contains(subjectCommonName)) {
- return NodePrincipal.withAthenzIdentity(subjectCommonName, certificateChain);
- } else if (tenantAndProxyHostIndentities.contains(subjectCommonName)) {
- return NodePrincipal.withAthenzIdentity(subjectCommonName, getHostFromCalypsoCertificate(sans), certificateChain);
- } else if (subjectCommonName.equals(tenantIdentity)) {
- return NodePrincipal.withAthenzIdentity(subjectCommonName, getHostFromVespaCertificate(sans), certificateChain);
- }
-
- throw new NodeIdentifierException(String.format(
- "Subject common name (%s) does not match any expected identity", subjectCommonName));
- } else if (subjectCommonName.contains(athenzProviderHostname)) {
- // ZTS treated as a node principal even though its not a Vespa node
- return NodePrincipal.withLegacyIdentity(subjectCommonName, certificateChain);
- } else {
- throw new NodeIdentifierException(String.format("Unknown certificate (subject=%s, issuer=%s)",
- subjectCommonName,
- X509CertificateUtils.getIssuerCommonNames(clientCertificate)));
- }
- }
-
- private boolean isAthenzIssued(X509Certificate certificate) {
- String issuerCommonName = X509CertificateUtils.getIssuerCommonNames(certificate).stream()
- .findFirst()
- .orElseThrow(() -> new NodeIdentifierException("Certificate issuer common name is missing!"));
- return issuerCommonName.equals("Yahoo Athenz CA") || issuerCommonName.equals("Athenz AWS CA");
- }
-
- private String getHostFromCalypsoCertificate(List<SubjectAlternativeName> sans) {
- String openStackId = getUniqueInstanceId(sans);
- return nodeCache.get().stream()
- .filter(node -> node.id().equals(openStackId))
- .map(Node::hostname)
- .findFirst()
- .orElseThrow(() -> new NodeIdentifierException(
- String.format(
- "Cannot find node with openstack-id '%s' in node repository (SANs=%s)",
- openStackId,
- sans.stream().map(SubjectAlternativeName::getValue).collect(Collectors.joining(",", "[", "]")))));
- }
-
- private String getHostFromVespaCertificate(List<SubjectAlternativeName> sans) {
- // TODO Remove this branch once all BM nodes are gone
- if (sans.stream().anyMatch(san -> san.getValue().endsWith("ostk.yahoo.cloud"))) {
- return getHostFromCalypsoCertificate(sans);
- }
- VespaUniqueInstanceId instanceId = VespaUniqueInstanceId.fromDottedString(getUniqueInstanceId(sans));
- if (!zone.environment().value().equals(instanceId.environment()))
- throw new NodeIdentifierException("Invalid environment: " + instanceId.environment());
- if (!zone.region().value().equals(instanceId.region()))
- throw new NodeIdentifierException("Invalid region(): " + instanceId.region());
- List<Node> applicationNodes =
- nodeRepository.getNodes(ApplicationId.from(instanceId.tenant(), instanceId.application(), instanceId.instance()));
- return applicationNodes.stream()
- .filter(
- node -> node.allocation()
- .map(allocation -> allocation.membership().index() == instanceId.clusterIndex()
- && allocation.membership().cluster().id().value().equals(instanceId.clusterId()))
- .orElse(false))
- .map(Node::hostname)
- .findFirst()
- .orElseThrow(() -> new NodeIdentifierException("Could not find any node with instance id: " + instanceId.asDottedString()));
- }
-
- private static String getUniqueInstanceId(List<SubjectAlternativeName> sans) {
- return sans.stream()
- .filter(san -> san.getType() == DNS_NAME)
- .map(SubjectAlternativeName::getValue)
- .filter(dnsName -> (dnsName.endsWith("yahoo.cloud") || dnsName.endsWith("oath.cloud")) && dnsName.contains(INSTANCE_ID_DELIMITER))
- .map(dnsName -> dnsName.substring(0, dnsName.indexOf(INSTANCE_ID_DELIMITER)))
- .findFirst()
- .orElseThrow(() -> new NodeIdentifierException("Could not find unique instance id from SAN addresses: " + sans));
- }
-
- static class NodeIdentifierException extends RuntimeException {
- NodeIdentifierException(String message) {
- super(message);
- }
- }
-
-}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/NodeIdentifierFilter.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/NodeIdentifierFilter.java
deleted file mode 100644
index 1c66c17a0bb..00000000000
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/NodeIdentifierFilter.java
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.provision.restapi.v2.filter;
-
-import com.google.inject.Inject;
-import com.yahoo.config.provision.Zone;
-import com.yahoo.config.provisioning.ConfigServerSecurityConfig;
-import com.yahoo.jdisc.Response;
-import com.yahoo.jdisc.http.filter.DiscFilterRequest;
-import com.yahoo.jdisc.http.filter.security.base.JsonSecurityRequestFilterBase;
-import com.yahoo.log.LogLevel;
-import com.yahoo.vespa.hosted.provision.NodeRepository;
-import com.yahoo.yolean.chain.Provides;
-
-import java.security.cert.X509Certificate;
-import java.util.List;
-import java.util.Optional;
-import java.util.logging.Logger;
-
-/**
- * A filter that identifies the remote node based on the subject and subject alternative names in client certificate.
- * A {@link NodePrincipal} object is assigned to user principal field if identification is successful.
- *
- * @author bjorncs
- */
-@Provides("NodeIdentifierFilter")
-public class NodeIdentifierFilter extends JsonSecurityRequestFilterBase {
-
- private static final Logger log = Logger.getLogger(NodeIdentifierFilter.class.getName());
-
- private final NodeIdentifier nodeIdentifier;
-
- @Inject
- public NodeIdentifierFilter(Zone zone, NodeRepository nodeRepository, ConfigServerSecurityConfig securityConfig) {
- this.nodeIdentifier = new NodeIdentifier(zone, nodeRepository, securityConfig);
- }
-
- @Override
- protected Optional<ErrorResponse> filter(DiscFilterRequest request) {
- List<X509Certificate> clientCertificateChain = request.getClientCertificateChain();
- if (clientCertificateChain.isEmpty())
- return Optional.of(new ErrorResponse(Response.Status.UNAUTHORIZED, 0, "Missing client certificate"));
- try {
- NodePrincipal identity = nodeIdentifier.resolveNode(clientCertificateChain);
- request.setUserPrincipal(identity);
- return Optional.empty();
- } catch (NodeIdentifier.NodeIdentifierException e) {
- log.log(LogLevel.WARNING, "Node identification failed: " + e.getMessage(), e);
- return Optional.of(new ErrorResponse(Response.Status.UNAUTHORIZED, 1, e.getMessage()));
- }
- }
-}
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
deleted file mode 100644
index 56a5aa72910..00000000000
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/NodePrincipal.java
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.provision.restapi.v2.filter;
-
-import com.yahoo.vespa.athenz.api.AthenzIdentity;
-import com.yahoo.vespa.athenz.utils.AthenzIdentities;
-
-import java.security.Principal;
-import java.security.cert.X509Certificate;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-
-/**
- * Represents the identity of a hosted Vespa node
- *
- * @author bjorncs
- */
-public class NodePrincipal implements Principal {
- private final String identityName;
- private final String hostname;
- private final List<X509Certificate> clientCertificateChain;
- private final Type type;
-
- 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 identityName;
- }
-
- public Optional<AthenzIdentity> getAthenzIdentityName() {
- if (type == Type.LEGACY) return Optional.empty();
- return Optional.of(AthenzIdentities.from(identityName));
- }
-
- public Optional<String> getHostname() {
- return Optional.ofNullable(hostname);
- }
-
- public List<X509Certificate> getClientCertificateChain() {
- return clientCertificateChain;
- }
-
- public Type getType() {
- return type;
- }
-
- @Override
- public String getName() {
- if (hostname == null || identityName.equals(hostname)) {
- return identityName;
- } else {
- return identityName + "/" + hostname;
- }
- }
-
- public enum Type { ATHENZ, LEGACY }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- NodePrincipal principal = (NodePrincipal) o;
- return Objects.equals(identityName, principal.identityName) &&
- Objects.equals(hostname, principal.hostname) &&
- type == principal.type;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(identityName, hostname, type);
- }
-
- @Override
- public String toString() {
- return "NodePrincipal{" +
- "identityName='" + identityName + '\'' +
- ", hostname='" + hostname + '\'' +
- ", type=" + type +
- '}';
- }
-}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/package-info.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/package-info.java
deleted file mode 100644
index e2a40f398a9..00000000000
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/package-info.java
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-/**
- * @author mpolden
- */
-@ExportPackage
-package com.yahoo.vespa.hosted.provision.restapi.v2.filter;
-
-import com.yahoo.osgi.annotation.ExportPackage; \ No newline at end of file
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/AuthorizationFilterTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/AuthorizationFilterTest.java
deleted file mode 100644
index 3ea1570f770..00000000000
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/AuthorizationFilterTest.java
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.provision.restapi.v2.filter;
-
-import com.yahoo.application.container.handler.Request.Method;
-import com.yahoo.vespa.curator.mock.MockCurator;
-import com.yahoo.vespa.hosted.provision.restapi.v2.filter.FilterTester.Request;
-import com.yahoo.vespa.hosted.provision.testutils.MockNodeFlavors;
-import com.yahoo.vespa.hosted.provision.testutils.MockNodeRepository;
-import org.junit.Test;
-
-/**
- * @author mpolden
- */
-public class AuthorizationFilterTest {
-
- private final FilterTester tester = new FilterTester(new AuthorizationFilter(
- new MockNodeRepository(new MockCurator(), new MockNodeFlavors()),
- NodeIdentifierTest.SECURITY_CONFIG));
-
- @Test
- public void filter() {
- // These are just rudimentary tests of the filter. See AuthorizerTest for more exhaustive tests
- tester.assertRequest(new Request(Method.GET, "/"), 500,
- "{\"error-code\":\"INTERNAL_SERVER_ERROR\",\"message\":\"GET / denied for " +
- "remote-addr: Principal is missing. NodeIdentifierFilter has not been applied.\"}");
-
- tester.assertRequest(new Request(Method.GET, "/").commonName("foo"), 403,
- "{\"error-code\":\"FORBIDDEN\",\"message\":\"GET / " +
- "denied for remote-addr: Invalid credentials: NodePrincipal{identityName='foo', hostname='foo', type=LEGACY}\"}");
-
- tester.assertRequest(new Request(Method.GET, "/nodes/v2/node/foo").commonName("bar"),
- 403, "{\"error-code\":\"FORBIDDEN\",\"message\":\"GET /nodes/v2/node/foo " +
- "denied for remote-addr: Invalid credentials: NodePrincipal{identityName='bar', hostname='bar', type=LEGACY}\"}");
-
- tester.assertSuccess(new Request(Method.GET, "/nodes/v2/node/foo").commonName("foo"));
- }
-
-}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/AuthorizerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/AuthorizerTest.java
deleted file mode 100644
index 939de2dff25..00000000000
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/AuthorizerTest.java
+++ /dev/null
@@ -1,196 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.provision.restapi.v2.filter;
-
-import com.yahoo.config.provision.Flavor;
-import com.yahoo.config.provision.NodeFlavors;
-import com.yahoo.config.provision.NodeType;
-import com.yahoo.vespa.curator.mock.MockCurator;
-import com.yahoo.vespa.hosted.provision.Node;
-import com.yahoo.vespa.hosted.provision.testutils.MockNodeFlavors;
-import com.yahoo.vespa.hosted.provision.testutils.MockNodeRepository;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.net.URI;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-
-import static com.yahoo.vespa.hosted.provision.restapi.v2.filter.NodeIdentifierTest.ATHENZ_PROVIDER_HOSTNAME;
-import static com.yahoo.vespa.hosted.provision.restapi.v2.filter.NodeIdentifierTest.CONFIG_SERVER_IDENTITY;
-import static com.yahoo.vespa.hosted.provision.restapi.v2.filter.NodeIdentifierTest.CONTROLLER_IDENTITY;
-import static com.yahoo.vespa.hosted.provision.restapi.v2.filter.NodeIdentifierTest.SECURITY_CONFIG;
-import static com.yahoo.vespa.hosted.provision.restapi.v2.filter.NodeIdentifierTest.TENANT_HOST_IDENTITY;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-/**
- * @author mpolden
- */
-public class AuthorizerTest {
-
- private Authorizer authorizer;
-
- @Before
- public void before() {
- NodeFlavors flavors = new MockNodeFlavors();
- MockNodeRepository nodeRepository = new MockNodeRepository(new MockCurator(), flavors);
- authorizer = new Authorizer(nodeRepository, SECURITY_CONFIG);
-
- Set<String> ipAddresses = Set.of("127.0.0.1", "::1");
- Flavor flavor = flavors.getFlavorOrThrow("default");
- List<Node> nodes = List.of(
- nodeRepository.createNode(
- "host1", "host1", ipAddresses, Optional.empty(), flavor, NodeType.host),
- nodeRepository.createNode(
- "child1-1", "child1-1", ipAddresses, Optional.of("host1"), flavor, NodeType.tenant),
- nodeRepository.createNode(
- "child1-2", "child1-2", ipAddresses, Optional.of("host1"), flavor, NodeType.tenant),
- nodeRepository.createNode(
- "host2", "host2", ipAddresses, Optional.empty(), flavor, NodeType.host),
- nodeRepository.createNode(
- "child2-1", "child2-1", ipAddresses, Optional.of("host1.tld"), flavor, NodeType.tenant),
- nodeRepository.createNode(
- "proxy1", "proxy1", ipAddresses, Optional.of("proxyhost1"), flavor, NodeType.proxy),
- nodeRepository.createNode(
- "proxyhost1", "proxyhost1", ipAddresses, Optional.empty(), flavor, NodeType.proxyhost)
- );
- nodeRepository.addNodes(nodes);
- }
-
- @Test
- public void root_authorization() {
- assertFalse(authorizedTenantNode("", ""));
- assertFalse(authorizedTenantNode("", "/"));
- assertFalse(authorizedTenantNode("node1", ""));
- assertFalse(authorizedTenantNode("node1", "/"));
- }
-
- @Test
- public void nodes_authorization() {
- // Node can only access its own resources
- assertFalse(authorizedTenantNode("node1", "/nodes/v2/node"));
- assertFalse(authorizedTenantNode("node1", "/nodes/v2/node/"));
- assertFalse(authorizedTenantNode("node1", "/nodes/v2/node/node2"));
- assertFalse(authorizedTenantNode("node1", "/nodes/v2/state/dirty/"));
- assertFalse(authorizedTenantNode("node1", "/nodes/v2/state/dirty/node2"));
- // Path traversal fails gracefully
- assertFalse(authorizedTenantNode("node1", "/nodes/v2/node/."));
- assertFalse(authorizedTenantNode("node1", "/nodes/v2/node/.."));
- assertFalse(authorizedTenantNode("node1", "/nodes/v2/acl/node2"));
- assertFalse(authorizedTenantNode("node1", "/nodes/v2/node/?parentHost=node2"));
- // Node resource always takes precedence over filter
- assertFalse(authorizedTenantNode("node1", "/nodes/v2/acl/node2?hostname=node1"));
- assertFalse(authorizedTenantNode("node1", "/nodes/v2/command/reboot/"));
- assertFalse(authorizedTenantNode("node1", "/nodes/v2/command/reboot/?hostname="));
- assertFalse(authorizedTenantNode("node1", "/nodes/v2/command/reboot/?hostname=node2"));
- assertTrue(authorizedTenantNode("node1", "/nodes/v2/node/node1"));
- assertTrue(authorizedTenantNode("node1", "/nodes/v2/state/dirty/node1"));
- assertTrue(authorizedTenantNode("node1", "/nodes/v2/acl/node1"));
- assertTrue(authorizedTenantNode("node1", "/nodes/v2/command/reboot?hostname=node1"));
- assertTrue(authorizedTenantNode("node1", "/nodes/v2/node/?parentHost=node1"));
-
- // Host node can access itself and its children
- assertFalse(authorizedTenantHostNode("host1", "/nodes/v2/node/child2-1"));
- assertFalse(authorizedTenantHostNode("host1", "/nodes/v2/command/reboot?hostname=child2-1"));
- assertTrue(authorizedTenantHostNode("host1", "/nodes/v2/node/host1"));
- assertTrue(authorizedTenantHostNode("host1", "/nodes/v2/node/child1-1"));
- assertTrue(authorizedTenantHostNode("host1", "/nodes/v2/command/reboot?hostname=child1-1"));
- assertTrue(authorizedTenantHostNode("host1", "/athenz/v1/provider/identity-document/tenant/host1"));
- assertTrue(authorizedTenantHostNode("host1", "/athenz/v1/provider/identity-document/node/child1-1"));
-
- // Trusted services can access everything in their own system
- assertTrue(authorizedController(CONTROLLER_IDENTITY, "/"));
- assertTrue(authorizedController(CONFIG_SERVER_IDENTITY, "/"));
- assertTrue(authorizedController(CONTROLLER_IDENTITY, "/nodes/v2/node/"));
- assertTrue(authorizedController(CONTROLLER_IDENTITY, "/nodes/v2/node/node1"));
- assertTrue(authorizedController(CONFIG_SERVER_IDENTITY, "/nodes/v2/node/node1"));
- }
-
- @Test
- public void orchestrator_authorization() {
- // Node can only access its own resources
- assertFalse(authorizedTenantNode("node1", "/orchestrator/v1/hosts"));
- assertFalse(authorizedTenantNode("node1", "/orchestrator/v1/hosts/"));
- assertFalse(authorizedTenantNode("node1", "/orchestrator/v1/hosts/node2"));
- assertFalse(authorizedTenantNode("node1", "/orchestrator/v1/hosts/node2/suspended"));
-
- // Node can suspend itself
- assertTrue(authorizedTenantNode("node1", "/orchestrator/v1/hosts/node1"));
- assertTrue(authorizedTenantNode("node1", "/orchestrator/v1/hosts/node1/suspended"));
-
- // Host node can suspend itself and its children
- assertFalse(authorizedTenantHostNode("host1", "/orchestrator/v1/hosts/child2-1/suspended"));
- assertFalse(authorizedTenantHostNode("host1", "/orchestrator/v1/suspensions/hosts/host1?hostname=child2-1"));
- // All given hostnames must be children
- assertFalse(authorizedTenantHostNode("host1", "/orchestrator/v1/suspensions/hosts/host1?hostname=child1-1&hostname=child2-1"));
- assertTrue(authorizedTenantHostNode("host1", "/orchestrator/v1/hosts/host1/suspended"));
- assertTrue(authorizedTenantHostNode("host1", "/orchestrator/v1/hosts/child1-1/suspended"));
- assertTrue(authorizedTenantHostNode("host1", "/orchestrator/v1/suspensions/hosts/host1?hostname=child1-1"));
- // Multiple children
- assertTrue(authorizedTenantHostNode("host1", "/orchestrator/v1/suspensions/hosts/host1?hostname=child1-1&hostname=child1-2"));
- }
-
- @Test
- public void flags_authorization() {
- // Tenant nodes cannot access flags resources
- assertFalse(authorizedTenantNode("node1", "/flags/v1/data"));
- assertFalse(authorizedTenantNode("node1", "/flags/v1/data/flagid"));
- assertFalse(authorizedTenantNode("node1", "/flags/v1/foo"));
-
- // Host node can access data
- assertTrue(authorizedTenantHostNode("host1", "/flags/v1/data"));
- assertFalse(authorizedTenantHostNode("host1", "/flags/v1/data/flagid"));
- assertFalse(authorizedTenantHostNode("host1", "/flags/v1/foo"));
- assertTrue(authorizedTenantHostNode("proxy1-host", "/flags/v1/data"));
- assertFalse(authorizedTenantHostNode("proxy1-host", "/flags/v1/data/flagid"));
- assertFalse(authorizedTenantHostNode("proxy1-host", "/flags/v1/foo"));
- assertTrue(authorizedController(CONFIG_SERVER_IDENTITY, "/flags/v1/data"));
- assertFalse(authorizedController(CONFIG_SERVER_IDENTITY, "/flags/v1/data/flagid"));
- assertFalse(authorizedController(CONFIG_SERVER_IDENTITY, "/flags/v1/foo"));
-
- // Controller can access everything
- assertTrue(authorizedController(CONTROLLER_IDENTITY, "/flags/v1/data"));
- assertTrue(authorizedController(CONTROLLER_IDENTITY, "/flags/v1/data/flagid"));
- assertTrue(authorizedController(CONTROLLER_IDENTITY, "/flags/v1/foo"));
- }
-
- @Test
- public void routing_authorization() {
- // Node of proxy or proxyhost type can access routing resource
- assertFalse(authorizedTenantNode("node1", "/routing/v1/status"));
- assertTrue(authorizedTenantNode("proxy1", "/routing/v1/status"));
- assertTrue(authorizedTenantNode("proxyhost1", "/routing/v1/status"));
- }
-
- @Test
- public void zts_allowed_for_athenz_provider_api() {
- assertTrue(authorizedLegacyNode(ATHENZ_PROVIDER_HOSTNAME, "/athenz/v1/provider/refresh"));
- assertTrue(authorizedLegacyNode(ATHENZ_PROVIDER_HOSTNAME, "/athenz/v1/provider/instance"));
- }
-
- private boolean authorizedTenantNode(String hostname, String path) {
- return authorized(NodePrincipal.withAthenzIdentity("vespa.vespa.tenant", hostname, List.of()), path);
- }
-
- private boolean authorizedTenantHostNode(String hostname, String path) {
- return authorized(NodePrincipal.withAthenzIdentity(TENANT_HOST_IDENTITY, hostname, List.of()), path);
- }
-
- private boolean authorizedLegacyNode(String hostname, String path) {
- return authorized(NodePrincipal.withLegacyIdentity(hostname, List.of()), path);
- }
-
- private boolean authorizedController(String controllerIdentity, String path) {
- return authorized(NodePrincipal.withAthenzIdentity(controllerIdentity, List.of()), path);
- }
-
- private boolean authorized(NodePrincipal principal, String path) {
- return authorizer.test(principal, uri(path));
- }
-
- private static URI uri(String path) {
- return URI.create("http://localhost").resolve(path);
- }
-
-}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/FilterTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/FilterTester.java
deleted file mode 100644
index c6546f05955..00000000000
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/FilterTester.java
+++ /dev/null
@@ -1,155 +0,0 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.provision.restapi.v2.filter;
-
-import com.yahoo.application.container.handler.Request.Method;
-import com.yahoo.container.jdisc.RequestHandlerTestDriver;
-import com.yahoo.jdisc.http.filter.DiscFilterRequest;
-import com.yahoo.jdisc.http.filter.SecurityRequestFilter;
-import com.yahoo.security.KeyAlgorithm;
-import com.yahoo.security.KeyUtils;
-import com.yahoo.security.X509CertificateBuilder;
-
-import javax.security.auth.x500.X500Principal;
-import java.math.BigInteger;
-import java.net.URI;
-import java.security.KeyPair;
-import java.security.cert.X509Certificate;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-import static com.yahoo.security.SignatureAlgorithm.SHA256_WITH_ECDSA;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-/**
- * @author mpolden
- */
-public class FilterTester {
-
- private final SecurityRequestFilter filter;
-
- public FilterTester(SecurityRequestFilter filter) {
- this.filter = filter;
- }
-
- public void assertSuccess(Request request) {
- assertFalse("No response written by filter", getResponse(request).isPresent());
- }
-
- public void assertRequest(Request request, int status, String body) {
- Optional<Response> response = getResponse(request);
- assertTrue("Expected response from filter", response.isPresent());
- assertEquals("Response body", body, response.get().body);
- assertEquals("Content type", "application/json",
- response.get().headers.get("Content-Type").get(0));
- assertEquals("Status code", status, response.get().status);
- }
-
- private Optional<Response> getResponse(Request request) {
- RequestHandlerTestDriver.MockResponseHandler handler = new RequestHandlerTestDriver.MockResponseHandler();
- filter.filter(toDiscFilterRequest(request), handler);
- return Optional.ofNullable(handler.getResponse())
- .map(response -> new Response(response.getStatus(), response.headers(), handler.readAll()));
- }
-
- private static DiscFilterRequest toDiscFilterRequest(Request request) {
- DiscFilterRequest r = mock(DiscFilterRequest.class);
- when(r.getMethod()).thenReturn(request.method().name());
- when(r.getUri()).thenReturn(URI.create("http://localhost").resolve(request.path()));
- when(r.getRemoteAddr()).thenReturn(request.remoteAddr());
- when(r.getLocalAddr()).thenReturn(request.localAddr());
- if (request.commonName().isPresent()) {
- X509Certificate cert = certificateFor(request.commonName().get(), KeyUtils.generateKeypair(KeyAlgorithm.EC));
- List<X509Certificate> certs = Collections.singletonList(cert);
- when(r.getClientCertificateChain()).thenReturn(certs);
- when(r.getUserPrincipal()).thenReturn(NodePrincipal.withLegacyIdentity(request.commonName().get(), certs));
- }
- return r;
- }
-
-
- /** Create a self signed certificate for commonName using given public/private key pair */
- private static X509Certificate certificateFor(String commonName, KeyPair keyPair) {
- Instant now = Instant.now();
- X500Principal subject = new X500Principal("CN=" + commonName);
- return X509CertificateBuilder
- .fromKeypair(keyPair, subject, now, now.plus(Duration.ofDays(30)), SHA256_WITH_ECDSA, BigInteger.valueOf(now.toEpochMilli()))
- .setBasicConstraints(true, true)
- .build();
- }
-
- private static class Response {
-
- private final int status;
- private final Map<String, List<String>> headers;
- private final String body;
-
- private Response(int status, Map<String, List<String>> headers, String body) {
- this.status = status;
- this.headers = headers;
- this.body = body;
- }
-
- }
-
- public static class Request {
-
- private final Method method;
- private final String path;
- private String localAddr;
- private String remoteAddr;
- private String commonName;
-
- public Request(Method method, String path) {
- this.method = method;
- this.path = path;
- this.commonName = null;
- this.localAddr = "local-addr";
- this.remoteAddr = "remote-addr";
- }
-
- public Method method() {
- return method;
- }
-
- public String path() {
- return path;
- }
-
- public String localAddr() {
- return localAddr;
- }
-
- public String remoteAddr() {
- return remoteAddr;
- }
-
- public Optional<String> commonName() {
- return Optional.ofNullable(commonName);
- }
-
- public Request commonName(String commonName) {
- this.commonName = commonName;
- return this;
- }
-
- public Request localAddr(String localAddr) {
- this.localAddr = localAddr;
- return this;
- }
-
- public Request remoteAddr(String remoteAddr) {
- this.remoteAddr = remoteAddr;
- return this;
- }
-
- }
-
-}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/NodeIdentifierTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/NodeIdentifierTest.java
deleted file mode 100644
index 914a2e62164..00000000000
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/NodeIdentifierTest.java
+++ /dev/null
@@ -1,272 +0,0 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.provision.restapi.v2.filter;
-
-import com.yahoo.component.Version;
-import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.ClusterMembership;
-import com.yahoo.config.provision.ClusterSpec;
-import com.yahoo.config.provision.Environment;
-import com.yahoo.config.provision.NodeResources;
-import com.yahoo.config.provision.NodeType;
-import com.yahoo.config.provision.RegionName;
-import com.yahoo.config.provision.SystemName;
-import com.yahoo.config.provision.Zone;
-import com.yahoo.config.provisioning.ConfigServerSecurityConfig;
-import com.yahoo.security.KeyUtils;
-import com.yahoo.security.Pkcs10Csr;
-import com.yahoo.security.Pkcs10CsrBuilder;
-import com.yahoo.security.X509CertificateBuilder;
-import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId;
-import com.yahoo.vespa.hosted.provision.Node;
-import com.yahoo.vespa.hosted.provision.NodeRepositoryTester;
-import com.yahoo.vespa.hosted.provision.node.Allocation;
-import com.yahoo.vespa.hosted.provision.node.Generation;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-
-import javax.security.auth.x500.X500Principal;
-import java.math.BigInteger;
-import java.security.KeyPair;
-import java.security.cert.X509Certificate;
-import java.time.Instant;
-import java.util.Collections;
-import java.util.Optional;
-
-import static com.yahoo.security.KeyAlgorithm.EC;
-import static com.yahoo.security.SignatureAlgorithm.SHA256_WITH_ECDSA;
-import static com.yahoo.vespa.athenz.identityprovider.api.IdentityType.NODE;
-import static java.util.Collections.emptySet;
-import static java.util.Collections.singleton;
-import static java.util.Collections.singletonList;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-/**
- * @author bjorncs
- */
-public class NodeIdentifierTest {
-
- @Rule
- public final ExpectedException expectedException = ExpectedException.none();
-
- static final String ATHENZ_PROVIDER_HOSTNAME = "zts.domain.tld";
- static final String CONTROLLER_IDENTITY = "vespa.controller";
- static final String CONFIG_SERVER_IDENTITY = "vespa.configserver";
- static final String PROXY_HOST_IDENTITY = "vespa.proxy";
- static final String TENANT_HOST_IDENTITY = "vespa.tenant-host";
- static final String TENANT_IDENTITY = "vespa.tenant";
- static final ConfigServerSecurityConfig SECURITY_CONFIG = new ConfigServerSecurityConfig.Builder()
- .athenzProviderHostname(ATHENZ_PROVIDER_HOSTNAME)
- .controllerHostIdentity(CONTROLLER_IDENTITY)
- .configServerHostIdentity(CONFIG_SERVER_IDENTITY)
- .proxyHostIdentity(PROXY_HOST_IDENTITY)
- .tenantHostIdentity(TENANT_HOST_IDENTITY)
- .tenantIdentity(TENANT_IDENTITY)
- .build();
-
- private static final String HOSTNAME = "myhostname";
- private static final String PROXY_HOSTNAME = "myproxyhostname";
-
- private static final String OPENSTACK_ID = "OPENSTACK-ID";
- private static final String AWS_INSTANCE_ID = "i-abcdef123456";
-
- private static final String INSTANCE_ID = "default";
- private static final Zone ZONE = new Zone(SystemName.main, Environment.prod, RegionName.defaultName());
- private static final KeyPair KEYPAIR = KeyUtils.generateKeypair(EC);
- private static final X509Certificate ATHENZ_YAHOO_CA_CERT = createDummyCaCertificate("Yahoo Athenz CA");
- private static final X509Certificate ATHENZ_AWS_CA_CERT = createDummyCaCertificate("Athenz AWS CA");
-
- @Test
- public void rejects_unknown_cert() {
- NodeRepositoryTester nodeRepositoryDummy = new NodeRepositoryTester();
- X509Certificate certificate = X509CertificateBuilder
- .fromKeypair(
- KEYPAIR, new X500Principal("CN=" + HOSTNAME), Instant.EPOCH, Instant.EPOCH.plusSeconds(60), SHA256_WITH_ECDSA, BigInteger.ONE)
- .build();
- NodeIdentifier identifier = new NodeIdentifier(ZONE, nodeRepositoryDummy.nodeRepository(), SECURITY_CONFIG);
- expectedException.expect(NodeIdentifier.NodeIdentifierException.class);
- expectedException.expectMessage("(subject=myhostname, issuer=[myhostname])");
- identifier.resolveNode(singletonList(certificate));
- }
-
- @Test
- public void accepts_openstack_host_certificate() {
- NodeRepositoryTester nodeRepositoryDummy = new NodeRepositoryTester();
- nodeRepositoryDummy.addNode(OPENSTACK_ID, HOSTNAME, INSTANCE_ID, NodeType.host);
- nodeRepositoryDummy.setNodeState(HOSTNAME, Node.State.active);
- Pkcs10Csr csr = Pkcs10CsrBuilder
- .fromKeypair(new X500Principal("CN=" + TENANT_HOST_IDENTITY), KEYPAIR, SHA256_WITH_ECDSA)
- .build();
- X509Certificate certificate = X509CertificateBuilder
- .fromCsr(csr, ATHENZ_YAHOO_CA_CERT.getSubjectX500Principal(), Instant.EPOCH, Instant.EPOCH.plusSeconds(60), KEYPAIR.getPrivate(), SHA256_WITH_ECDSA, BigInteger.ONE)
- .addSubjectAlternativeName(OPENSTACK_ID + ".instanceid.athenz.provider-name.ostk.yahoo.cloud")
- .build();
- NodeIdentifier identifier = new NodeIdentifier(ZONE, nodeRepositoryDummy.nodeRepository(), SECURITY_CONFIG);
- NodePrincipal identity = identifier.resolveNode(singletonList(certificate));
- assertTrue(identity.getHostname().isPresent());
- assertEquals(HOSTNAME, identity.getHostname().get());
- assertEquals(TENANT_HOST_IDENTITY, identity.getHostIdentityName());
- }
-
- @Test
- public void accepts_aws_host_certificate() {
- NodeRepositoryTester nodeRepositoryDummy = new NodeRepositoryTester();
- nodeRepositoryDummy.addNode(AWS_INSTANCE_ID, HOSTNAME, INSTANCE_ID, NodeType.host);
- nodeRepositoryDummy.setNodeState(HOSTNAME, Node.State.active);
- Pkcs10Csr csr = Pkcs10CsrBuilder
- .fromKeypair(new X500Principal("CN=" + TENANT_HOST_IDENTITY), KEYPAIR, SHA256_WITH_ECDSA)
- .build();
- X509Certificate certificate = X509CertificateBuilder
- .fromCsr(csr, ATHENZ_AWS_CA_CERT.getSubjectX500Principal(), Instant.EPOCH, Instant.EPOCH.plusSeconds(60), KEYPAIR.getPrivate(), SHA256_WITH_ECDSA, BigInteger.ONE)
- .addSubjectAlternativeName(AWS_INSTANCE_ID + ".instanceid.athenz.aws.oath.cloud")
- .build();
- NodeIdentifier identifier = new NodeIdentifier(ZONE, nodeRepositoryDummy.nodeRepository(), SECURITY_CONFIG);
- NodePrincipal identity = identifier.resolveNode(singletonList(certificate));
- assertTrue(identity.getHostname().isPresent());
- assertEquals(HOSTNAME, identity.getHostname().get());
- assertEquals(TENANT_HOST_IDENTITY, identity.getHostIdentityName());
- }
-
- @Test
- public void accepts_aws_proxy_host_certificate() {
- NodeRepositoryTester nodeRepositoryDummy = new NodeRepositoryTester();
- nodeRepositoryDummy.addNode(AWS_INSTANCE_ID, PROXY_HOSTNAME, INSTANCE_ID, NodeType.proxyhost);
- nodeRepositoryDummy.setNodeState(PROXY_HOSTNAME, Node.State.active);
- Pkcs10Csr csr = Pkcs10CsrBuilder
- .fromKeypair(new X500Principal("CN=" + PROXY_HOST_IDENTITY), KEYPAIR, SHA256_WITH_ECDSA)
- .build();
- X509Certificate certificate = X509CertificateBuilder
- .fromCsr(csr, ATHENZ_AWS_CA_CERT.getSubjectX500Principal(), Instant.EPOCH, Instant.EPOCH.plusSeconds(60), KEYPAIR.getPrivate(), SHA256_WITH_ECDSA, BigInteger.ONE)
- .addSubjectAlternativeName(AWS_INSTANCE_ID + ".instanceid.athenz.aws.oath.cloud")
- .build();
- NodeIdentifier identifier = new NodeIdentifier(ZONE, nodeRepositoryDummy.nodeRepository(), SECURITY_CONFIG);
- NodePrincipal identity = identifier.resolveNode(singletonList(certificate));
- assertTrue(identity.getHostname().isPresent());
- assertEquals(PROXY_HOSTNAME, identity.getHostname().get());
- assertEquals(PROXY_HOST_IDENTITY, identity.getHostIdentityName());
- }
-
- @Test
- public void accepts_aws_configserver_host_certificate() {
- NodeRepositoryTester nodeRepositoryDummy = new NodeRepositoryTester();
- Pkcs10Csr csr = Pkcs10CsrBuilder
- .fromKeypair(new X500Principal("CN=" + CONFIG_SERVER_IDENTITY), KEYPAIR, SHA256_WITH_ECDSA)
- .build();
- X509Certificate certificate = X509CertificateBuilder
- .fromCsr(csr, ATHENZ_AWS_CA_CERT.getSubjectX500Principal(), Instant.EPOCH, Instant.EPOCH.plusSeconds(60), KEYPAIR.getPrivate(), SHA256_WITH_ECDSA, BigInteger.ONE)
- .addSubjectAlternativeName(AWS_INSTANCE_ID + ".instanceid.athenz.aws.oath.cloud")
- .build();
- NodeIdentifier identifier = new NodeIdentifier(ZONE, nodeRepositoryDummy.nodeRepository(), SECURITY_CONFIG);
- NodePrincipal identity = identifier.resolveNode(singletonList(certificate));
- assertEquals(CONFIG_SERVER_IDENTITY, identity.getHostIdentityName());
- }
-
- @Test
- public void accepts_zts_certificate() {
- X509Certificate certificate = X509CertificateBuilder
- .fromKeypair(KEYPAIR, new X500Principal("CN=" + ATHENZ_PROVIDER_HOSTNAME), Instant.EPOCH, Instant.EPOCH.plusSeconds(60), SHA256_WITH_ECDSA, BigInteger.ONE)
- .build();
- NodeIdentifier identifier = new NodeIdentifier(ZONE, new NodeRepositoryTester().nodeRepository(), SECURITY_CONFIG);
- NodePrincipal identity = identifier.resolveNode(singletonList(certificate));
- assertEquals(ATHENZ_PROVIDER_HOSTNAME, identity.getHostIdentityName());
- assertEquals(NodePrincipal.Type.LEGACY, identity.getType());
- }
-
- @Test
- public void accepts_docker_container_certificate() {
- String clusterId = "clusterid";
- int clusterIndex = 0;
- String tenant = "tenant";
- String application = "application";
- String region = ZONE.region().value();
- String environment = ZONE.environment().value();
- NodeRepositoryTester nodeRepositoryDummy = new NodeRepositoryTester();
- Node node = createNode(clusterId, clusterIndex, tenant, application);
- nodeRepositoryDummy.nodeRepository().addDockerNodes(singletonList(node), nodeRepositoryDummy.nodeRepository().lockAllocation());
- Pkcs10Csr csr = Pkcs10CsrBuilder
- .fromKeypair(new X500Principal("CN=" + TENANT_IDENTITY), KEYPAIR, SHA256_WITH_ECDSA)
- .build();
- VespaUniqueInstanceId vespaUniqueInstanceId = new VespaUniqueInstanceId(clusterIndex, clusterId, INSTANCE_ID, application, tenant, region, environment, NODE);
- X509Certificate certificate = X509CertificateBuilder
- .fromCsr(csr, ATHENZ_YAHOO_CA_CERT.getSubjectX500Principal(), Instant.EPOCH, Instant.EPOCH.plusSeconds(60), KEYPAIR.getPrivate(), SHA256_WITH_ECDSA, BigInteger.ONE)
- .addSubjectAlternativeName(vespaUniqueInstanceId.asDottedString() + ".instanceid.athenz.provider-name.vespa.yahoo.cloud")
- .build();
- NodeIdentifier identifier = new NodeIdentifier(ZONE, nodeRepositoryDummy.nodeRepository(), SECURITY_CONFIG);
- NodePrincipal identity = identifier.resolveNode(singletonList(certificate));
- assertTrue(identity.getHostname().isPresent());
- assertEquals(HOSTNAME, identity.getHostname().get());
- assertEquals(TENANT_IDENTITY, identity.getHostIdentityName());
- }
-
- @Test
- public void accepts_controller_certificate() {
- NodeRepositoryTester nodeRepositoryDummy = new NodeRepositoryTester();
- Pkcs10Csr csr = Pkcs10CsrBuilder
- .fromKeypair(new X500Principal("CN=" + CONTROLLER_IDENTITY), KEYPAIR, SHA256_WITH_ECDSA)
- .build();
- X509Certificate certificate = X509CertificateBuilder
- .fromCsr(csr, ATHENZ_YAHOO_CA_CERT.getSubjectX500Principal(), Instant.EPOCH, Instant.EPOCH.plusSeconds(60), KEYPAIR.getPrivate(), SHA256_WITH_ECDSA, BigInteger.ONE)
- .build();
- NodeIdentifier identifier = new NodeIdentifier(ZONE, nodeRepositoryDummy.nodeRepository(), SECURITY_CONFIG);
- NodePrincipal identity = identifier.resolveNode(singletonList(certificate));
- assertFalse(identity.getHostname().isPresent());
- assertEquals(CONTROLLER_IDENTITY, identity.getHostIdentityName());
- }
-
- @Test
- public void accepts_openstack_bm_tenant_certificate() {
- NodeRepositoryTester nodeRepositoryDummy = new NodeRepositoryTester();
- nodeRepositoryDummy.addNode(OPENSTACK_ID, HOSTNAME, INSTANCE_ID, NodeType.tenant);
- nodeRepositoryDummy.setNodeState(HOSTNAME, Node.State.active);
- Pkcs10Csr csr = Pkcs10CsrBuilder
- .fromKeypair(new X500Principal("CN=" + TENANT_IDENTITY), KEYPAIR, SHA256_WITH_ECDSA)
- .build();
- X509Certificate certificate = X509CertificateBuilder
- .fromCsr(csr, ATHENZ_YAHOO_CA_CERT.getSubjectX500Principal(), Instant.EPOCH, Instant.EPOCH.plusSeconds(60), KEYPAIR.getPrivate(), SHA256_WITH_ECDSA, BigInteger.ONE)
- .addSubjectAlternativeName(OPENSTACK_ID + ".instanceid.athenz.ostk.yahoo.cloud")
- .build();
- NodeIdentifier identifier = new NodeIdentifier(ZONE, nodeRepositoryDummy.nodeRepository(), SECURITY_CONFIG);
- NodePrincipal identity = identifier.resolveNode(singletonList(certificate));
- assertTrue(identity.getHostname().isPresent());
- assertEquals(HOSTNAME, identity.getHostname().get());
- assertEquals(TENANT_IDENTITY, identity.getHostIdentityName());
- }
-
- private static Node createNode(String clusterId, int clusterIndex, String tenant, String application) {
- return Node
- .createDockerNode(
- singleton("1.2.3.4"),
- emptySet(),
- HOSTNAME,
- Optional.of("parenthost"),
- new NodeResources(1, 2, 50),
- NodeType.tenant)
- .with(
- new Allocation(
- ApplicationId.from(tenant, application, INSTANCE_ID),
- ClusterMembership.from(
- ClusterSpec.from(
- ClusterSpec.Type.container,
- new ClusterSpec.Id(clusterId),
- ClusterSpec.Group.from(0),
- Version.emptyVersion,
- false, Collections.emptySet()),
- clusterIndex),
- Generation.initial(),
- false));
-
- }
-
- private static X509Certificate createDummyCaCertificate(String caCommonName) {
- KeyPair keyPair = KeyUtils.generateKeypair(EC);
- return X509CertificateBuilder
- .fromKeypair(
- keyPair, new X500Principal("CN=" + caCommonName), Instant.EPOCH, Instant.EPOCH.plusSeconds(60), SHA256_WITH_ECDSA, BigInteger.ONE)
- .setBasicConstraints(true, true)
- .build();
-
- }
-
-}