diff options
author | Morten Tokle <mortent@yahooinc.com> | 2022-09-12 09:35:36 +0200 |
---|---|---|
committer | Morten Tokle <mortent@yahooinc.com> | 2022-09-12 09:35:36 +0200 |
commit | 4f8bccce3a80181c5e745acedca0accc8bfa3d68 (patch) | |
tree | 1696a3bcd7b9a82b245024c51fae6f950af5bd25 /config-model | |
parent | aa436622a3e954d9da14847339a8ca1055f1a5d1 (diff) |
Restrict data plane bindings
Diffstat (limited to 'config-model')
7 files changed, 196 insertions, 36 deletions
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomHandlerBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomHandlerBuilder.java index 7bfe971981e..dde38544924 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomHandlerBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomHandlerBuilder.java @@ -14,6 +14,7 @@ import com.yahoo.vespa.model.container.component.UserBindingPattern; import com.yahoo.vespa.model.container.xml.BundleInstantiationSpecificationBuilder; import org.w3c.dom.Element; +import java.util.OptionalInt; import java.util.Set; import static com.yahoo.vespa.model.container.ApplicationContainerCluster.METRICS_V2_HANDLER_BINDING_1; @@ -36,23 +37,38 @@ public class DomHandlerBuilder extends VespaDomBuilder.DomConfigProducerBuilder< VIP_HANDLER_BINDING); private final ApplicationContainerCluster cluster; + private OptionalInt portBindingOverride; public DomHandlerBuilder(ApplicationContainerCluster cluster) { + this(cluster, OptionalInt.empty()); + } + public DomHandlerBuilder(ApplicationContainerCluster cluster, OptionalInt portBindingOverride) { this.cluster = cluster; + this.portBindingOverride = portBindingOverride; } @Override protected Handler doBuild(DeployState deployState, AbstractConfigProducer<?> parent, Element handlerElement) { Handler handler = createHandler(handlerElement); + OptionalInt port = portBindingOverride.isPresent() && deployState.isHosted() && deployState.featureFlags().useRestrictedDataPlaneBindings() + ? portBindingOverride + : OptionalInt.empty(); for (Element binding : XML.getChildren(handlerElement, "binding")) - addServerBinding(handler, UserBindingPattern.fromPattern(XML.getValue(binding)), deployState.getDeployLogger()); + addServerBinding(handler, userBindingPattern(XML.getValue(binding), port), deployState.getDeployLogger()); DomComponentBuilder.addChildren(deployState, parent, handlerElement, handler); return handler; } + private static UserBindingPattern userBindingPattern(String path, OptionalInt port) { + UserBindingPattern bindingPattern = UserBindingPattern.fromPattern(path); + return port.isPresent() + ? bindingPattern.withPort(port.getAsInt()) + : bindingPattern; + } + Handler createHandler(Element handlerElement) { BundleInstantiationSpecification bundleSpec = BundleInstantiationSpecificationBuilder.build(handlerElement); return new Handler(new ComponentModel(bundleSpec)); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/clients/ContainerDocumentApi.java b/config-model/src/main/java/com/yahoo/vespa/model/clients/ContainerDocumentApi.java index f23f27c0d8e..8163c268d09 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/clients/ContainerDocumentApi.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/clients/ContainerDocumentApi.java @@ -6,6 +6,7 @@ import com.yahoo.osgi.provider.model.ComponentModel; import com.yahoo.vespa.model.container.ContainerCluster; import com.yahoo.vespa.model.container.ContainerThreadpool; import com.yahoo.vespa.model.container.PlatformBundles; +import com.yahoo.vespa.model.container.component.BindingPattern; import com.yahoo.vespa.model.container.component.Handler; import com.yahoo.vespa.model.container.component.SystemBindingPattern; import com.yahoo.vespa.model.container.component.UserBindingPattern; @@ -13,6 +14,7 @@ import com.yahoo.vespa.model.container.component.UserBindingPattern; import java.nio.file.Path; import java.util.Collection; import java.util.Collections; +import java.util.OptionalInt; /** * @author Einar M R Rosenvinge @@ -26,10 +28,10 @@ public class ContainerDocumentApi { private final boolean ignoreUndefinedFields; - public ContainerDocumentApi(ContainerCluster<?> cluster, HandlerOptions handlerOptions, boolean ignoreUndefinedFields) { + public ContainerDocumentApi(ContainerCluster<?> cluster, HandlerOptions handlerOptions, boolean ignoreUndefinedFields, OptionalInt portOverride) { this.ignoreUndefinedFields = ignoreUndefinedFields; - addRestApiHandler(cluster, handlerOptions); - addFeedHandler(cluster, handlerOptions); + addRestApiHandler(cluster, handlerOptions, portOverride); + addFeedHandler(cluster, handlerOptions, portOverride); addVespaClientContainerBundle(cluster); } @@ -37,18 +39,18 @@ public class ContainerDocumentApi { c.addPlatformBundle(VESPACLIENT_CONTAINER_BUNDLE); } - private static void addFeedHandler(ContainerCluster<?> cluster, HandlerOptions handlerOptions) { + private static void addFeedHandler(ContainerCluster<?> cluster, HandlerOptions handlerOptions, OptionalInt portOverride) { String bindingSuffix = ContainerCluster.RESERVED_URI_PREFIX + "/feedapi"; var executor = new Threadpool("feedapi-handler", handlerOptions.feedApiThreadpoolOptions); var handler = newVespaClientHandler("com.yahoo.vespa.http.server.FeedHandler", - bindingSuffix, handlerOptions, executor); + bindingSuffix, handlerOptions, executor, portOverride); cluster.addComponent(handler); } - private static void addRestApiHandler(ContainerCluster<?> cluster, HandlerOptions handlerOptions) { + private static void addRestApiHandler(ContainerCluster<?> cluster, HandlerOptions handlerOptions, OptionalInt portOverride) { var handler = newVespaClientHandler("com.yahoo.document.restapi.resource.DocumentV1ApiHandler", - DOCUMENT_V1_PREFIX + "/*", handlerOptions, null); + DOCUMENT_V1_PREFIX + "/*", handlerOptions, null, portOverride); cluster.addComponent(handler); // We need to include a dummy implementation of the previous restapi handler (using the same class name). @@ -62,23 +64,37 @@ public class ContainerDocumentApi { private static Handler newVespaClientHandler(String componentId, String bindingSuffix, HandlerOptions handlerOptions, - Threadpool executor) { + Threadpool executor, + OptionalInt portOverride) { Handler handler = createHandler(componentId, executor); if (handlerOptions.bindings.isEmpty()) { handler.addServerBindings( - SystemBindingPattern.fromHttpPath(bindingSuffix), - SystemBindingPattern.fromHttpPath(bindingSuffix + '/')); + bindingPattern(bindingSuffix, portOverride), + bindingPattern(bindingSuffix + '/', portOverride)); } else { for (String rootBinding : handlerOptions.bindings) { String pathWithoutLeadingSlash = bindingSuffix.substring(1); handler.addServerBindings( - UserBindingPattern.fromPattern(rootBinding + pathWithoutLeadingSlash), - UserBindingPattern.fromPattern(rootBinding + pathWithoutLeadingSlash + '/')); + userBindingPattern(rootBinding + pathWithoutLeadingSlash, portOverride), + userBindingPattern(rootBinding + pathWithoutLeadingSlash + '/', portOverride)); } } return handler; } + private static BindingPattern bindingPattern(String path, OptionalInt port) { + return port.isPresent() + ? SystemBindingPattern.fromHttpPortAndPath(Integer.toString(port.getAsInt()), path) + : SystemBindingPattern.fromHttpPath(path); + } + + private static UserBindingPattern userBindingPattern(String path, OptionalInt port) { + UserBindingPattern bindingPattern = UserBindingPattern.fromPattern(path); + return port.isPresent() + ? bindingPattern.withPort(port.getAsInt()) + : bindingPattern; + } + private static Handler createHandler(String className, Threadpool executor) { return new Handler(new ComponentModel(className, null, "vespaclient-container-plugin"), executor); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/component/UserBindingPattern.java b/config-model/src/main/java/com/yahoo/vespa/model/container/component/UserBindingPattern.java index 5da11c06fb1..182eca835c1 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/component/UserBindingPattern.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/component/UserBindingPattern.java @@ -13,6 +13,7 @@ public class UserBindingPattern extends BindingPattern { public static UserBindingPattern fromHttpPath(String path) { return new UserBindingPattern("http", "*", null, path); } public static UserBindingPattern fromPattern(String binding) { return new UserBindingPattern(binding); } + public UserBindingPattern withPort(int port) { return new UserBindingPattern(scheme(), host(), Integer.toString(port), path()); } @Override public String toString() { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java index f55fb547bb0..cb52f701da4 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java @@ -32,10 +32,10 @@ public class HostedSslConnectorFactory extends ConnectorFactory { */ public static HostedSslConnectorFactory withProvidedCertificate( String serverName, EndpointCertificateSecrets endpointCertificateSecrets, boolean enforceHandshakeClientAuth, - Collection<String> tlsCiphersOverride, boolean enableProxyProtocolMixedMode) { + Collection<String> tlsCiphersOverride, boolean enableProxyProtocolMixedMode, int port) { ConfiguredDirectSslProvider sslProvider = createConfiguredDirectSslProvider( serverName, endpointCertificateSecrets, DEFAULT_HOSTED_TRUSTSTORE, /*tlsCaCertificates*/null, enforceHandshakeClientAuth); - return new HostedSslConnectorFactory(sslProvider, false, enforceHandshakeClientAuth, tlsCiphersOverride, enableProxyProtocolMixedMode); + return new HostedSslConnectorFactory(sslProvider, false, enforceHandshakeClientAuth, tlsCiphersOverride, enableProxyProtocolMixedMode, port); } /** @@ -43,24 +43,24 @@ public class HostedSslConnectorFactory extends ConnectorFactory { */ public static HostedSslConnectorFactory withProvidedCertificateAndTruststore( String serverName, EndpointCertificateSecrets endpointCertificateSecrets, String tlsCaCertificates, - Collection<String> tlsCiphersOverride, boolean enableProxyProtocolMixedMode) { + Collection<String> tlsCiphersOverride, boolean enableProxyProtocolMixedMode, int port) { ConfiguredDirectSslProvider sslProvider = createConfiguredDirectSslProvider( serverName, endpointCertificateSecrets, /*tlsCaCertificatesPath*/null, tlsCaCertificates, false); - return new HostedSslConnectorFactory(sslProvider, true, false, tlsCiphersOverride, enableProxyProtocolMixedMode); + return new HostedSslConnectorFactory(sslProvider, true, false, tlsCiphersOverride, enableProxyProtocolMixedMode, port); } /** * Create connector factory that uses the default certificate and truststore provided by Vespa (through Vespa-global TLS configuration). */ public static HostedSslConnectorFactory withDefaultCertificateAndTruststore(String serverName, Collection<String> tlsCiphersOverride, - boolean enableProxyProtocolMixedMode) { - return new HostedSslConnectorFactory(new DefaultSslProvider(serverName), true, false, tlsCiphersOverride, enableProxyProtocolMixedMode); + boolean enableProxyProtocolMixedMode, int port) { + return new HostedSslConnectorFactory(new DefaultSslProvider(serverName), true, false, tlsCiphersOverride, enableProxyProtocolMixedMode, port); } private HostedSslConnectorFactory(SslProvider sslProvider, boolean enforceClientAuth, boolean enforceHandshakeClientAuth, Collection<String> tlsCiphersOverride, - boolean enableProxyProtocolMixedMode) { - super(new Builder("tls4443", 4443).sslProvider(sslProvider)); + boolean enableProxyProtocolMixedMode, int port) { + super(new Builder("tls"+port, port).sslProvider(sslProvider)); this.enforceClientAuth = enforceClientAuth; this.enforceHandshakeClientAuth = enforceHandshakeClientAuth; this.tlsCiphersOverride = tlsCiphersOverride; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java index 0bf586a089f..4c94708d892 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java @@ -97,6 +97,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.OptionalInt; import java.util.Set; import java.util.function.Consumer; import java.util.logging.Level; @@ -114,6 +115,9 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { // Default path to vip status file for container in Hosted Vespa. static final String HOSTED_VESPA_STATUS_FILE = Defaults.getDefaults().underVespaHome("var/vespa/load-balancer/status.html"); + // Data plane port for hosted Vespa + static final int HOSTED_VESPA_DATAPLANE_PORT = 4443; + //Path to vip status file for container in Hosted Vespa. Only used if set, else use HOSTED_VESPA_STATUS_FILE private static final String HOSTED_VESPA_STATUS_FILE_SETTING = "VESPA_LB_STATUS_FILE"; @@ -450,11 +454,11 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { connectorFactory = authorizeClient ? HostedSslConnectorFactory.withProvidedCertificateAndTruststore( - serverName, endpointCertificateSecrets, getTlsClientAuthorities(deployState), tlsCiphersOverride, proxyProtocolMixedMode) + serverName, endpointCertificateSecrets, getTlsClientAuthorities(deployState), tlsCiphersOverride, proxyProtocolMixedMode, HOSTED_VESPA_DATAPLANE_PORT) : HostedSslConnectorFactory.withProvidedCertificate( - serverName, endpointCertificateSecrets, enforceHandshakeClientAuth, tlsCiphersOverride, proxyProtocolMixedMode); + serverName, endpointCertificateSecrets, enforceHandshakeClientAuth, tlsCiphersOverride, proxyProtocolMixedMode, HOSTED_VESPA_DATAPLANE_PORT); } else { - connectorFactory = HostedSslConnectorFactory.withDefaultCertificateAndTruststore(serverName, tlsCiphersOverride, proxyProtocolMixedMode); + connectorFactory = HostedSslConnectorFactory.withDefaultCertificateAndTruststore(serverName, tlsCiphersOverride, proxyProtocolMixedMode, HOSTED_VESPA_DATAPLANE_PORT); } cluster.getHttp().getAccessControl().ifPresent(accessControl -> accessControl.configureHostedConnector(connectorFactory)); server.addConnector(connectorFactory); @@ -540,7 +544,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { addIncludes(searchElement); cluster.setSearch(buildSearch(deployState, cluster, searchElement)); - addSearchHandler(cluster, searchElement); + addSearchHandler(deployState, cluster, searchElement); validateAndAddConfiguredComponents(deployState, cluster, searchElement, "renderer", ContainerModelBuilder::validateRendererElement); } @@ -591,7 +595,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { cluster.addSearchAndDocprocBundles(); addIncludes(processingElement); cluster.setProcessingChains(new DomProcessingBuilder(null).build(deployState, cluster, processingElement), - serverBindings(processingElement, ProcessingChains.defaultBindings).toArray(BindingPattern[]::new)); + serverBindings(deployState, processingElement, ProcessingChains.defaultBindings).toArray(BindingPattern[]::new)); validateAndAddConfiguredComponents(deployState, cluster, processingElement, "renderer", ContainerModelBuilder::validateRendererElement); } @@ -616,7 +620,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { private void addUserHandlers(DeployState deployState, ApplicationContainerCluster cluster, Element spec) { for (Element component: XML.getChildren(spec, "handler")) { cluster.addComponent( - new DomHandlerBuilder(cluster).build(deployState, cluster, component)); + new DomHandlerBuilder(cluster, OptionalInt.of(HOSTED_VESPA_DATAPLANE_PORT)).build(deployState, cluster, component)); } } @@ -875,9 +879,13 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { container.setPreLoad(nodesElement.getAttribute(VespaDomBuilder.PRELOAD_ATTRIB_NAME)); } - private void addSearchHandler(ApplicationContainerCluster cluster, Element searchElement) { + private void addSearchHandler(DeployState deployState, ApplicationContainerCluster cluster, Element searchElement) { + BindingPattern bindingPattern = SearchHandler.DEFAULT_BINDING; + if (deployState.isHosted() && deployState.featureFlags().useRestrictedDataPlaneBindings()) { + bindingPattern = SearchHandler.bindingPattern(Optional.of(Integer.toString(HOSTED_VESPA_DATAPLANE_PORT))); + } SearchHandler searchHandler = new SearchHandler(cluster, - serverBindings(searchElement, SearchHandler.DEFAULT_BINDING), + serverBindings(deployState, searchElement, bindingPattern), ContainerThreadpool.UserOptions.fromXml(searchElement).orElse(null)); cluster.addComponent(searchHandler); @@ -885,25 +893,31 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { searchHandler.addComponent(Component.fromClassAndBundle(SearchHandler.EXECUTION_FACTORY_CLASS, PlatformBundles.SEARCH_AND_DOCPROC_BUNDLE)); } - private List<BindingPattern> serverBindings(Element searchElement, BindingPattern... defaultBindings) { + private List<BindingPattern> serverBindings(DeployState deployState, Element searchElement, BindingPattern... defaultBindings) { List<Element> bindings = XML.getChildren(searchElement, "binding"); if (bindings.isEmpty()) return List.of(defaultBindings); - return toBindingList(bindings); + return toBindingList(deployState, bindings); } - private List<BindingPattern> toBindingList(List<Element> bindingElements) { + private List<BindingPattern> toBindingList(DeployState deployState, List<Element> bindingElements) { List<BindingPattern> result = new ArrayList<>(); - + OptionalInt port = deployState.isHosted() && deployState.featureFlags().useRestrictedDataPlaneBindings() ? OptionalInt.of(HOSTED_VESPA_DATAPLANE_PORT) : OptionalInt.empty(); for (Element element: bindingElements) { String text = element.getTextContent().trim(); if (!text.isEmpty()) - result.add(UserBindingPattern.fromPattern(text)); + result.add(userBindingPattern(text, port)); } return result; } + private static UserBindingPattern userBindingPattern(String path, OptionalInt port) { + UserBindingPattern bindingPattern = UserBindingPattern.fromPattern(path); + return port.isPresent() + ? bindingPattern.withPort(port.getAsInt()) + : bindingPattern; + } private ContainerDocumentApi buildDocumentApi(ApplicationContainerCluster cluster, Element spec) { Element documentApiElement = XML.getChild(spec, "document-api"); @@ -911,8 +925,9 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { ContainerDocumentApi.HandlerOptions documentApiOptions = DocumentApiOptionsBuilder.build(documentApiElement); Element ignoreUndefinedFields = XML.getChild(documentApiElement, "ignore-undefined-fields"); + OptionalInt portBindingOverride = cluster.isHostedVespa()? OptionalInt.of(HOSTED_VESPA_DATAPLANE_PORT) : OptionalInt.empty(); return new ContainerDocumentApi(cluster, documentApiOptions, - "true".equals(XML.getValue(ignoreUndefinedFields))); + "true".equals(XML.getValue(ignoreUndefinedFields)), portBindingOverride); } private ContainerDocproc buildDocproc(DeployState deployState, ApplicationContainerCluster cluster, Element spec) { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/SearchHandler.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/SearchHandler.java index 54cd061d2c5..596375ea93a 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/SearchHandler.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/SearchHandler.java @@ -11,6 +11,8 @@ import com.yahoo.vespa.model.container.component.chain.ProcessingHandler; import com.yahoo.vespa.model.container.search.searchchain.SearchChains; import java.util.List; +import java.util.Optional; +import java.util.OptionalInt; import static com.yahoo.container.bundle.BundleInstantiationSpecification.fromSearchAndDocproc; @@ -25,7 +27,7 @@ class SearchHandler extends ProcessingHandler<SearchChains> { static final String EXECUTION_FACTORY_CLASS = com.yahoo.search.searchchain.ExecutionFactory.class.getName(); static final BundleInstantiationSpecification HANDLER_SPEC = fromSearchAndDocproc(HANDLER_CLASS); - static final BindingPattern DEFAULT_BINDING = SystemBindingPattern.fromHttpPath("/search/*"); + static final BindingPattern DEFAULT_BINDING = bindingPattern(Optional.empty()); SearchHandler(ApplicationContainerCluster cluster, List<BindingPattern> bindings, @@ -34,6 +36,13 @@ class SearchHandler extends ProcessingHandler<SearchChains> { bindings.forEach(this::addServerBindings); } + static BindingPattern bindingPattern(Optional<String> port) { + String path = "/search/*"; + return port + .filter(s -> !s.isBlank()) + .map(s -> SystemBindingPattern.fromHttpPortAndPath(s, path)) + .orElseGet(() -> SystemBindingPattern.fromHttpPath(path)); + } private static class Threadpool extends ContainerThreadpool { diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/HandlerBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/HandlerBuilderTest.java index 291a5f21305..0c9b87c0ae3 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/HandlerBuilderTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/HandlerBuilderTest.java @@ -1,6 +1,8 @@ package com.yahoo.vespa.model.container.xml; import com.yahoo.config.model.builder.xml.test.DomBuilderTest; +import com.yahoo.config.model.deploy.DeployState; +import com.yahoo.config.model.deploy.TestProperties; import com.yahoo.container.ComponentsConfig; import com.yahoo.container.jdisc.JdiscBindingsConfig; import com.yahoo.container.usability.BindingsOverviewHandler; @@ -10,10 +12,14 @@ import com.yahoo.vespa.model.container.component.Handler; import org.junit.jupiter.api.Test; import org.w3c.dom.Element; +import java.util.List; +import java.util.Map; + import static com.yahoo.vespa.model.container.ContainerCluster.ROOT_HANDLER_BINDING; import static com.yahoo.vespa.model.container.ContainerCluster.STATE_HANDLER_BINDING_1; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasItem; import static org.junit.jupiter.api.Assertions.*; @@ -100,6 +106,103 @@ public class HandlerBuilderTest extends ContainerModelBuilderTestBase { assertTrue(handler.getInjectedComponentIds().contains("threadpool@default-handler-common")); } + @Test + void restricts_default_bindings_in_hosted_vespa() { + DeployState deployState = new DeployState.Builder() + .properties(new TestProperties().setHostedVespa(true).setUseRestrictedDataPlaneBindings(true)) + .build(); + verifyDefaultBindings(deployState, "http://*:4443"); + } + + @Test + void restricts_custom_bindings_in_hosted_vespa() { + DeployState deployState = new DeployState.Builder() + .properties(new TestProperties().setHostedVespa(true).setUseRestrictedDataPlaneBindings(true)) + .build(); + verifyCustomSearchBindings(deployState, "http://*:4443"); + } + + @Test + void does_not_restrict_default_bindings_in_self_hosted() { + DeployState deployState = new DeployState.Builder() + .properties(new TestProperties().setHostedVespa(false).setUseRestrictedDataPlaneBindings(true)) + .build(); + verifyDefaultBindings(deployState, "http://*"); + } + + @Test + void does_not_restrict_custom_bindings_in_self_hosted() { + DeployState deployState = new DeployState.Builder() + .properties(new TestProperties().setHostedVespa(false).setUseRestrictedDataPlaneBindings(true)) + .build(); + verifyCustomSearchBindings(deployState, "http://*"); + } + + private void verifyDefaultBindings(DeployState deployState, String bindingPrefix) { + Element clusterElem = DomBuilderTest.parse( + "<container id='default' version='1.0'>", + " <search/>", + " <document-api/>", + " <handler id='FooHandler'>", + " <binding>http://*/foo</binding>", + " </handler>", + nodesXml, + "</container>"); + + createModel(root, deployState, null, clusterElem); + JdiscBindingsConfig bindingsConfig = root.getConfig(JdiscBindingsConfig.class, "default"); + + // Verify /search /feed /document and custom handler are bound correctly + Map<String, JdiscBindingsConfig.Handlers> handlers = bindingsConfig.handlers(); + Map<String, List<String>> expectedHandlerMappings = Map.of( + "com.yahoo.search.handler.SearchHandler", List.of("/search/*"), + "com.yahoo.document.restapi.resource.DocumentV1ApiHandler", List.of("/document/v1/*", "/document/v1/*/"), + "com.yahoo.vespa.http.server.FeedHandler", List.of("/reserved-for-internal-use/feedapi", "/reserved-for-internal-use/feedapi/"), + "FooHandler", List.of("/foo")); + expectedHandlerMappings.forEach((handler, bindings) -> validateHandler(handlers.get(handler), bindingPrefix, bindings)); + + // All other handlers should be bound to default (http://*/...) + handlers.entrySet().stream() + .filter(e -> ! expectedHandlerMappings.containsKey(e.getKey())) + .forEach(e -> assertTrue(e.getValue().serverBindings().stream().allMatch(s -> s.startsWith("http://*/")))); + } + + private void verifyCustomSearchBindings(DeployState deployState, String bindingPrefix) { + Element clusterElem = DomBuilderTest.parse( + "<container id='default' version='1.0'>", + " <search>", + " <binding>http://*/search-binding/</binding>", + " </search>", + " <document-api>", + " <binding>http://*/docapi-binding/</binding>", + " </document-api>", + nodesXml, + "</container>"); + + createModel(root, deployState, null, clusterElem); + JdiscBindingsConfig bindingsConfig = root.getConfig(JdiscBindingsConfig.class, "default"); + + // Verify search feed and document handler are bound correctly + Map<String, JdiscBindingsConfig.Handlers> handlers = bindingsConfig.handlers(); + Map<String, List<String>> expectedHandlerMappings = Map.of( + "com.yahoo.search.handler.SearchHandler", List.of("/search-binding/"), + "com.yahoo.document.restapi.resource.DocumentV1ApiHandler", List.of("/docapi-binding/document/v1/*", "/docapi-binding/document/v1/*/"), + "com.yahoo.vespa.http.server.FeedHandler", List.of("/docapi-binding/reserved-for-internal-use/feedapi", "/docapi-binding/reserved-for-internal-use/feedapi/")); + expectedHandlerMappings.forEach((handler, bindings) -> validateHandler(handlers.get(handler), bindingPrefix, bindings)); + + // All other handlers should be bound to default (http://*/...) + handlers.entrySet().stream() + .filter(e -> ! expectedHandlerMappings.containsKey(e.getKey())) + .forEach(e -> assertTrue(e.getValue().serverBindings().stream().allMatch(s -> s.startsWith("http://*/")))); + + } + + private void validateHandler(JdiscBindingsConfig.Handlers handler, String bindingPrefix, List<String> expectedBindings) { + assertNotNull(handler); + assertEquals(expectedBindings.size(), handler.serverBindings().size()); + assertThat(handler.serverBindings(), containsInAnyOrder(expectedBindings.stream().map(s->bindingPrefix+s).toArray())); + } + private void createClusterWithJDiscHandler() { Element clusterElem = DomBuilderTest.parse( "<container id='default' version='1.0'>", |