diff options
16 files changed, 306 insertions, 5 deletions
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java index ef491c039ba..92e8c630c7b 100644 --- a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java +++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java @@ -91,6 +91,7 @@ public interface ModelContext { @ModelFeatureFlag(owners = {"vekterli"}) default int maxActivationInhibitedOutOfSyncGroups() { return 0; } @ModelFeatureFlag(owners = {"hmusum"}) default String jvmOmitStackTraceInFastThrowOption(ClusterSpec.Type type) { return ""; } @ModelFeatureFlag(owners = {"bjorncs", "jonmv"}) default boolean enableJdiscHttp2() { return false; } + @ModelFeatureFlag(owners = {"tokle", "bjorncs"}) default boolean enableCustomAclMapping() { return false; } } /** Warning: As elsewhere in this package, do not make backwards incompatible changes that will break old config models! */ diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java index 20699a7211a..6569bde612a 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java @@ -179,6 +179,7 @@ public class ModelContextImpl implements ModelContext { private final int maxActivationInhibitedOutOfSyncGroups; private final ToIntFunction<ClusterSpec.Type> jvmOmitStackTraceInFastThrow; private final boolean enableJdiscHttp2; + private final boolean enableCustomAclMapping; public FeatureFlags(FlagSource source, ApplicationId appId) { this.dedicatedClusterControllerFlavor = parseDedicatedClusterControllerFlavor(flagValue(source, appId, Flags.DEDICATED_CLUSTER_CONTROLLER_FLAVOR)); @@ -204,6 +205,7 @@ public class ModelContextImpl implements ModelContext { this.maxActivationInhibitedOutOfSyncGroups = flagValue(source, appId, Flags.MAX_ACTIVATION_INHIBITED_OUT_OF_SYNC_GROUPS); this.jvmOmitStackTraceInFastThrow = type -> flagValueAsInt(source, appId, type, PermanentFlags.JVM_OMIT_STACK_TRACE_IN_FAST_THROW); this.enableJdiscHttp2 = flagValue(source, appId, Flags.ENABLE_JDISC_HTTP2); + this.enableCustomAclMapping = flagValue(source, appId, Flags.ENABLE_CUSTOM_ACL_MAPPING); } @Override public Optional<NodeResources> dedicatedClusterControllerFlavor() { return Optional.ofNullable(dedicatedClusterControllerFlavor); } @@ -231,6 +233,7 @@ public class ModelContextImpl implements ModelContext { return translateJvmOmitStackTraceInFastThrowIntToString(jvmOmitStackTraceInFastThrow, type); } @Override public boolean enableJdiscHttp2() { return enableJdiscHttp2; } + @Override public boolean enableCustomAclMapping() { return enableCustomAclMapping; } private static <V> V flagValue(FlagSource source, ApplicationId appId, UnboundFlag<? extends V, ?, ?> flag) { return flag.bindTo(source) diff --git a/container-core/abi-spec.json b/container-core/abi-spec.json index c133c9cc158..22fb744ad70 100644 --- a/container-core/abi-spec.json +++ b/container-core/abi-spec.json @@ -478,6 +478,38 @@ "public static final java.lang.String FULL_COVERAGE" ] }, + "com.yahoo.container.jdisc.AclMapping$Action": { + "superClass": "java.lang.Enum", + "interfaces": [], + "attributes": [ + "public", + "final", + "enum" + ], + "methods": [ + "public static com.yahoo.container.jdisc.AclMapping$Action[] values()", + "public static com.yahoo.container.jdisc.AclMapping$Action valueOf(java.lang.String)" + ], + "fields": [ + "public static final enum com.yahoo.container.jdisc.AclMapping$Action create", + "public static final enum com.yahoo.container.jdisc.AclMapping$Action read", + "public static final enum com.yahoo.container.jdisc.AclMapping$Action update", + "public static final enum com.yahoo.container.jdisc.AclMapping$Action delete" + ] + }, + "com.yahoo.container.jdisc.AclMapping": { + "superClass": "java.lang.Object", + "interfaces": [], + "attributes": [ + "public", + "interface", + "abstract" + ], + "methods": [ + "public abstract com.yahoo.container.jdisc.AclMapping$Action get(com.yahoo.container.jdisc.RequestView)" + ], + "fields": [] + }, "com.yahoo.container.jdisc.AsyncHttpResponse": { "superClass": "com.yahoo.container.jdisc.HttpResponse", "interfaces": [], @@ -515,6 +547,20 @@ ], "fields": [] }, + "com.yahoo.container.jdisc.DefaultAclMapping": { + "superClass": "java.lang.Object", + "interfaces": [ + "com.yahoo.container.jdisc.AclMapping" + ], + "attributes": [ + "public" + ], + "methods": [ + "public void <init>()", + "public com.yahoo.container.jdisc.AclMapping$Action get(com.yahoo.container.jdisc.RequestView)" + ], + "fields": [] + }, "com.yahoo.container.jdisc.EmptyResponse": { "superClass": "com.yahoo.container.jdisc.HttpResponse", "interfaces": [], @@ -689,6 +735,34 @@ ], "fields": [] }, + "com.yahoo.container.jdisc.RequestHandlerSpec$Builder": { + "superClass": "java.lang.Object", + "interfaces": [], + "attributes": [ + "public" + ], + "methods": [ + "public void <init>()", + "public com.yahoo.container.jdisc.RequestHandlerSpec$Builder withAclMapping(com.yahoo.container.jdisc.AclMapping)", + "public com.yahoo.container.jdisc.RequestHandlerSpec build()" + ], + "fields": [] + }, + "com.yahoo.container.jdisc.RequestHandlerSpec": { + "superClass": "java.lang.Object", + "interfaces": [], + "attributes": [ + "public" + ], + "methods": [ + "public com.yahoo.container.jdisc.AclMapping aclMapping()", + "public static com.yahoo.container.jdisc.RequestHandlerSpec$Builder builder()" + ], + "fields": [ + "public static final java.lang.String ATTRIBUTE_NAME", + "public static final com.yahoo.container.jdisc.RequestHandlerSpec DEFAULT_INSTANCE" + ] + }, "com.yahoo.container.jdisc.RequestHandlerTestDriver$MockResponseHandler": { "superClass": "java.lang.Object", "interfaces": [ @@ -734,6 +808,35 @@ ], "fields": [] }, + "com.yahoo.container.jdisc.RequestHandlerWithSpec": { + "superClass": "java.lang.Object", + "interfaces": [ + "com.yahoo.jdisc.handler.RequestHandler" + ], + "attributes": [ + "public", + "interface", + "abstract" + ], + "methods": [ + "public com.yahoo.container.jdisc.RequestHandlerSpec requestHandlerSpec()" + ], + "fields": [] + }, + "com.yahoo.container.jdisc.RequestView": { + "superClass": "java.lang.Object", + "interfaces": [], + "attributes": [ + "public", + "interface", + "abstract" + ], + "methods": [ + "public abstract com.yahoo.jdisc.http.HttpRequest$Method method()", + "public abstract java.net.URI uri()" + ], + "fields": [] + }, "com.yahoo.container.jdisc.ThreadedHttpRequestHandler$LazyContentChannel": { "superClass": "java.lang.Object", "interfaces": [ @@ -775,7 +878,9 @@ }, "com.yahoo.container.jdisc.ThreadedRequestHandler": { "superClass": "com.yahoo.jdisc.handler.AbstractRequestHandler", - "interfaces": [], + "interfaces": [ + "com.yahoo.container.jdisc.RequestHandlerWithSpec" + ], "attributes": [ "public", "abstract" @@ -2019,6 +2124,7 @@ "public abstract void setHeaders(java.lang.String, java.lang.String)", "public abstract void setHeaders(java.lang.String, java.util.List)", "public int getIntHeader(java.lang.String)", + "public com.yahoo.container.jdisc.RequestView asRequestView()", "public java.util.List getCookies()", "public void setCookies(java.util.List)", "public long getConnectedAt(java.util.concurrent.TimeUnit)", diff --git a/container-core/src/main/java/com/yahoo/container/jdisc/AclMapping.java b/container-core/src/main/java/com/yahoo/container/jdisc/AclMapping.java new file mode 100644 index 00000000000..8c7701a7534 --- /dev/null +++ b/container-core/src/main/java/com/yahoo/container/jdisc/AclMapping.java @@ -0,0 +1,14 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package com.yahoo.container.jdisc; + +/** + * Mapping from request to action + * + * @author mortent + */ +public interface AclMapping { + enum Action {create, read, update, delete}; + + Action get(RequestView requestView); +} diff --git a/container-core/src/main/java/com/yahoo/container/jdisc/DefaultAclMapping.java b/container-core/src/main/java/com/yahoo/container/jdisc/DefaultAclMapping.java new file mode 100644 index 00000000000..0a996cb5967 --- /dev/null +++ b/container-core/src/main/java/com/yahoo/container/jdisc/DefaultAclMapping.java @@ -0,0 +1,31 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package com.yahoo.container.jdisc; + +/** + * Default ACL mapping + * @author mortent + */ +public class DefaultAclMapping implements AclMapping { + + @Override + public Action get(RequestView requestMeta) { + switch (requestMeta.method()) { + case GET: + case HEAD: + case OPTIONS: + return Action.read; + case POST: + return Action.create; + case DELETE: + return Action.delete; + case PUT: + case PATCH: + case CONNECT: + case TRACE: + return Action.update; + default: + throw new IllegalArgumentException("Illegal request method: " + requestMeta.method()); + } + } +} diff --git a/container-core/src/main/java/com/yahoo/container/jdisc/RequestHandlerSpec.java b/container-core/src/main/java/com/yahoo/container/jdisc/RequestHandlerSpec.java new file mode 100644 index 00000000000..91fac9ac448 --- /dev/null +++ b/container-core/src/main/java/com/yahoo/container/jdisc/RequestHandlerSpec.java @@ -0,0 +1,46 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package com.yahoo.container.jdisc; + +import java.util.Objects; + +/** + * A specification provided by a request handler. + * Available through request context attribute + * + * @author mortent + */ +public class RequestHandlerSpec { + + public static final String ATTRIBUTE_NAME = RequestHandlerSpec.class.getName(); + public static final RequestHandlerSpec DEFAULT_INSTANCE = RequestHandlerSpec.builder().build(); + + private final AclMapping aclMapping; + + private RequestHandlerSpec(AclMapping aclMapping) { + this.aclMapping = aclMapping; + } + + public AclMapping aclMapping() { + return aclMapping; + } + + public static Builder builder(){ + return new Builder(); + } + + public static class Builder { + + private AclMapping aclMapping = new DefaultAclMapping(); + + public Builder withAclMapping(AclMapping aclMapping) { + this.aclMapping = Objects.requireNonNull(aclMapping); + return this; + } + + public RequestHandlerSpec build() { + return new RequestHandlerSpec(aclMapping); + } + } +} + diff --git a/container-core/src/main/java/com/yahoo/container/jdisc/RequestHandlerWithSpec.java b/container-core/src/main/java/com/yahoo/container/jdisc/RequestHandlerWithSpec.java new file mode 100644 index 00000000000..89107195671 --- /dev/null +++ b/container-core/src/main/java/com/yahoo/container/jdisc/RequestHandlerWithSpec.java @@ -0,0 +1,21 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package com.yahoo.container.jdisc; + +import com.yahoo.jdisc.handler.RequestHandler; + +/** + * Extends a request handler with a request handler specification. + * + * @author mortent + */ +public interface RequestHandlerWithSpec extends RequestHandler { + + /** + * + * @return handler specification + */ + default RequestHandlerSpec requestHandlerSpec() { + return RequestHandlerSpec.DEFAULT_INSTANCE; + } +} diff --git a/container-core/src/main/java/com/yahoo/container/jdisc/RequestView.java b/container-core/src/main/java/com/yahoo/container/jdisc/RequestView.java new file mode 100644 index 00000000000..51a5fdc8959 --- /dev/null +++ b/container-core/src/main/java/com/yahoo/container/jdisc/RequestView.java @@ -0,0 +1,18 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package com.yahoo.container.jdisc; + +import com.yahoo.jdisc.http.HttpRequest; + +import java.net.URI; + +/** + * Read-only view of the request + * + * @author mortent + */ +public interface RequestView { + HttpRequest.Method method(); + + URI uri(); +} diff --git a/container-core/src/main/java/com/yahoo/container/jdisc/ThreadedRequestHandler.java b/container-core/src/main/java/com/yahoo/container/jdisc/ThreadedRequestHandler.java index 446ee90c205..0b05f58f75e 100644 --- a/container-core/src/main/java/com/yahoo/container/jdisc/ThreadedRequestHandler.java +++ b/container-core/src/main/java/com/yahoo/container/jdisc/ThreadedRequestHandler.java @@ -35,7 +35,7 @@ import java.util.logging.Logger; * * @author Simon Thoresen Hult */ -public abstract class ThreadedRequestHandler extends AbstractRequestHandler { +public abstract class ThreadedRequestHandler extends AbstractRequestHandler implements RequestHandlerWithSpec { private static final Logger log = Logger.getLogger(ThreadedRequestHandler.class.getName()); private static final Duration TIMEOUT = Duration.ofSeconds(Integer.parseInt(System.getProperty("ThreadedRequestHandler.timeout", "300"))); diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterRequest.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterRequest.java index f7ab399574c..72068bd2dd5 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterRequest.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterRequest.java @@ -1,6 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.jdisc.http.filter; +import com.yahoo.container.jdisc.RequestView; import com.yahoo.jdisc.HeaderFields; import com.yahoo.jdisc.http.Cookie; import com.yahoo.jdisc.http.HttpHeaders; @@ -254,6 +255,19 @@ public abstract class DiscFilterRequest { } } + public RequestView asRequestView() { + return new RequestView() { + @Override + public HttpRequest.Method method() { + return HttpRequest.Method.valueOf(getMethod()); + } + + @Override + public URI uri() { + return getUri(); + } + }; + } public List<Cookie> getCookies() { return parent.decodeCookieHeader(); diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/FilteringRequestHandler.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/FilteringRequestHandler.java index a487b63ef10..0d94701f794 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/FilteringRequestHandler.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/FilteringRequestHandler.java @@ -2,6 +2,8 @@ package com.yahoo.jdisc.http.server.jetty; import com.google.common.base.Preconditions; +import com.yahoo.container.jdisc.RequestHandlerSpec; +import com.yahoo.container.jdisc.RequestHandlerWithSpec; import com.yahoo.jdisc.Request; import com.yahoo.jdisc.Response; import com.yahoo.jdisc.handler.AbstractRequestHandler; @@ -65,6 +67,11 @@ class FilteringRequestHandler extends AbstractRequestHandler { throw new BindingNotFoundException(request.getUri()); } + if (resolvedRequestHandler instanceof RequestHandlerWithSpec) { + RequestHandlerSpec requestHandlerSpec = ((RequestHandlerWithSpec) resolvedRequestHandler).requestHandlerSpec(); + request.context().put(RequestHandlerSpec.ATTRIBUTE_NAME, requestHandlerSpec); + } + RequestHandler requestHandler = new ReferenceCountingRequestHandler(resolvedRequestHandler); ResponseHandler responseHandler; diff --git a/container-search/abi-spec.json b/container-search/abi-spec.json index 75b8814ecb0..9e5963e5152 100644 --- a/container-search/abi-spec.json +++ b/container-search/abi-spec.json @@ -4235,7 +4235,8 @@ "public com.yahoo.search.Result searchAndFill(com.yahoo.search.Query, com.yahoo.component.chain.Chain)", "public com.yahoo.processing.rendering.Renderer getRendererCopy(com.yahoo.component.ComponentSpecification)", "public com.yahoo.search.searchchain.SearchChainRegistry getSearchChainRegistry()", - "public void createRequestMapping(com.yahoo.slime.Inspector, java.util.Map, java.lang.String)" + "public void createRequestMapping(com.yahoo.slime.Inspector, java.util.Map, java.lang.String)", + "public com.yahoo.container.jdisc.RequestHandlerSpec requestHandlerSpec()" ], "fields": [ "public static final java.lang.String defaultSearchChainName" diff --git a/container-search/src/main/java/com/yahoo/search/handler/SearchHandler.java b/container-search/src/main/java/com/yahoo/search/handler/SearchHandler.java index 2b27f60ef73..e95e085ed5c 100644 --- a/container-search/src/main/java/com/yahoo/search/handler/SearchHandler.java +++ b/container-search/src/main/java/com/yahoo/search/handler/SearchHandler.java @@ -14,11 +14,14 @@ import com.yahoo.container.handler.threadpool.ContainerThreadPool; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.container.jdisc.LoggingRequestHandler; +import com.yahoo.container.jdisc.RequestHandlerSpec; import com.yahoo.container.jdisc.VespaHeaders; import com.yahoo.container.logging.AccessLog; import com.yahoo.io.IOUtils; import com.yahoo.jdisc.Metric; import com.yahoo.jdisc.Request; +import com.yahoo.container.jdisc.AclMapping; +import com.yahoo.container.jdisc.RequestView; import com.yahoo.language.Linguistics; import com.yahoo.net.HostName; import com.yahoo.net.UriTools; @@ -103,6 +106,8 @@ public class SearchHandler extends LoggingRequestHandler { private final AtomicLong numRequestsLeftToTrace; + private final static RequestHandlerSpec REQUEST_HANDLER_SPEC = RequestHandlerSpec.builder().withAclMapping(SearchHandler::mapRequestToAction).build(); + private final class MeanConnections implements Callback { @Override @@ -631,6 +636,26 @@ public class SearchHandler extends LoggingRequestHandler { }); } + @Override + public RequestHandlerSpec requestHandlerSpec() { + return REQUEST_HANDLER_SPEC; + } + + private static AclMapping.Action mapRequestToAction(RequestView requestMeta) { + switch (requestMeta.method()){ + case GET: + case POST: + case HEAD: + case OPTIONS: + return AclMapping.Action.read; + case PUT: + return AclMapping.Action.update; + case DELETE: + return AclMapping.Action.delete; + default: + return AclMapping.Action.create; + } + } } diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java index c7c2a1672f8..9ce2c6171a6 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java @@ -260,7 +260,6 @@ public class Flags { "Whether to wait for all participating servers to delete application on config servers (with timeout) on", "Takes effect on next delete of an application"); - public static final UnboundBooleanFlag REBUILD_HOST = defineFeatureFlag( "rebuild-host", false, List.of("mpolden"), "2021-04-09", "2021-06-01", @@ -274,6 +273,13 @@ public class Flags { "Takes effect at redeployment", APPLICATION_ID); + public static final UnboundBooleanFlag ENABLE_CUSTOM_ACL_MAPPING = defineFeatureFlag( + "enable-custom-acl-mapping", false, + List.of("mortent","bjorncs"), "2021-04-13", "2021-08-01", + "Whether access control filters should read acl request mapping from handler or use default", + "Takes effect at redeployment", + APPLICATION_ID); + public static final UnboundBooleanFlag UPGRADE_DELL_SSD_FIRMWARE = defineFeatureFlag( "upgrade_dell_ssd_firmware", false, List.of("andreer"), "2021-04-13", "2021-05-13", diff --git a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzAuthorizationFilter.java b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzAuthorizationFilter.java index 56dd3bcbf5b..dd4b62ee494 100644 --- a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzAuthorizationFilter.java +++ b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzAuthorizationFilter.java @@ -83,7 +83,7 @@ public class AthenzAuthorizationFilter extends JsonSecurityRequestFilterBase { public Optional<ErrorResponse> filter(DiscFilterRequest request) { try { Optional<ResourceNameAndAction> resourceMapping = - requestResourceMapper.getResourceNameAndAction(request.getMethod(), request.getRequestURI(), request.getQueryString()); + requestResourceMapper.getResourceNameAndAction(request); log.log(Level.FINE, () -> String.format("Resource mapping for '%s': %s", request, resourceMapping)); if (resourceMapping.isEmpty()) { incrementAcceptedMetrics(request, false); diff --git a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/RequestResourceMapper.java b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/RequestResourceMapper.java index 0bf000efc00..56c52bd71c4 100644 --- a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/RequestResourceMapper.java +++ b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/RequestResourceMapper.java @@ -1,6 +1,7 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.jdisc.http.filter.security.athenz; +import com.yahoo.jdisc.http.filter.DiscFilterRequest; import com.yahoo.vespa.athenz.api.AthenzResourceName; import java.util.Optional; @@ -17,6 +18,13 @@ public interface RequestResourceMapper { */ Optional<ResourceNameAndAction> getResourceNameAndAction(String method, String uriPath, String uriQuery); + /** + * @return A resource name + action to use for access control, empty if no access control should be performed. + */ + default Optional<ResourceNameAndAction> getResourceNameAndAction(DiscFilterRequest request) { + return getResourceNameAndAction(request.getMethod(), request.getRequestURI(), request.getQueryString()); + } + class ResourceNameAndAction { private final AthenzResourceName resourceName; private final String action; |