diff options
author | Morten Tokle <mortent@verizonmedia.com> | 2020-10-14 14:23:45 +0200 |
---|---|---|
committer | Morten Tokle <mortent@verizonmedia.com> | 2020-10-14 14:23:45 +0200 |
commit | 7e01a997ee5f918ce32ef2d6bddc44691cc6c530 (patch) | |
tree | 2a3e1561abe7d11a3dc8f8ae57a3df5f70820c53 /config-model | |
parent | d27f881027a5fddeefe336da0d84f2e160c01eb1 (diff) |
Set up need_auth for connector when access_control configured
Diffstat (limited to 'config-model')
4 files changed, 70 insertions, 11 deletions
diff --git a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java index 1455b4d8007..2c6608c37ad 100644 --- a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java +++ b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java @@ -52,6 +52,7 @@ public class TestProperties implements ModelContext.Properties { private AthenzDomain athenzDomain; private ApplicationRoles applicationRoles; private Quota quota = Quota.unlimited(); + private boolean useAccessControlTlsHandshakeClientAuth; @Override public boolean multitenant() { return multitenant; } @Override public ApplicationId applicationId() { return applicationId; } @@ -89,6 +90,7 @@ public class TestProperties implements ModelContext.Properties { @Override public double visibilityDelay() { return visibilityDelay; } @Override public boolean tlsUseFSync() { return tlsUseFSync; } @Override public String tlsCompressionType() { return tlsCompressionType; } + @Override public boolean useAccessControlTlsHandshakeClientAuth() { return useAccessControlTlsHandshakeClientAuth; } public TestProperties setJvmGCOptions(String gcOptions) { jvmGCOptions = gcOptions; @@ -200,6 +202,11 @@ public class TestProperties implements ModelContext.Properties { return this; } + public TestProperties useAccessControlTlsHandshakeClientAuth(boolean useAccessControlTlsHandshakeClientAuth) { + this.useAccessControlTlsHandshakeClientAuth = useAccessControlTlsHandshakeClientAuth; + return this; + } + public static class Spec implements ConfigServerSpec { private final String hostName; 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 bcc2c9a3d6a..6c4ebec2301 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 @@ -21,14 +21,15 @@ public class HostedSslConnectorFactory extends ConnectorFactory { private static final String DEFAULT_HOSTED_TRUSTSTORE = "/opt/yahoo/share/ssl/certs/athenz_certificate_bundle.pem"; private final boolean enforceClientAuth; + private final boolean enforceHandshakeClientAuth; /** * Create connector factory that uses a certificate provided by the config-model / configserver and default hosted Vespa truststore. */ // TODO Enforce client authentication public static HostedSslConnectorFactory withProvidedCertificate( - String serverName, EndpointCertificateSecrets endpointCertificateSecrets) { - return new HostedSslConnectorFactory(createConfiguredDirectSslProvider(serverName, endpointCertificateSecrets, DEFAULT_HOSTED_TRUSTSTORE, /*tlsCaCertificates*/null), false); + String serverName, EndpointCertificateSecrets endpointCertificateSecrets, boolean enforceHandshakeClientAuth) { + return new HostedSslConnectorFactory(createConfiguredDirectSslProvider(serverName, endpointCertificateSecrets, DEFAULT_HOSTED_TRUSTSTORE, /*tlsCaCertificates*/null), false, enforceHandshakeClientAuth); } /** @@ -36,19 +37,20 @@ public class HostedSslConnectorFactory extends ConnectorFactory { */ public static HostedSslConnectorFactory withProvidedCertificateAndTruststore( String serverName, EndpointCertificateSecrets endpointCertificateSecrets, String tlsCaCertificates) { - return new HostedSslConnectorFactory(createConfiguredDirectSslProvider(serverName, endpointCertificateSecrets, /*tlsCaCertificatesPath*/null, tlsCaCertificates), true); + return new HostedSslConnectorFactory(createConfiguredDirectSslProvider(serverName, endpointCertificateSecrets, /*tlsCaCertificatesPath*/null, tlsCaCertificates), true, false); } /** * Create connector factory that uses the default certificate and truststore provided by Vespa (through Vespa-global TLS configuration). */ public static HostedSslConnectorFactory withDefaultCertificateAndTruststore(String serverName) { - return new HostedSslConnectorFactory(new DefaultSslProvider(serverName), true); + return new HostedSslConnectorFactory(new DefaultSslProvider(serverName), true, false); } - private HostedSslConnectorFactory(SimpleComponent sslProviderComponent, boolean enforceClientAuth) { + private HostedSslConnectorFactory(SimpleComponent sslProviderComponent, boolean enforceClientAuth, boolean enforceHandshakeClientAuth) { super("tls4443", 4443, sslProviderComponent); this.enforceClientAuth = enforceClientAuth; + this.enforceHandshakeClientAuth = enforceHandshakeClientAuth; } private static ConfiguredDirectSslProvider createConfiguredDirectSslProvider( @@ -65,10 +67,15 @@ public class HostedSslConnectorFactory extends ConnectorFactory { @Override public void getConfig(ConnectorConfig.Builder connectorBuilder) { super.getConfig(connectorBuilder); + if (enforceHandshakeClientAuth) { + connectorBuilder.ssl.clientAuth(ClientAuth.Enum.NEED_AUTH); + } else { + connectorBuilder + .tlsClientAuthEnforcer(new ConnectorConfig.TlsClientAuthEnforcer.Builder() + .pathWhitelist(INSECURE_WHITELISTED_PATHS) + .enable(enforceClientAuth)); + } connectorBuilder - .tlsClientAuthEnforcer(new ConnectorConfig.TlsClientAuthEnforcer.Builder() - .pathWhitelist(INSECURE_WHITELISTED_PATHS) - .enable(enforceClientAuth)) .proxyProtocol(new ConnectorConfig.ProxyProtocol.Builder().enabled(true).mixedMode(true)) .idleTimeout(Duration.ofMinutes(3).toSeconds()) .maxConnectionLife(Duration.ofMinutes(10).toSeconds()); 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 bc8b01ec6df..6fd1c9b35fb 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 @@ -318,11 +318,11 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { if (isHostedTenantApplication(context)) { addHostedImplicitHttpIfNotPresent(cluster); addHostedImplicitAccessControlIfNotPresent(deployState, cluster); - addAdditionalHostedConnector(deployState, cluster); + addAdditionalHostedConnector(deployState, cluster, context); } } - private void addAdditionalHostedConnector(DeployState deployState, ApplicationContainerCluster cluster) { + private void addAdditionalHostedConnector(DeployState deployState, ApplicationContainerCluster cluster, ConfigModelContext context) { JettyHttpServer server = cluster.getHttp().getHttpServer().get(); String serverName = server.getComponentId().getName(); @@ -333,9 +333,16 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { throw new RuntimeException("Client certificate authority security/clients.pem is missing - see: https://cloud.vespa.ai/security-model#data-plane"); } EndpointCertificateSecrets endpointCertificateSecrets = deployState.endpointCertificateSecrets().get(); + + boolean enforceHandshakeClientAuth = context.properties().useAccessControlTlsHandshakeClientAuth() && + cluster.getHttp().getAccessControl() + .map(accessControl -> accessControl.clientAuthentication) + .map(clientAuth -> clientAuth.equals(AccessControl.ClientAuthentication.need)) + .orElse(false); + HostedSslConnectorFactory connectorFactory = authorizeClient ? HostedSslConnectorFactory.withProvidedCertificateAndTruststore(serverName, endpointCertificateSecrets, deployState.tlsClientAuthority().get()) - : HostedSslConnectorFactory.withProvidedCertificate(serverName, endpointCertificateSecrets); + : HostedSslConnectorFactory.withProvidedCertificate(serverName, endpointCertificateSecrets, enforceHandshakeClientAuth); server.addConnector(connectorFactory); } else { server.addConnector(HostedSslConnectorFactory.withDefaultCertificateAndTruststore(serverName)); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java index f8046bfe92d..13c1631e0ce 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java @@ -824,6 +824,7 @@ public class ContainerModelBuilderTest extends ContainerModelBuilderTestBase { ConnectorConfig connectorConfig = new ConnectorConfig(builder); assertTrue(connectorConfig.ssl().enabled()); + assertEquals(ConnectorConfig.Ssl.ClientAuth.Enum.WANT_AUTH, connectorConfig.ssl().clientAuth()); assertEquals("CERT", connectorConfig.ssl().certificate()); assertEquals("KEY", connectorConfig.ssl().privateKey()); assertEquals(4443, connectorConfig.listenPort()); @@ -833,6 +834,43 @@ public class ContainerModelBuilderTest extends ContainerModelBuilderTestBase { assertThat(connectorConfig.ssl().caCertificate(), isEmptyString()); } + @Test + public void requireThatClientAuthenticationIsEnforced() { + Element clusterElem = DomBuilderTest.parse( + "<container version='1.0'>", + nodesXml, + " <http><filtering>" + + " <access-control domain=\"vespa\" tls-handshake-client-auth=\"need\"/>" + + " </filtering></http>" + + "</container>" ); + + DeployState state = new DeployState.Builder().properties( + new TestProperties() + .setHostedVespa(true) + .setEndpointCertificateSecrets(Optional.of(new EndpointCertificateSecrets("CERT", "KEY"))) + .useAccessControlTlsHandshakeClientAuth(true)) + .build(); + createModel(root, state, null, clusterElem); + ApplicationContainer container = (ApplicationContainer)root.getProducer("container/container.0"); + + List<ConnectorFactory> connectorFactories = container.getHttp().getHttpServer().get().getConnectorFactories(); + ConnectorFactory tlsPort = connectorFactories.stream().filter(connectorFactory -> connectorFactory.getListenPort() == 4443).findFirst().orElseThrow(); + + ConnectorConfig.Builder builder = new ConnectorConfig.Builder(); + tlsPort.getConfig(builder); + + ConnectorConfig connectorConfig = new ConnectorConfig(builder); + assertTrue(connectorConfig.ssl().enabled()); + assertEquals(ConnectorConfig.Ssl.ClientAuth.Enum.NEED_AUTH, connectorConfig.ssl().clientAuth()); + assertEquals("CERT", connectorConfig.ssl().certificate()); + assertEquals("KEY", connectorConfig.ssl().privateKey()); + assertEquals(4443, connectorConfig.listenPort()); + + assertThat("Connector must use Athenz truststore in a non-public system.", + connectorConfig.ssl().caCertificateFile(), equalTo("/opt/yahoo/share/ssl/certs/athenz_certificate_bundle.pem")); + assertThat(connectorConfig.ssl().caCertificate(), isEmptyString()); + } + private Element generateContainerElementWithRenderer(String rendererId) { return DomBuilderTest.parse( |