From 30633fe895a45a8772ce53bee594868b96982ac8 Mon Sep 17 00:00:00 2001 From: gjoranv Date: Wed, 22 Feb 2017 15:32:54 +0100 Subject: Support excluding access control bindings for servlets and rest-apis. --- .../config/model/test/ConfigModelTestUtil.java | 5 +- .../vespa/model/container/ContainerCluster.java | 5 ++ .../vespa/model/container/component/Servlet.java | 2 +- .../vespa/model/container/http/AccessControl.java | 37 ++++++++- .../model/container/http/xml/HttpBuilder.java | 8 +- .../model/container/jersey/Jersey2Servlet.java | 3 +- .../model/container/xml/AccessControlTest.java | 88 ++++++++++++++++++++-- 7 files changed, 131 insertions(+), 17 deletions(-) (limited to 'config-model') 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>)(Collection)componentGroup.getComponents(Handler.class); } + // Returns all servlets, including rest-api/jersey servlets. + public Collection getAllServlets() { + return allServlets().collect(Collectors.toCollection(ArrayList::new)); + } + public Map> 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 excludeBindings = new LinkedHashSet<>(); private Collection> handlers = Collections.emptyList(); + private Collection 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 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 vespaDomain; private final Set excludedBindings; private final Collection> handlers; + private final Collection servlets; private AccessControl(String domain, String applicationId, @@ -93,6 +103,7 @@ public final class AccessControl { boolean readEnabled, Set excludedBindings, Optional vespaDomain, + Collection servlets, Collection> 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 getBindings() { + return Stream.concat(getHandlerBindings(), getServletBindings()) + .collect(Collectors.toCollection(ArrayList::new)); + } + + private Stream getHandlerBindings() { return handlers.stream() .filter(this::shouldHandlerBeProtected) .flatMap(handler -> handler.getServerBindings().stream()) - .map(AccessControl::accessControlBinding) - .collect(Collectors.toList()); + .map(AccessControl::accessControlBinding); + } + + private Stream 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 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 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( "", + httpWithExcludedBinding(excludedBinding), " ", " " + notExcludedBinding + "", " " + excludedBinding + "", " ", + ""); + + 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 requiredBindings = ImmutableSet.of(servletPath, restApiPath); + Element clusterElem = DomBuilderTest.parse( + "", + " ", + " " + servletPath + "", + " ", + " ", " ", " ", - " ", - " ", - " " + excludedBinding + "", - " ", - " ", + " ", " ", " ", ""); + Http http = getHttp(clusterElem); + + Set 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( + "", + httpWithExcludedBinding(excludedBinding), + " ", + " " + servletPath + "", + " ", + ""); + 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( + "", + httpWithExcludedBinding(excludedBinding), + " ", + ""); + + 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( + " ", + " ", + " ", + " ", + " " + excludedBinding + "", + " ", + " ", + " ", + " "); + } + 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; } - } -- cgit v1.2.3