summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMorten Tokle <mortent@verizonmedia.com>2020-11-23 15:02:46 +0100
committerGitHub <noreply@github.com>2020-11-23 15:02:46 +0100
commit0616eecc2ec2a0b267100512ad0dcac5767dde87 (patch)
tree1299b8636d39bc7374fc83b0817cc3e65b51f4f1
parentd5fa517246cb0647123615b9b10d6054b9c759c6 (diff)
parent885cb31bad09bae15067c9c527f051ade6bb2d44 (diff)
Merge pull request #15434 from vespa-engine/mortent/vespa-tls-filter
Create default connector request chain
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/http/AccessControl.java31
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java20
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessControlTest.java25
-rw-r--r--jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/misc/VespaTlsFilter.java21
-rw-r--r--jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/misc/VespaTlsFilterTest.java66
5 files changed, 155 insertions, 8 deletions
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 9bd12350f26..00ff19ae68a 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
@@ -3,6 +3,10 @@ package com.yahoo.vespa.model.container.http;
import com.yahoo.component.ComponentId;
import com.yahoo.component.ComponentSpecification;
+import com.yahoo.component.chain.dependencies.Dependencies;
+import com.yahoo.component.chain.model.ChainedComponentModel;
+import com.yahoo.container.bundle.BundleInstantiationSpecification;
+import com.yahoo.vespa.defaults.Defaults;
import com.yahoo.vespa.model.container.ApplicationContainerCluster;
import com.yahoo.vespa.model.container.ContainerCluster;
import com.yahoo.vespa.model.container.component.BindingPattern;
@@ -27,11 +31,13 @@ import java.util.Set;
*/
public class AccessControl {
- public enum ClientAuthentication { want, need }
+
+ public enum ClientAuthentication { want, need;}
public static final ComponentId ACCESS_CONTROL_CHAIN_ID = ComponentId.fromString("access-control-chain");
- public static final ComponentId ACCESS_CONTROL_EXCLUDED_CHAIN_ID = ComponentId.fromString("access-control-excluded-chain");
+ public static final ComponentId ACCESS_CONTROL_EXCLUDED_CHAIN_ID = ComponentId.fromString("access-control-excluded-chain");
+ public static final ComponentId DEFAULT_CONNECTOR_HOSTED_REQUEST_CHAIN_ID = ComponentId.fromString("default-connector-hosted-request-chain");
private static final int HOSTED_CONTAINER_PORT = 4443;
// Handlers that are excluded from access control
@@ -44,7 +50,6 @@ public class AccessControl {
ApplicationContainerCluster.METRICS_V2_HANDLER_CLASS,
ApplicationContainerCluster.PROMETHEUS_V1_HANDLER_CLASS
);
-
public static class Builder {
private final String domain;
private boolean readEnabled = false;
@@ -52,7 +57,6 @@ public class AccessControl {
private ClientAuthentication clientAuthentication = ClientAuthentication.need;
private final Set<BindingPattern> excludeBindings = new LinkedHashSet<>();
private Collection<Handler<?>> handlers = Collections.emptyList();
-
public Builder(String domain) {
this.domain = domain;
}
@@ -112,6 +116,7 @@ public class AccessControl {
http.setAccessControl(this);
addAccessControlFilterChain(http);
addAccessControlExcludedChain(http);
+ addDefaultHostedRequestChain(http);
removeDuplicateBindingsFromAccessControlChain(http);
}
@@ -119,6 +124,18 @@ public class AccessControl {
connectorFactory.setDefaultRequestFilterChain(ACCESS_CONTROL_CHAIN_ID);
}
+ public void configureDefaultHostedConnector(Http http) {
+ // Set default filter chain on local port
+ http.getHttpServer()
+ .get()
+ .getConnectorFactories()
+ .stream()
+ .filter(cf -> cf.getListenPort() == Defaults.getDefaults().vespaWebServicePort())
+ .findFirst()
+ .orElseThrow(() -> new RuntimeException("Could not find default connector"))
+ .setDefaultRequestFilterChain(DEFAULT_CONNECTOR_HOSTED_REQUEST_CHAIN_ID);
+ }
+
/** returns the excluded bindings as specified in 'access-control' in services.xml **/
public Set<BindingPattern> excludedBindings() { return excludedBindings; }
@@ -148,6 +165,12 @@ public class AccessControl {
}
}
+ // Add a filter chain used by default hosted connector
+ private void addDefaultHostedRequestChain(Http http) {
+ Chain<Filter> chain = createChain(DEFAULT_CONNECTOR_HOSTED_REQUEST_CHAIN_ID);
+ http.getFilterChains().add(chain);
+ }
+
// Remove bindings from access control chain that have binding pattern as a different filter chain
private void removeDuplicateBindingsFromAccessControlChain(Http http) {
removeDuplicateBindingsFromChain(http, ACCESS_CONTROL_EXCLUDED_CHAIN_ID);
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 0e23527c97c..7eea5d8496f 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
@@ -85,6 +85,7 @@ import org.w3c.dom.Node;
import java.net.URI;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -318,10 +319,16 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
if (isHostedTenantApplication(context)) {
addHostedImplicitHttpIfNotPresent(cluster);
addHostedImplicitAccessControlIfNotPresent(deployState, cluster);
+ addDefaultConnectorHostedFilterBinding(cluster);
addAdditionalHostedConnector(deployState, cluster, context);
}
}
+ private void addDefaultConnectorHostedFilterBinding(ApplicationContainerCluster cluster) {
+ cluster.getHttp().getAccessControl()
+ .ifPresent(accessControl -> accessControl.configureDefaultHostedConnector(cluster.getHttp())); ;
+ }
+
private void addAdditionalHostedConnector(DeployState deployState, ApplicationContainerCluster cluster, ConfigModelContext context) {
JettyHttpServer server = cluster.getHttp().getHttpServer().get();
String serverName = server.getComponentId().getName();
@@ -361,10 +368,15 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
if(cluster.getHttp() == null) {
cluster.setHttp(new Http(new FilterChains(cluster)));
}
- if(cluster.getHttp().getHttpServer().isEmpty()) {
- JettyHttpServer defaultHttpServer = new JettyHttpServer(new ComponentId("DefaultHttpServer"), cluster, cluster.isHostedVespa());
- cluster.getHttp().setHttpServer(defaultHttpServer);
- defaultHttpServer.addConnector(new ConnectorFactory.Builder("SearchServer", Defaults.getDefaults().vespaWebServicePort()).build());
+ JettyHttpServer httpServer = cluster.getHttp().getHttpServer().orElse(null);
+ if (httpServer == null) {
+ httpServer = new JettyHttpServer(new ComponentId("DefaultHttpServer"), cluster, cluster.isHostedVespa());
+ cluster.getHttp().setHttpServer(httpServer);
+ }
+ int defaultPort = Defaults.getDefaults().vespaWebServicePort();
+ boolean defaultConnectorPresent = httpServer.getConnectorFactories().stream().anyMatch(connector -> connector.getListenPort() == defaultPort);
+ if (!defaultConnectorPresent) {
+ httpServer.addConnector(new ConnectorFactory.Builder("SearchServer", defaultPort).build());
}
}
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 1ac95ac9a99..4993a51ab74 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
@@ -6,8 +6,10 @@ 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.config.provision.AthenzDomain;
+import com.yahoo.vespa.defaults.Defaults;
import com.yahoo.vespa.model.container.ApplicationContainer;
import com.yahoo.vespa.model.container.http.AccessControl;
+import com.yahoo.vespa.model.container.http.ConnectorFactory;
import com.yahoo.vespa.model.container.http.FilterChains;
import com.yahoo.vespa.model.container.http.Http;
import com.yahoo.vespa.model.container.http.ssl.HostedSslConnectorFactory;
@@ -50,6 +52,7 @@ public class AccessControlTest extends ContainerModelBuilderTestBase {
FilterChains filterChains = http.getFilterChains();
assertTrue(filterChains.hasChain(AccessControl.ACCESS_CONTROL_CHAIN_ID));
assertTrue(filterChains.hasChain(AccessControl.ACCESS_CONTROL_EXCLUDED_CHAIN_ID));
+ assertTrue(filterChains.hasChain(AccessControl.DEFAULT_CONNECTOR_HOSTED_REQUEST_CHAIN_ID));
}
@Test
@@ -297,6 +300,28 @@ public class AccessControlTest extends ContainerModelBuilderTestBase {
assertEquals(AccessControl.ClientAuthentication.want, http.getAccessControl().get().clientAuthentication);
}
+ @Test
+ public void local_connector_has_default_chain() {
+ Http http = createModelAndGetHttp(
+ " <http>",
+ " <filtering>",
+ " <access-control/>",
+ " </filtering>",
+ " </http>");
+
+ Set<String> actualBindings = getFilterBindings(http, AccessControl.DEFAULT_CONNECTOR_HOSTED_REQUEST_CHAIN_ID);
+ assertThat(actualBindings, empty());
+
+ ConnectorFactory connectorFactory = http.getHttpServer().get().getConnectorFactories().stream()
+ .filter(cf -> cf.getListenPort() == Defaults.getDefaults().vespaWebServicePort())
+ .findAny()
+ .get();
+
+ Optional<ComponentId> defaultChain = connectorFactory.getDefaultRequestFilterChain();
+ assertTrue(defaultChain.isPresent());
+ assertEquals(AccessControl.DEFAULT_CONNECTOR_HOSTED_REQUEST_CHAIN_ID, defaultChain.get());
+ }
+
private Http createModelAndGetHttp(String... httpElement) {
List<String> servicesXml = new ArrayList<>();
servicesXml.add("<container version='1.0'>");
diff --git a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/misc/VespaTlsFilter.java b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/misc/VespaTlsFilter.java
new file mode 100644
index 00000000000..b891212031f
--- /dev/null
+++ b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/misc/VespaTlsFilter.java
@@ -0,0 +1,21 @@
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+package com.yahoo.jdisc.http.filter.security.misc;
+
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.http.filter.DiscFilterRequest;
+import com.yahoo.jdisc.http.filter.security.base.JsonSecurityRequestFilterBase;
+
+import java.security.cert.X509Certificate;
+import java.util.List;
+import java.util.Optional;
+
+public class VespaTlsFilter extends JsonSecurityRequestFilterBase {
+
+ @Override
+ protected Optional<ErrorResponse> filter(DiscFilterRequest request) {
+ return request.getClientCertificateChain().isEmpty()
+ ? Optional.of(new ErrorResponse(Response.Status.FORBIDDEN, "Forbidden to access this path"))
+ : Optional.empty();
+ }
+}
diff --git a/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/misc/VespaTlsFilterTest.java b/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/misc/VespaTlsFilterTest.java
new file mode 100644
index 00000000000..294126eb349
--- /dev/null
+++ b/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/misc/VespaTlsFilterTest.java
@@ -0,0 +1,66 @@
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+package com.yahoo.jdisc.http.filter.security.misc;
+
+import com.yahoo.container.jdisc.RequestHandlerTestDriver;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.http.filter.DiscFilterRequest;
+import com.yahoo.security.KeyAlgorithm;
+import com.yahoo.security.KeyUtils;
+import com.yahoo.security.SignatureAlgorithm;
+import com.yahoo.security.X509CertificateBuilder;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import javax.security.auth.x500.X500Principal;
+import java.math.BigInteger;
+import java.net.URI;
+import java.security.cert.X509Certificate;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.when;
+
+public class VespaTlsFilterTest {
+
+ @Test
+ public void testFilter() {
+ assertSuccess(createRequest(List.of(createCertificate())));
+ assertForbidden(createRequest(Collections.emptyList()));
+ }
+
+ private static X509Certificate createCertificate() {
+ return X509CertificateBuilder
+ .fromKeypair(
+ KeyUtils.generateKeypair(KeyAlgorithm.EC), new X500Principal("CN=test"),
+ Instant.now(), Instant.now().plus(1, ChronoUnit.DAYS),
+ SignatureAlgorithm.SHA512_WITH_ECDSA, BigInteger.valueOf(1))
+ .build();
+ }
+
+ private static DiscFilterRequest createRequest(List<X509Certificate> certChain) {
+ DiscFilterRequest request = Mockito.mock(DiscFilterRequest.class);
+ when(request.getClientCertificateChain()).thenReturn(certChain);
+ when(request.getMethod()).thenReturn("GET");
+ when(request.getUri()).thenReturn(URI.create("http://localhost:8080/"));
+ return request;
+ }
+
+ private static void assertForbidden(DiscFilterRequest request) {
+ VespaTlsFilter filter = new VespaTlsFilter();
+ RequestHandlerTestDriver.MockResponseHandler handler = new RequestHandlerTestDriver.MockResponseHandler();
+ filter.filter(request, handler);
+ assertEquals(Response.Status.FORBIDDEN, handler.getStatus());
+ }
+
+ private static void assertSuccess(DiscFilterRequest request) {
+ VespaTlsFilter filter = new VespaTlsFilter();
+ RequestHandlerTestDriver.MockResponseHandler handler = new RequestHandlerTestDriver.MockResponseHandler();
+ filter.filter(request, handler);
+ assertNull(handler.getResponse());
+ }
+}