aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/CloudSslProvider.java13
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java110
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java61
3 files changed, 113 insertions, 71 deletions
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/CloudSslProvider.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/CloudSslProvider.java
index ae60ed77a7a..5fa893e9599 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/CloudSslProvider.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/CloudSslProvider.java
@@ -2,6 +2,8 @@
package com.yahoo.vespa.model.container.http.ssl;
import com.yahoo.jdisc.http.ConnectorConfig;
+import com.yahoo.jdisc.http.ssl.impl.CloudSslContextProvider;
+import com.yahoo.jdisc.http.ssl.impl.ConfiguredSslContextFactoryProvider;
import java.util.Optional;
@@ -15,6 +17,8 @@ import static com.yahoo.jdisc.http.ConnectorConfig.Ssl.ClientAuth;
*/
public class CloudSslProvider extends SslProvider {
public static final String COMPONENT_ID_PREFIX = "configured-ssl-provider@";
+ public static final String MTLSONLY_COMPONENT_CLASS = ConfiguredSslContextFactoryProvider.class.getName();
+ public static final String TOKEN_COMPONENT_CLASS = CloudSslContextProvider.class.getName();
private final String privateKey;
private final String certificate;
@@ -22,9 +26,8 @@ public class CloudSslProvider extends SslProvider {
private final String caCertificate;
private final ClientAuth.Enum clientAuthentication;
- public CloudSslProvider(String servername, String privateKey, String certificate, String caCertificatePath,
- String caCertificate, ClientAuth.Enum clientAuthentication) {
- super(COMPONENT_ID_PREFIX, servername, "com.yahoo.jdisc.http.ssl.impl.CloudSslContextProvider", null);
+ public CloudSslProvider(String servername, String privateKey, String certificate, String caCertificatePath, String caCertificate, ClientAuth.Enum clientAuthentication, boolean enableTokenSupport) {
+ super(COMPONENT_ID_PREFIX, servername, componentClass(enableTokenSupport), null);
this.privateKey = privateKey;
this.certificate = certificate;
this.caCertificatePath = caCertificatePath;
@@ -32,6 +35,10 @@ public class CloudSslProvider extends SslProvider {
this.clientAuthentication = clientAuthentication;
}
+ private static String componentClass(boolean enableTokenSupport) {
+ return enableTokenSupport ? TOKEN_COMPONENT_CLASS : MTLSONLY_COMPONENT_CLASS;
+ }
+
@Override
public void amendConnectorConfig(ConnectorConfig.Builder builder) {
builder.ssl.enabled(true);
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 4f11611541d..5bf348e5bb5 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
@@ -3,6 +3,7 @@ package com.yahoo.vespa.model.container.http.ssl;
import com.yahoo.config.model.api.EndpointCertificateSecrets;
import com.yahoo.jdisc.http.ConnectorConfig;
+import com.yahoo.jdisc.http.ConnectorConfig.Ssl.ClientAuth;
import com.yahoo.security.tls.TlsContext;
import com.yahoo.vespa.model.container.http.ConnectorFactory;
@@ -17,71 +18,96 @@ import java.util.List;
*/
public class HostedSslConnectorFactory extends ConnectorFactory {
- boolean requireTlsClientAuthDuringTlsHandshake;
- private final List<String> tlsCiphersOverride;
+ private static final List<String> INSECURE_WHITELISTED_PATHS = List.of("/status.html");
+ private static final String DEFAULT_HOSTED_TRUSTSTORE = "/opt/yahoo/share/ssl/certs/athenz_certificate_bundle.pem";
+
+ private final boolean enforceClientAuth;
+ private final boolean enforceHandshakeClientAuth;
+ private final Collection<String> tlsCiphersOverride;
private final boolean enableProxyProtocolMixedMode;
private final Duration endpointConnectionTtl;
- public static Builder builder(String name, int listenPort) { return new Builder(name, listenPort); }
+ /**
+ * Create connector factory that uses a certificate provided by the config-model / configserver and default hosted Vespa truststore.
+ */
+ public static HostedSslConnectorFactory withProvidedCertificate(
+ String serverName, EndpointCertificateSecrets endpointCertificateSecrets, boolean enforceHandshakeClientAuth,
+ Collection<String> tlsCiphersOverride, boolean enableProxyProtocolMixedMode, int port,
+ Duration endpointConnectionTtl, boolean enableTokenSupport) {
+ CloudSslProvider sslProvider = createConfiguredDirectSslProvider(
+ serverName, endpointCertificateSecrets, DEFAULT_HOSTED_TRUSTSTORE, /*tlsCaCertificates*/null, enforceHandshakeClientAuth, enableTokenSupport);
+ return new HostedSslConnectorFactory(sslProvider, false, enforceHandshakeClientAuth, tlsCiphersOverride,
+ enableProxyProtocolMixedMode, port, endpointConnectionTtl);
+ }
+
+ /**
+ * Create connector factory that uses a certificate provided by the config-model / configserver and a truststore configured by the application.
+ */
+ public static HostedSslConnectorFactory withProvidedCertificateAndTruststore(
+ String serverName, EndpointCertificateSecrets endpointCertificateSecrets, String tlsCaCertificates,
+ Collection<String> tlsCiphersOverride, boolean enableProxyProtocolMixedMode, int port,
+ Duration endpointConnectionTtl, boolean enableTokenSupport) {
+ CloudSslProvider sslProvider = createConfiguredDirectSslProvider(
+ serverName, endpointCertificateSecrets, /*tlsCaCertificatesPath*/null, tlsCaCertificates, false, enableTokenSupport);
+ return new HostedSslConnectorFactory(sslProvider, true, false, tlsCiphersOverride, enableProxyProtocolMixedMode,
+ port, endpointConnectionTtl);
+ }
+
+ /**
+ * 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, int port,
+ Duration endpointConnectionTtl) {
+ return new HostedSslConnectorFactory(new DefaultSslProvider(serverName), true, false, tlsCiphersOverride,
+ enableProxyProtocolMixedMode, port, endpointConnectionTtl);
+ }
- private HostedSslConnectorFactory(Builder builder) {
- super(new ConnectorFactory.Builder("tls"+builder.port, builder.port).sslProvider(createSslProvider(builder)));
- this.requireTlsClientAuthDuringTlsHandshake = builder.requireTlsClientAuthDuringTlsHandshake;
- this.tlsCiphersOverride = List.copyOf(builder.tlsCiphersOverride);
- this.enableProxyProtocolMixedMode = builder.enableProxyProtocolMixedMode;
- this.endpointConnectionTtl = builder.endpointConnectionTtl;
+ private HostedSslConnectorFactory(SslProvider sslProvider, boolean enforceClientAuth,
+ boolean enforceHandshakeClientAuth, Collection<String> tlsCiphersOverride,
+ boolean enableProxyProtocolMixedMode, int port, Duration endpointConnectionTtl) {
+ super(new Builder("tls"+port, port).sslProvider(sslProvider));
+ this.enforceClientAuth = enforceClientAuth;
+ this.enforceHandshakeClientAuth = enforceHandshakeClientAuth;
+ this.tlsCiphersOverride = tlsCiphersOverride;
+ this.enableProxyProtocolMixedMode = enableProxyProtocolMixedMode;
+ this.endpointConnectionTtl = endpointConnectionTtl;
}
- private static SslProvider createSslProvider(Builder builder) {
- if (builder.endpointCertificate == null) return new DefaultSslProvider(builder.name);
- var clientAuthentication = builder.requireTlsClientAuthDuringTlsHandshake
- ? ConnectorConfig.Ssl.ClientAuth.Enum.NEED_AUTH : ConnectorConfig.Ssl.ClientAuth.Enum.WANT_AUTH;
+ private static CloudSslProvider createConfiguredDirectSslProvider(
+ String serverName, EndpointCertificateSecrets endpointCertificateSecrets, String tlsCaCertificatesPath, String tlsCaCertificates, boolean enforceHandshakeClientAuth, boolean enableTokenSupport) {
+ var clientAuthentication = enforceHandshakeClientAuth ? ClientAuth.Enum.NEED_AUTH : ClientAuth.Enum.WANT_AUTH;
return new CloudSslProvider(
- builder.name, builder.endpointCertificate.key(), builder.endpointCertificate.certificate(),
- builder.tlsCaCertificatesPath, builder.tlsCaCertificatesPem, clientAuthentication);
+ serverName,
+ endpointCertificateSecrets.key(),
+ endpointCertificateSecrets.certificate(),
+ tlsCaCertificatesPath,
+ tlsCaCertificates,
+ clientAuthentication,
+ enableTokenSupport);
}
@Override
public void getConfig(ConnectorConfig.Builder connectorBuilder) {
super.getConfig(connectorBuilder);
- if (! requireTlsClientAuthDuringTlsHandshake) {
- connectorBuilder.tlsClientAuthEnforcer(
- new ConnectorConfig.TlsClientAuthEnforcer.Builder()
- .pathWhitelist(List.of("/status.html")).enable(true));
+ if (! enforceHandshakeClientAuth) {
+ connectorBuilder
+ .tlsClientAuthEnforcer(new ConnectorConfig.TlsClientAuthEnforcer.Builder()
+ .pathWhitelist(INSECURE_WHITELISTED_PATHS)
+ .enable(enforceClientAuth));
}
// Disables TLSv1.3 as it causes some browsers to prompt user for client certificate (when connector has 'want' auth)
connectorBuilder.ssl.enabledProtocols(List.of("TLSv1.2"));
+
if (!tlsCiphersOverride.isEmpty()) {
connectorBuilder.ssl.enabledCipherSuites(tlsCiphersOverride.stream().sorted().toList());
} else {
connectorBuilder.ssl.enabledCipherSuites(TlsContext.ALLOWED_CIPHER_SUITES.stream().sorted().toList());
}
+
connectorBuilder
.proxyProtocol(new ConnectorConfig.ProxyProtocol.Builder().enabled(true).mixedMode(enableProxyProtocolMixedMode))
.idleTimeout(Duration.ofSeconds(30).toSeconds())
.maxConnectionLife(endpointConnectionTtl != null ? endpointConnectionTtl.toSeconds() : 0);
}
-
- public static class Builder {
- final String name;
- final int port;
- boolean requireTlsClientAuthDuringTlsHandshake;
- List<String> tlsCiphersOverride;
- boolean enableProxyProtocolMixedMode;
- Duration endpointConnectionTtl;
- EndpointCertificateSecrets endpointCertificate;
- String tlsCaCertificatesPem;
- String tlsCaCertificatesPath;
-
- private Builder(String name, int port) { this.name = name; this.port = port; }
- public Builder requireTlsClientAuthDuringTlsHandshake(boolean enable) {this.requireTlsClientAuthDuringTlsHandshake = enable; return this; }
- public Builder endpointConnectionTtl(Duration ttl) { endpointConnectionTtl = ttl; return this; }
- public Builder tlsCiphersOverride(Collection<String> ciphers) { tlsCiphersOverride = List.copyOf(ciphers); return this; }
- public Builder proxyProtocolMixedMode(boolean enable) { enableProxyProtocolMixedMode = enable; return this; }
- public Builder endpointCertificate(EndpointCertificateSecrets cert) { this.endpointCertificate = cert; return this; }
- public Builder tlsCaCertificatesPath(String path) { this.tlsCaCertificatesPath = path; return this; }
- public Builder tlsCaCertificatesPem(String pem) { this.tlsCaCertificatesPem = pem; return this; }
-
- public HostedSslConnectorFactory build() { return new HostedSslConnectorFactory(this); }
- }
}
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 3318138ebd7..00feb0a1c76 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
@@ -16,6 +16,7 @@ import com.yahoo.config.model.ConfigModelContext;
import com.yahoo.config.model.api.ApplicationClusterEndpoint;
import com.yahoo.config.model.api.ConfigServerSpec;
import com.yahoo.config.model.api.ContainerEndpoint;
+import com.yahoo.config.model.api.EndpointCertificateSecrets;
import com.yahoo.config.model.api.TenantSecretStore;
import com.yahoo.config.model.application.provider.IncludeDirs;
import com.yahoo.config.model.builder.xml.ConfigModelBuilder;
@@ -108,6 +109,7 @@ import java.io.IOException;
import java.io.Reader;
import java.net.URI;
import java.security.cert.X509Certificate;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -598,35 +600,31 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
.ifPresent(accessControl -> accessControl.configureDefaultHostedConnector(cluster.getHttp())); ;
}
- private void addAdditionalHostedConnector(DeployState state, ApplicationContainerCluster cluster) {
+ private void addAdditionalHostedConnector(DeployState deployState, ApplicationContainerCluster cluster) {
JettyHttpServer server = cluster.getHttp().getHttpServer().get();
String serverName = server.getComponentId().getName();
// If the deployment contains certificate/private key reference, setup TLS port
- var builder = HostedSslConnectorFactory.builder(serverName, getDataplanePort(state))
- .proxyProtocolMixedMode(state.getProperties().featureFlags().enableProxyProtocolMixedMode())
- .tlsCiphersOverride(state.getProperties().tlsCiphersOverride())
- .endpointConnectionTtl(state.getProperties().endpointConnectionTtl());
- var endpointCert = state.endpointCertificateSecrets().orElse(null);
- if (endpointCert != null) {
- builder.endpointCertificate(endpointCert);
- boolean isPublic = state.zone().system().isPublic();
+ HostedSslConnectorFactory connectorFactory;
+ Collection<String> tlsCiphersOverride = deployState.getProperties().tlsCiphersOverride();
+ boolean proxyProtocolMixedMode = deployState.getProperties().featureFlags().enableProxyProtocolMixedMode();
+ Duration endpointConnectionTtl = deployState.getProperties().endpointConnectionTtl();
+ var port = getDataplanePort(deployState);
+ if (deployState.endpointCertificateSecrets().isPresent()) {
+ boolean authorizeClient = deployState.zone().system().isPublic();
List<X509Certificate> clientCertificates = getClientCertificates(cluster);
- if (isPublic) {
- if (clientCertificates.isEmpty())
- throw new IllegalArgumentException("Client certificate authority security/clients.pem is missing - " +
- "see: https://cloud.vespa.ai/en/security/guide#data-plane");
- builder.tlsCaCertificatesPem(X509CertificateUtils.toPem(clientCertificates));
- } else {
- builder.tlsCaCertificatesPath("/opt/yahoo/share/ssl/certs/athenz_certificate_bundle.pem");
+ if (authorizeClient && clientCertificates.isEmpty()) {
+ throw new IllegalArgumentException("Client certificate authority security/clients.pem is missing - " +
+ "see: https://cloud.vespa.ai/en/security/guide#data-plane");
}
- builder.requireTlsClientAuthDuringTlsHandshake(
- cluster.getHttp().getAccessControl()
- .map(accessControl -> accessControl.clientAuthentication)
- .map(clientAuth -> clientAuth == AccessControl.ClientAuthentication.need)
- .orElse(false));
+ EndpointCertificateSecrets endpointCertificateSecrets = deployState.endpointCertificateSecrets().get();
+
+ boolean enforceHandshakeClientAuth = cluster.getHttp().getAccessControl()
+ .map(accessControl -> accessControl.clientAuthentication)
+ .map(clientAuth -> clientAuth == AccessControl.ClientAuthentication.need)
+ .orElse(false);
- boolean enableTokenSupport = state.featureFlags().enableDataplaneProxy()
+ boolean enableTokenSupport = deployState.featureFlags().enableDataplaneProxy()
&& cluster.getClients().stream().anyMatch(c -> !c.tokens().isEmpty());
// Set up component to generate proxy cert if token support is enabled
@@ -635,13 +633,24 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
cluster.addSimpleComponent(DataplaneProxyService.class);
var dataplaneProxy = new DataplaneProxy(
- getDataplanePort(state),
- endpointCert.certificate(),
- endpointCert.key());
+ getDataplanePort(deployState),
+ endpointCertificateSecrets.certificate(),
+ endpointCertificateSecrets.key());
cluster.addComponent(dataplaneProxy);
}
+
+ connectorFactory = authorizeClient
+ ? HostedSslConnectorFactory.withProvidedCertificateAndTruststore(
+ serverName, endpointCertificateSecrets, X509CertificateUtils.toPem(clientCertificates),
+ tlsCiphersOverride, proxyProtocolMixedMode, port, endpointConnectionTtl, enableTokenSupport)
+ : HostedSslConnectorFactory.withProvidedCertificate(
+ serverName, endpointCertificateSecrets, enforceHandshakeClientAuth, tlsCiphersOverride,
+ proxyProtocolMixedMode, port, endpointConnectionTtl, enableTokenSupport);
+ } else {
+ connectorFactory = HostedSslConnectorFactory.withDefaultCertificateAndTruststore(
+ serverName, tlsCiphersOverride, proxyProtocolMixedMode, port,
+ endpointConnectionTtl);
}
- var connectorFactory = builder.build();
cluster.getHttp().getAccessControl().ifPresent(accessControl -> accessControl.configureHostedConnector(connectorFactory));
server.addConnector(connectorFactory);
}