diff options
Diffstat (limited to 'config-model/src/main')
4 files changed, 72 insertions, 114 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 5fa893e9599..ae60ed77a7a 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,8 +2,6 @@ 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; @@ -17,8 +15,6 @@ 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; @@ -26,8 +22,9 @@ 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, boolean enableTokenSupport) { - super(COMPONENT_ID_PREFIX, servername, componentClass(enableTokenSupport), null); + 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); this.privateKey = privateKey; this.certificate = certificate; this.caCertificatePath = caCertificatePath; @@ -35,10 +32,6 @@ 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 5bf348e5bb5..4f11611541d 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,7 +3,6 @@ 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; @@ -18,96 +17,71 @@ import java.util.List; */ public class HostedSslConnectorFactory extends ConnectorFactory { - 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; + boolean requireTlsClientAuthDuringTlsHandshake; + private final List<String> tlsCiphersOverride; private final boolean enableProxyProtocolMixedMode; private final Duration endpointConnectionTtl; - /** - * 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); - } + public static Builder builder(String name, int listenPort) { return new Builder(name, listenPort); } - 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 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 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; + 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; return new CloudSslProvider( - serverName, - endpointCertificateSecrets.key(), - endpointCertificateSecrets.certificate(), - tlsCaCertificatesPath, - tlsCaCertificates, - clientAuthentication, - enableTokenSupport); + builder.name, builder.endpointCertificate.key(), builder.endpointCertificate.certificate(), + builder.tlsCaCertificatesPath, builder.tlsCaCertificatesPem, clientAuthentication); } @Override public void getConfig(ConnectorConfig.Builder connectorBuilder) { super.getConfig(connectorBuilder); - if (! enforceHandshakeClientAuth) { - connectorBuilder - .tlsClientAuthEnforcer(new ConnectorConfig.TlsClientAuthEnforcer.Builder() - .pathWhitelist(INSECURE_WHITELISTED_PATHS) - .enable(enforceClientAuth)); + if (! requireTlsClientAuthDuringTlsHandshake) { + connectorBuilder.tlsClientAuthEnforcer( + new ConnectorConfig.TlsClientAuthEnforcer.Builder() + .pathWhitelist(List.of("/status.html")).enable(true)); } // 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 00feb0a1c76..3318138ebd7 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,7 +16,6 @@ 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; @@ -109,7 +108,6 @@ 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; @@ -600,31 +598,35 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { .ifPresent(accessControl -> accessControl.configureDefaultHostedConnector(cluster.getHttp())); ; } - private void addAdditionalHostedConnector(DeployState deployState, ApplicationContainerCluster cluster) { + private void addAdditionalHostedConnector(DeployState state, ApplicationContainerCluster cluster) { JettyHttpServer server = cluster.getHttp().getHttpServer().get(); String serverName = server.getComponentId().getName(); // If the deployment contains certificate/private key reference, setup TLS port - 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(); + 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(); List<X509Certificate> clientCertificates = getClientCertificates(cluster); - 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"); + 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"); } - EndpointCertificateSecrets endpointCertificateSecrets = deployState.endpointCertificateSecrets().get(); - - boolean enforceHandshakeClientAuth = cluster.getHttp().getAccessControl() - .map(accessControl -> accessControl.clientAuthentication) - .map(clientAuth -> clientAuth == AccessControl.ClientAuthentication.need) - .orElse(false); + builder.requireTlsClientAuthDuringTlsHandshake( + cluster.getHttp().getAccessControl() + .map(accessControl -> accessControl.clientAuthentication) + .map(clientAuth -> clientAuth == AccessControl.ClientAuthentication.need) + .orElse(false)); - boolean enableTokenSupport = deployState.featureFlags().enableDataplaneProxy() + boolean enableTokenSupport = state.featureFlags().enableDataplaneProxy() && cluster.getClients().stream().anyMatch(c -> !c.tokens().isEmpty()); // Set up component to generate proxy cert if token support is enabled @@ -633,24 +635,13 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { cluster.addSimpleComponent(DataplaneProxyService.class); var dataplaneProxy = new DataplaneProxy( - getDataplanePort(deployState), - endpointCertificateSecrets.certificate(), - endpointCertificateSecrets.key()); + getDataplanePort(state), + endpointCert.certificate(), + endpointCert.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); } diff --git a/config-model/src/main/resources/schema/content.rnc b/config-model/src/main/resources/schema/content.rnc index bb63dcd73ff..bb0e39a41ab 100644 --- a/config-model/src/main/resources/schema/content.rnc +++ b/config-model/src/main/resources/schema/content.rnc @@ -82,7 +82,7 @@ ClusterControllerTuning = element cluster-controller { element stable-state-period { xsd:string { pattern = "([0-9\.]+)\s*([a-z]+)?" } }? & element min-distributor-up-ratio { xsd:double }? & element min-storage-up-ratio { xsd:double }? & - element groups-allowed-down-ratio { xsd:double }? + element groups-allowed-down-ratio { xsd:double { minInclusive = "0" maxInclusive = "1" } }? } DispatchTuning = element dispatch { |