aboutsummaryrefslogtreecommitdiffstats
path: root/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilter.java
diff options
context:
space:
mode:
authorJon Marius Venstad <venstad@gmail.com>2019-10-02 17:46:33 +0200
committerJon Marius Venstad <venstad@gmail.com>2019-10-02 17:46:33 +0200
commit816cf75a092b426f671799c645d04845758181eb (patch)
tree42f85cc0ff589e38eca05138c45b253385e1c849 /controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilter.java
parent70290819f83da07f76c2e5d70513a09ef6fd4f52 (diff)
Differentiate between developer and headeless keys in signature filter
Diffstat (limited to 'controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilter.java')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilter.java77
1 files changed, 61 insertions, 16 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilter.java
index 6755110bb49..fc9fd8ae235 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilter.java
@@ -10,19 +10,27 @@ import com.yahoo.config.provision.TenantName;
import com.yahoo.jdisc.http.filter.DiscFilterRequest;
import com.yahoo.jdisc.http.filter.security.base.JsonSecurityRequestFilterBase;
import com.yahoo.log.LogLevel;
+import com.yahoo.security.KeyUtils;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.role.Role;
import com.yahoo.vespa.hosted.controller.api.role.SecurityContext;
import com.yahoo.vespa.hosted.controller.api.role.SimplePrincipal;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
+import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.yolean.Exceptions;
import java.security.Principal;
+import java.security.PublicKey;
+import java.util.Base64;
+import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Function;
import java.util.logging.Logger;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
/**
* Assigns the {@link Role#buildService(TenantName, ApplicationName)} role to requests with a
* Authorization header signature matching the public key of the indicated application.
@@ -47,23 +55,40 @@ public class SignatureFilter extends JsonSecurityRequestFilterBase {
&& request.getHeader("X-Authorization") != null)
try {
ApplicationId id = ApplicationId.fromSerializedForm(request.getHeader("X-Key-Id"));
- boolean verified = controller.applications().getApplication(TenantAndApplicationId.from(id)).stream()
- .flatMap(application -> application.pemDeployKeys().stream())
- .map(key -> new RequestVerifier(key, controller.clock()))
- .anyMatch(verifier -> verifier.verify(Method.valueOf(request.getMethod()),
- request.getUri(),
- request.getHeader("X-Timestamp"),
- request.getHeader("X-Content-Hash"),
- request.getHeader("X-Authorization")));
+ SecurityContext securityContext = null;
+ if (request.getHeader("X-Key") != null) {
+ PublicKey key = KeyUtils.fromPemEncodedPublicKey(new String(Base64.getDecoder().decode(request.getHeader("X-Key")), UTF_8));
+ if (keyVerifies(key, request)) {
+ Principal principal = null;
+ Set<Role> roles = new HashSet<>();
+ Optional <Application> application = controller.applications().getApplication(TenantAndApplicationId.from(id));
+ if (application.isPresent() && application.get().deployKeys().contains(key)) {
+ principal = new SimplePrincipal("headless@" + id.tenant() + "." + id.application());
+ roles.add(Role.reader(id.tenant()));
+ roles.add(Role.headless(id.tenant(), id.application()));
+ }
+
+ Optional<CloudTenant> tenant = controller.tenants().get(id.tenant())
+ .filter(CloudTenant.class::isInstance)
+ .map(CloudTenant.class::cast);
+ if (tenant.isPresent() && tenant.get().developerKeys().containsKey(key)) {
+ principal = tenant.get().developerKeys().get(key); // Precedence over headless user.
+ roles.add(Role.reader(id.tenant()));
+ roles.add(Role.developer(id.tenant()));
+ }
+
+ if (principal != null)
+ securityContext = new SecurityContext(principal, roles);
+ }
+ }
+ else if (anyDeployKeyMatches(TenantAndApplicationId.from(id), request))
+ securityContext = new SecurityContext(new SimplePrincipal("buildService@" + id.tenant() + "." + id.application()),
+ Set.of(Role.buildService(id.tenant(), id.application())));
- if (verified) {
- Principal principal = new SimplePrincipal("buildService@" + id.tenant() + "." + id.application());
- request.setUserPrincipal(principal);
- request.setRemoteUser(principal.getName());
- request.setAttribute(SecurityContext.ATTRIBUTE_NAME,
- new SecurityContext(principal,
- Set.of(Role.buildService(id.tenant(), id.application()),
- Role.applicationDeveloper(id.tenant(), id.application()))));
+ if (securityContext != null) {
+ request.setUserPrincipal(securityContext.principal());
+ request.setRemoteUser(securityContext.principal().getName());
+ request.setAttribute(SecurityContext.ATTRIBUTE_NAME, securityContext);
}
}
catch (Exception e) {
@@ -72,4 +97,24 @@ public class SignatureFilter extends JsonSecurityRequestFilterBase {
return Optional.empty();
}
+ // TODO jonmv: Remove after October 2019.
+ private boolean anyDeployKeyMatches(TenantAndApplicationId id, DiscFilterRequest request) {
+ return controller.applications().getApplication(id).stream()
+ .map(Application::deployKeys)
+ .flatMap(Set::stream)
+ .anyMatch(key -> keyVerifies(key, request));
+ }
+
+ private boolean keyVerifies(PublicKey key, DiscFilterRequest request) {
+ return new RequestVerifier(key, controller.clock()).verify(Method.valueOf(request.getMethod()),
+ request.getUri(),
+ request.getHeader("X-Timestamp"),
+ request.getHeader("X-Content-Hash"),
+ request.getHeader("X-Authorization"));
+ }
+
+ private Optional<Principal> getPrincipal(CloudTenant tenant, PublicKey key) {
+ return Optional.empty();
+ }
+
}