summaryrefslogtreecommitdiffstats
path: root/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilter.java
diff options
context:
space:
mode:
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.java76
1 files changed, 57 insertions, 19 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..7ad2e03ef1d 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.
@@ -46,25 +54,11 @@ public class SignatureFilter extends JsonSecurityRequestFilterBase {
if ( request.getAttribute(SecurityContext.ATTRIBUTE_NAME) == null
&& 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")));
-
- 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()))));
- }
+ getSecurityContext(request).ifPresent(securityContext -> {
+ request.setUserPrincipal(securityContext.principal());
+ request.setRemoteUser(securityContext.principal().getName());
+ request.setAttribute(SecurityContext.ATTRIBUTE_NAME, securityContext);
+ });
}
catch (Exception e) {
logger.log(LogLevel.DEBUG, () -> "Exception verifying signed request: " + Exceptions.toMessageString(e));
@@ -72,4 +66,48 @@ 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<SecurityContext> getSecurityContext(DiscFilterRequest request) {
+ ApplicationId id = ApplicationId.fromSerializedForm(request.getHeader("X-Key-Id"));
+ if (request.getHeader("X-Key") != null) { // TODO jonmv: Remove check and else branch after Oct 2019.
+ PublicKey key = KeyUtils.fromPemEncodedPublicKey(new String(Base64.getDecoder().decode(request.getHeader("X-Key")), UTF_8));
+ if (keyVerifies(key, request)) {
+ Optional<CloudTenant> tenant = controller.tenants().get(id.tenant())
+ .filter(CloudTenant.class::isInstance)
+ .map(CloudTenant.class::cast);
+ if (tenant.isPresent() && tenant.get().developerKeys().containsKey(key))
+ return Optional.of(new SecurityContext(tenant.get().developerKeys().get(key),
+ Set.of(Role.reader(id.tenant()),
+ Role.developer(id.tenant()))));
+
+ Optional <Application> application = controller.applications().getApplication(TenantAndApplicationId.from(id));
+ if (application.isPresent() && application.get().deployKeys().contains(key))
+ return Optional.of(new SecurityContext(new SimplePrincipal("headless@" + id.tenant() + "." + id.application()),
+ Set.of(Role.reader(id.tenant()),
+ Role.developer(id.tenant())))); // TODO jonmv: Change to headless after Oct 10 2019.
+ }
+ }
+ else if (anyDeployKeyMatches(TenantAndApplicationId.from(id), request))
+ return Optional.of(new SecurityContext(new SimplePrincipal("headless@" + id.tenant() + "." + id.application()),
+ Set.of(Role.reader(id.tenant()),
+ Role.developer(id.tenant()))));
+
+ return Optional.empty();
+ }
+
}