diff options
author | gjoranv <gv@yahoo-inc.com> | 2017-02-22 15:32:54 +0100 |
---|---|---|
committer | gjoranv <gv@yahoo-inc.com> | 2017-02-22 15:32:54 +0100 |
commit | 30633fe895a45a8772ce53bee594868b96982ac8 (patch) | |
tree | 468482518f2dc55bda7e21471cccb040861fc86f /config-model | |
parent | 9112dca9392e634f49cb27e502d58002f32abdde (diff) |
Support excluding access control bindings for servlets and rest-apis.
Diffstat (limited to 'config-model')
7 files changed, 131 insertions, 17 deletions
diff --git a/config-model/src/main/java/com/yahoo/config/model/test/ConfigModelTestUtil.java b/config-model/src/main/java/com/yahoo/config/model/test/ConfigModelTestUtil.java index b75147774ab..f9ed7d329d3 100644 --- a/config-model/src/main/java/com/yahoo/config/model/test/ConfigModelTestUtil.java +++ b/config-model/src/main/java/com/yahoo/config/model/test/ConfigModelTestUtil.java @@ -33,8 +33,11 @@ public class ConfigModelTestUtil { } } + public static String joinLines(CharSequence... lines) { + return String.join("\n", lines); + } + private static InputSource inputSource(String str) { return new InputSource(new StringReader(str)); } - } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java index 993798789a4..6015aaaaa20 100755 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java @@ -511,6 +511,11 @@ public final class ContainerCluster return (Collection<Handler<?>>)(Collection)componentGroup.getComponents(Handler.class); } + // Returns all servlets, including rest-api/jersey servlets. + public Collection<Servlet> getAllServlets() { + return allServlets().collect(Collectors.toCollection(ArrayList::new)); + } + public Map<ComponentId, Component<?, ?>> getComponentsMap() { return componentGroup.getComponentMap(); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/component/Servlet.java b/config-model/src/main/java/com/yahoo/vespa/model/container/component/Servlet.java index 68ba3436209..c321ce84669 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/component/Servlet.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/component/Servlet.java @@ -9,7 +9,7 @@ import com.yahoo.osgi.provider.model.ComponentModel; * @author stiankri */ public class Servlet extends SimpleComponent { - private final String bindingPath; + public final String bindingPath; public Servlet(ComponentModel componentModel, String bindingPath) { super(componentModel); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/AccessControl.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/AccessControl.java index 0b8b560382c..a2cc89092a8 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/AccessControl.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/AccessControl.java @@ -7,8 +7,10 @@ import com.yahoo.component.ComponentSpecification; import com.yahoo.vespa.model.container.ContainerCluster; import com.yahoo.vespa.model.container.component.FileStatusHandlerComponent; import com.yahoo.vespa.model.container.component.Handler; +import com.yahoo.vespa.model.container.component.Servlet; import com.yahoo.vespa.model.container.http.Http.Binding; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; @@ -16,6 +18,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * Helper class for http access control. @@ -42,6 +45,7 @@ public final class AccessControl { private boolean writeEnabled = true; private final Set<String> excludeBindings = new LinkedHashSet<>(); private Collection<Handler<?>> handlers = Collections.emptyList(); + private Collection<Servlet> servlets = Collections.emptyList(); public Builder(String domain, String applicationId) { this.domain = domain; @@ -73,9 +77,14 @@ public final class AccessControl { return this; } + public Builder setServlets(Collection<Servlet> servlets) { + this.servlets = servlets; + return this; + } + public AccessControl build() { return new AccessControl(domain, applicationId, writeEnabled, readEnabled, - excludeBindings, vespaDomain, handlers); + excludeBindings, vespaDomain, servlets, handlers); } } @@ -86,6 +95,7 @@ public final class AccessControl { public final Optional<String> vespaDomain; private final Set<String> excludedBindings; private final Collection<Handler<?>> handlers; + private final Collection<Servlet> servlets; private AccessControl(String domain, String applicationId, @@ -93,6 +103,7 @@ public final class AccessControl { boolean readEnabled, Set<String> excludedBindings, Optional<String> vespaDomain, + Collection<Servlet> servlets, Collection<Handler<?>> handlers) { this.domain = domain; this.applicationId = applicationId; @@ -101,14 +112,26 @@ public final class AccessControl { this.excludedBindings = Collections.unmodifiableSet(excludedBindings); this.vespaDomain = vespaDomain; this.handlers = handlers; + this.servlets = servlets; } public List<Binding> getBindings() { + return Stream.concat(getHandlerBindings(), getServletBindings()) + .collect(Collectors.toCollection(ArrayList::new)); + } + + private Stream<Binding> getHandlerBindings() { return handlers.stream() .filter(this::shouldHandlerBeProtected) .flatMap(handler -> handler.getServerBindings().stream()) - .map(AccessControl::accessControlBinding) - .collect(Collectors.toList()); + .map(AccessControl::accessControlBinding); + } + + private Stream<Binding> getServletBindings() { + return servlets.stream() + .filter(this::shouldServletBeProtected) + .flatMap(AccessControl::servletBindings) + .map(AccessControl::accessControlBinding); } private boolean shouldHandlerBeProtected(Handler<?> handler) { @@ -116,7 +139,15 @@ public final class AccessControl { && handler.getServerBindings().stream().noneMatch(excludedBindings::contains); } + private boolean shouldServletBeProtected(Servlet servlet) { + return servletBindings(servlet).noneMatch(excludedBindings::contains); + } + private static Binding accessControlBinding(String binding) { return new Binding(new ComponentSpecification(ACCESS_CONTROL_CHAIN_ID.stringValue()), binding); } + + private static Stream<String> servletBindings(Servlet servlet) { + return Stream.of("http://*/", "https://*/").map(protocol -> protocol + servlet.bindingPath); + } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/HttpBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/HttpBuilder.java index 70246584cbf..e3847e4c7bc 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/HttpBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/HttpBuilder.java @@ -20,7 +20,6 @@ import com.yahoo.vespa.model.container.http.Http.Binding; import org.w3c.dom.Element; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Optional; @@ -67,9 +66,10 @@ public class HttpBuilder extends VespaDomBuilder.DomConfigProducerBuilder<Http> AccessControl.Builder builder = new AccessControl.Builder(accessControlElem.getAttribute("domain"), application); - builder.setHandlers(getContainerCluster(ancestor) - .map(ContainerCluster::getHandlers) - .orElse(Collections.emptyList())); + getContainerCluster(ancestor).ifPresent(cluster -> { + builder.setHandlers(cluster.getHandlers()); + builder.setServlets(cluster.getAllServlets()); + }); XmlHelper.getOptionalAttribute(accessControlElem, "read").ifPresent( readAttr -> builder.readEnabled(Boolean.valueOf(readAttr))); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/jersey/Jersey2Servlet.java b/config-model/src/main/java/com/yahoo/vespa/model/container/jersey/Jersey2Servlet.java index d7c9482cab4..8c01943484c 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/jersey/Jersey2Servlet.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/jersey/Jersey2Servlet.java @@ -15,6 +15,7 @@ public class Jersey2Servlet extends Servlet { public static final String BUNDLE = "container-jersey2"; public static final String CLASS = "com.yahoo.container.servlet.jersey.JerseyServletProvider"; + public static final String BINDING_SUFFIX = "/*"; private static final ComponentId REST_API_NAMESPACE = ComponentId.fromString("rest-api"); @@ -23,7 +24,7 @@ public class Jersey2Servlet extends Servlet { new BundleInstantiationSpecification(idSpecFromPath(bindingPath), ComponentSpecification.fromString(CLASS), ComponentSpecification.fromString(BUNDLE))), - bindingPath + "/*"); + bindingPath + BINDING_SUFFIX); } private static ComponentSpecification idSpecFromPath(String path) { diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessControlTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessControlTest.java index c02e333c5fc..9c20ebe3a18 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessControlTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessControlTest.java @@ -4,12 +4,14 @@ package com.yahoo.vespa.model.container.xml; import com.google.common.collect.ImmutableSet; import com.yahoo.collections.CollectionUtil; import com.yahoo.config.model.builder.xml.test.DomBuilderTest; +import com.yahoo.config.model.test.ConfigModelTestUtil; import com.yahoo.container.jdisc.state.StateHandler; import com.yahoo.vespa.model.container.ContainerCluster; import com.yahoo.vespa.model.container.http.AccessControl; import com.yahoo.vespa.model.container.http.Http; import com.yahoo.vespa.model.container.http.Http.Binding; import com.yahoo.vespa.model.container.http.xml.HttpBuilder; +import com.yahoo.vespa.model.container.jersey.Jersey2Servlet; import org.junit.Test; import org.w3c.dom.Element; import org.xml.sax.SAXException; @@ -154,29 +156,102 @@ public class AccessControlTest extends ContainerModelBuilderTestBase { final String excludedBinding = "http://*/excluded/*"; Element clusterElem = DomBuilderTest.parse( "<jdisc version='1.0'>", + httpWithExcludedBinding(excludedBinding), " <handler id='custom.Handler'>", " <binding>" + notExcludedBinding + "</binding>", " <binding>" + excludedBinding + "</binding>", " </handler>", + "</jdisc>"); + + Http http = getHttp(clusterElem); + assertFalse("Excluded binding was not removed.", + containsBinding(http.getBindings(), excludedBinding)); + assertFalse("Not all bindings of an excluded handler were removed.", + containsBinding(http.getBindings(), notExcludedBinding)); + + } + + @Test + public void access_control_filter_chain_has_all_servlet_bindings() throws Exception { + final String servletPath = "servlet/path"; + final String restApiPath = "api/v0"; + final Set<String> requiredBindings = ImmutableSet.of(servletPath, restApiPath); + Element clusterElem = DomBuilderTest.parse( + "<jdisc version='1.0'>", + " <servlet id='foo' class='bar' bundle='baz'>", + " <path>" + servletPath + "</path>", + " </servlet>", + " <rest-api jersey2='true' path='" + restApiPath + "' />", " <http>", " <filtering>", - " <access-control domain='foo'>", - " <exclude>", - " <binding>" + excludedBinding + "</binding>", - " </exclude>", - " </access-control>", + " <access-control domain='foo' />", " </filtering>", " </http>", "</jdisc>"); Http http = getHttp(clusterElem); + + Set<String> missingRequiredBindings = requiredBindings.stream() + .filter(requiredBinding -> ! containsBinding(http.getBindings(), requiredBinding)) + .collect(Collectors.toSet()); + + assertTrue("Access control chain was not bound to: " + CollectionUtil.mkString(missingRequiredBindings, ", "), + missingRequiredBindings.isEmpty()); + } + + @Test + public void servlet_can_be_excluded_by_excluding_one_of_its_bindings() throws Exception { + final String servletPath = "servlet/path"; + final String notExcludedBinding = "https://*/" + servletPath; + final String excludedBinding = "http://*/" + servletPath; + Element clusterElem = DomBuilderTest.parse( + "<jdisc version='1.0'>", + httpWithExcludedBinding(excludedBinding), + " <servlet id='foo' class='bar' bundle='baz'>", + " <path>" + servletPath + "</path>", + " </servlet>", + "</jdisc>"); + + Http http = getHttp(clusterElem); assertFalse("Excluded binding was not removed.", containsBinding(http.getBindings(), excludedBinding)); - assertFalse("Not all bindings of an excluded handler was removed.", + assertFalse("Not all bindings of an excluded servlet were removed.", containsBinding(http.getBindings(), notExcludedBinding)); } + @Test + public void rest_api_can_be_excluded_by_excluding_one_of_its_bindings() throws Exception { + final String restApiPath = "api/v0"; + final String notExcludedBinding = "http://*/" + restApiPath + Jersey2Servlet.BINDING_SUFFIX;; + final String excludedBinding = "https://*/" + restApiPath + Jersey2Servlet.BINDING_SUFFIX;; + Element clusterElem = DomBuilderTest.parse( + "<jdisc version='1.0'>", + httpWithExcludedBinding(excludedBinding), + " <rest-api jersey2='true' path='" + restApiPath + "' />", + "</jdisc>"); + + Http http = getHttp(clusterElem); + assertFalse("Excluded binding was not removed.", + containsBinding(http.getBindings(), excludedBinding)); + assertFalse("Not all bindings of an excluded rest-api were removed.", + containsBinding(http.getBindings(), notExcludedBinding)); + + } + + private String httpWithExcludedBinding(String excludedBinding) { + return ConfigModelTestUtil.joinLines( + " <http>", + " <filtering>", + " <access-control domain='foo'>", + " <exclude>", + " <binding>" + excludedBinding + "</binding>", + " </exclude>", + " </access-control>", + " </filtering>", + " </http>"); + } + private Http getHttp(Element clusterElem) throws SAXException, IOException { createModel(root, clusterElem); ContainerCluster cluster = (ContainerCluster) root.getChildren().get("jdisc"); @@ -192,5 +267,4 @@ public class AccessControlTest extends ContainerModelBuilderTestBase { } return false; } - } |