diff options
author | Morten Tokle <mortent@yahooinc.com> | 2023-06-07 11:29:07 +0200 |
---|---|---|
committer | Morten Tokle <mortent@yahooinc.com> | 2023-06-07 11:29:07 +0200 |
commit | e8e31b8f7ff1a77a47a8e9cf1bb884123ca2469a (patch) | |
tree | 55ae69cceca99e955f455348dd18c46f97d8b4fb | |
parent | 17680e5bd51252b282e011e4f9929653f78be016 (diff) |
Generate proxy certificate and inject in trust store
-rw-r--r-- | config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/CloudSslProvider.java (renamed from config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/ConfiguredDirectSslProvider.java) | 14 | ||||
-rw-r--r-- | config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java | 21 | ||||
-rw-r--r-- | config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java | 12 | ||||
-rw-r--r-- | configdefinitions/src/vespa/dataplane-proxy.def | 9 | ||||
-rw-r--r-- | container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/DataplaneProxyCredentials.java | 70 | ||||
-rw-r--r-- | container-core/src/main/java/com/yahoo/jdisc/http/ssl/impl/CloudSslContextProvider.java | 42 | ||||
-rw-r--r-- | container-core/src/main/java/com/yahoo/jdisc/http/ssl/impl/ConfiguredSslContextFactoryProvider.java | 4 | ||||
-rw-r--r-- | container-disc/src/main/java/com/yahoo/container/jdisc/DataplaneProxyConfigurator.java | 6 | ||||
-rw-r--r-- | container-disc/src/main/java/com/yahoo/container/jdisc/DataplaneProxyService.java | 11 |
9 files changed, 159 insertions, 30 deletions
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/ConfiguredDirectSslProvider.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/CloudSslProvider.java index adc1458ce85..5fa893e9599 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/ConfiguredDirectSslProvider.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/CloudSslProvider.java @@ -2,6 +2,7 @@ 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; @@ -14,9 +15,10 @@ import static com.yahoo.jdisc.http.ConnectorConfig.Ssl.ClientAuth; * @author mortent * @author andreer */ -public class ConfiguredDirectSslProvider extends SslProvider { +public class CloudSslProvider extends SslProvider { public static final String COMPONENT_ID_PREFIX = "configured-ssl-provider@"; - public static final String COMPONENT_CLASS = ConfiguredSslContextFactoryProvider.class.getName(); + 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; @@ -24,8 +26,8 @@ public class ConfiguredDirectSslProvider extends SslProvider { private final String caCertificate; private final ClientAuth.Enum clientAuthentication; - public ConfiguredDirectSslProvider(String servername, String privateKey, String certificate, String caCertificatePath, String caCertificate, ClientAuth.Enum clientAuthentication) { - super(COMPONENT_ID_PREFIX, servername, COMPONENT_CLASS, 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; @@ -33,6 +35,10 @@ public class ConfiguredDirectSslProvider 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 72d2927f910..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 @@ -33,9 +33,9 @@ public class HostedSslConnectorFactory extends ConnectorFactory { public static HostedSslConnectorFactory withProvidedCertificate( String serverName, EndpointCertificateSecrets endpointCertificateSecrets, boolean enforceHandshakeClientAuth, Collection<String> tlsCiphersOverride, boolean enableProxyProtocolMixedMode, int port, - Duration endpointConnectionTtl) { - ConfiguredDirectSslProvider sslProvider = createConfiguredDirectSslProvider( - serverName, endpointCertificateSecrets, DEFAULT_HOSTED_TRUSTSTORE, /*tlsCaCertificates*/null, enforceHandshakeClientAuth); + 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); } @@ -46,9 +46,9 @@ public class HostedSslConnectorFactory extends ConnectorFactory { public static HostedSslConnectorFactory withProvidedCertificateAndTruststore( String serverName, EndpointCertificateSecrets endpointCertificateSecrets, String tlsCaCertificates, Collection<String> tlsCiphersOverride, boolean enableProxyProtocolMixedMode, int port, - Duration endpointConnectionTtl) { - ConfiguredDirectSslProvider sslProvider = createConfiguredDirectSslProvider( - serverName, endpointCertificateSecrets, /*tlsCaCertificatesPath*/null, tlsCaCertificates, false); + Duration endpointConnectionTtl, boolean enableTokenSupport) { + CloudSslProvider sslProvider = createConfiguredDirectSslProvider( + serverName, endpointCertificateSecrets, /*tlsCaCertificatesPath*/null, tlsCaCertificates, false, enableTokenSupport); return new HostedSslConnectorFactory(sslProvider, true, false, tlsCiphersOverride, enableProxyProtocolMixedMode, port, endpointConnectionTtl); } @@ -74,16 +74,17 @@ public class HostedSslConnectorFactory extends ConnectorFactory { this.endpointConnectionTtl = endpointConnectionTtl; } - private static ConfiguredDirectSslProvider createConfiguredDirectSslProvider( - String serverName, EndpointCertificateSecrets endpointCertificateSecrets, String tlsCaCertificatesPath, String tlsCaCertificates, boolean enforceHandshakeClientAuth) { + 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 ConfiguredDirectSslProvider( + return new CloudSslProvider( serverName, endpointCertificateSecrets.key(), endpointCertificateSecrets.certificate(), tlsCaCertificatesPath, tlsCaCertificates, - clientAuthentication); + clientAuthentication, + enableTokenSupport); } @Override 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 c8bd661a00b..3305e596a87 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 @@ -39,6 +39,7 @@ import com.yahoo.container.bundle.BundleInstantiationSpecification; import com.yahoo.container.logging.AccessLog; import com.yahoo.container.logging.FileConnectionLog; import com.yahoo.io.IOUtils; +import com.yahoo.jdisc.http.server.jetty.DataplaneProxyCredentials; import com.yahoo.jdisc.http.server.jetty.VoidRequestLog; import com.yahoo.osgi.provider.model.ComponentModel; import com.yahoo.path.Path; @@ -606,13 +607,20 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { .map(clientAuth -> clientAuth == AccessControl.ClientAuthentication.need) .orElse(false); + // TODO (mortent): Implement token support in model + boolean enableTokenSupport = false; + + // Set up component to generate proxy cert if token support is enabled + if (enableTokenSupport) { + cluster.addSimpleComponent(DataplaneProxyCredentials.class); + } connectorFactory = authorizeClient ? HostedSslConnectorFactory.withProvidedCertificateAndTruststore( serverName, endpointCertificateSecrets, X509CertificateUtils.toPem(clientCertificates), - tlsCiphersOverride, proxyProtocolMixedMode, port, endpointConnectionTtl) + tlsCiphersOverride, proxyProtocolMixedMode, port, endpointConnectionTtl, enableTokenSupport) : HostedSslConnectorFactory.withProvidedCertificate( serverName, endpointCertificateSecrets, enforceHandshakeClientAuth, tlsCiphersOverride, - proxyProtocolMixedMode, port, endpointConnectionTtl); + proxyProtocolMixedMode, port, endpointConnectionTtl, enableTokenSupport); } else { connectorFactory = HostedSslConnectorFactory.withDefaultCertificateAndTruststore( serverName, tlsCiphersOverride, proxyProtocolMixedMode, port, diff --git a/configdefinitions/src/vespa/dataplane-proxy.def b/configdefinitions/src/vespa/dataplane-proxy.def index dab54a8b978..27f88583081 100644 --- a/configdefinitions/src/vespa/dataplane-proxy.def +++ b/configdefinitions/src/vespa/dataplane-proxy.def @@ -1,10 +1,15 @@ # Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. namespace=cloud.config +# The port Jdisc will be listening on port int -clientCertificate string -clientKey string + +# Server certificate and key to be used when creating server socket serverCertificate string serverKey string + +# The mTLS endpoint SNI header to route to Jdisc using L4 mTlsEndpoint string + +# The endpoint SNI header supporting token authentication tokenEndpoint string diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/DataplaneProxyCredentials.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/DataplaneProxyCredentials.java new file mode 100644 index 00000000000..46c840ad607 --- /dev/null +++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/DataplaneProxyCredentials.java @@ -0,0 +1,70 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.server.jetty; + +import com.yahoo.component.AbstractComponent; +import com.yahoo.security.KeyUtils; +import com.yahoo.security.X509CertificateUtils; +import com.yahoo.security.X509CertificateWithKey; +import com.yahoo.vespa.defaults.Defaults; +import com.yahoo.yolean.Exceptions; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.time.Duration; + +/** + * Generates temporary credentials to be used by a proxy for accessing Jdisc. + * Credentials are written to vespa_home/tmp/. + * + * @author mortent + */ +public class DataplaneProxyCredentials extends AbstractComponent { + + private final Path certificateFile; + private final Path keyFile; + + public DataplaneProxyCredentials() { + certificateFile = Paths.get(Defaults.getDefaults().underVespaHome("tmp/proxy_cert.pem")); + keyFile = Paths.get(Defaults.getDefaults().underVespaHome("tmp/proxy_key.pem")); + if (regenerateCredentials(certificateFile, keyFile)) { + X509CertificateWithKey selfSigned = X509CertificateUtils.createSelfSigned("cn=vespa dataplane proxy", Duration.ofDays(30)); + Exceptions.uncheck(() -> Files.writeString(certificateFile, X509CertificateUtils.toPem(selfSigned.certificate()))); + Exceptions.uncheck(() -> Files.writeString(keyFile, KeyUtils.toPem(selfSigned.privateKey()))); + } + } + + /* + * Returns true if credentials should be regenerated. + */ + private boolean regenerateCredentials(Path certificateFile, Path keyFile) { + if (!Files.exists(certificateFile) || !Files.exists(keyFile)) { + return true; + } + try { + X509Certificate x509Certificate = X509CertificateUtils.fromPem(Files.readString(certificateFile)); + PrivateKey privateKey = KeyUtils.fromPemEncodedPrivateKey(Files.readString(keyFile)); + return !X509CertificateUtils.privateKeyMatchesPublicKey(privateKey, x509Certificate.getPublicKey()); + } catch (IOException e) { + // Some exception occured, assume credentials corrupted and requires a new pair. + return true; + } + } + + public Path certificateFile() { + return certificateFile; + } + + public Path keyFile() { + return keyFile; + } + + @Override + public void deconstruct() { + super.deconstruct(); + } + +} diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/ssl/impl/CloudSslContextProvider.java b/container-core/src/main/java/com/yahoo/jdisc/http/ssl/impl/CloudSslContextProvider.java new file mode 100644 index 00000000000..cdfd4aa938e --- /dev/null +++ b/container-core/src/main/java/com/yahoo/jdisc/http/ssl/impl/CloudSslContextProvider.java @@ -0,0 +1,42 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.ssl.impl; + +import com.yahoo.jdisc.http.ConnectorConfig; +import com.yahoo.jdisc.http.server.jetty.DataplaneProxyCredentials; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Optional; + +/** + * Used to enable token based endpoints in Cloud. Amends trust store to allow proxy. + * + * @author mortent + */ +public class CloudSslContextProvider extends ConfiguredSslContextFactoryProvider { + + private final DataplaneProxyCredentials dataplaneProxyCredentials; + + public CloudSslContextProvider(ConnectorConfig connectorConfig, DataplaneProxyCredentials dataplaneProxyCredentials) { + super(connectorConfig); + this.dataplaneProxyCredentials = dataplaneProxyCredentials; + } + + @Override + Optional<String> getCaCertificates(ConnectorConfig.Ssl sslConfig) { + String proxyCert; + try { + proxyCert = Files.readString(dataplaneProxyCredentials.certificateFile(), StandardCharsets.UTF_8); + } catch (IOException e) { + throw new IllegalArgumentException("Dataplane proxy certificate not available", e); + } + if (!sslConfig.caCertificate().isBlank()) { + return Optional.of(sslConfig.caCertificate() + "\n" + proxyCert); + } else if (!sslConfig.caCertificateFile().isBlank()) { + return Optional.of(readToString(sslConfig.caCertificateFile()) + "\n" + proxyCert); + } else { + return Optional.of(proxyCert); + } + } +} diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/ssl/impl/ConfiguredSslContextFactoryProvider.java b/container-core/src/main/java/com/yahoo/jdisc/http/ssl/impl/ConfiguredSslContextFactoryProvider.java index 27c5aff22a9..b99bc007b32 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/ssl/impl/ConfiguredSslContextFactoryProvider.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/ssl/impl/ConfiguredSslContextFactoryProvider.java @@ -110,7 +110,7 @@ public class ConfiguredSslContextFactoryProvider implements SslProvider { private static boolean hasBoth(String a, String b) { return !a.isBlank() && !b.isBlank(); } private static boolean hasNeither(String a, String b) { return a.isBlank() && b.isBlank(); } - private static Optional<String> getCaCertificates(ConnectorConfig.Ssl sslConfig) { + Optional<String> getCaCertificates(ConnectorConfig.Ssl sslConfig) { if (!sslConfig.caCertificate().isBlank()) { return Optional.of(sslConfig.caCertificate()); } else if (!sslConfig.caCertificateFile().isBlank()) { @@ -130,7 +130,7 @@ public class ConfiguredSslContextFactoryProvider implements SslProvider { return readToString(config.certificateFile()); } - private static String readToString(String filename) { + static String readToString(String filename) { try { return Files.readString(Paths.get(filename), StandardCharsets.UTF_8); } catch (IOException e) { diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/DataplaneProxyConfigurator.java b/container-disc/src/main/java/com/yahoo/container/jdisc/DataplaneProxyConfigurator.java index 4c637b5798d..340c0035052 100644 --- a/container-disc/src/main/java/com/yahoo/container/jdisc/DataplaneProxyConfigurator.java +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/DataplaneProxyConfigurator.java @@ -3,7 +3,7 @@ package com.yahoo.container.jdisc; import com.yahoo.cloud.config.DataplaneProxyConfig; import com.yahoo.component.AbstractComponent; -import com.yahoo.jdisc.http.ConnectorConfig; +import com.yahoo.jdisc.http.server.jetty.DataplaneProxyCredentials; /** * Reconfigurable component for supporting data plane proxy. Configures the {@code DataplaneProxyService} by calling {@code DataplaneProxyService#init} @@ -12,8 +12,8 @@ import com.yahoo.jdisc.http.ConnectorConfig; */ public class DataplaneProxyConfigurator extends AbstractComponent { - public DataplaneProxyConfigurator(DataplaneProxyConfig config, DataplaneProxyService dataplaneProxyService) { - dataplaneProxyService.reconfigure(config); + public DataplaneProxyConfigurator(DataplaneProxyConfig config, DataplaneProxyService dataplaneProxyService, DataplaneProxyCredentials credentialsProvider) { + dataplaneProxyService.reconfigure(config, credentialsProvider); } @Override diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/DataplaneProxyService.java b/container-disc/src/main/java/com/yahoo/container/jdisc/DataplaneProxyService.java index 5b0cdfaf630..230d017c584 100644 --- a/container-disc/src/main/java/com/yahoo/container/jdisc/DataplaneProxyService.java +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/DataplaneProxyService.java @@ -3,6 +3,7 @@ package com.yahoo.container.jdisc; import com.yahoo.cloud.config.DataplaneProxyConfig; import com.yahoo.component.AbstractComponent; +import com.yahoo.jdisc.http.server.jetty.DataplaneProxyCredentials; import javax.inject.Inject; import java.io.IOException; @@ -36,22 +37,18 @@ public class DataplaneProxyService extends AbstractComponent { this.started = false; } - public void reconfigure(DataplaneProxyConfig config) { + public void reconfigure(DataplaneProxyConfig config, DataplaneProxyCredentials credentialsProvider) { try { String serverCert = config.serverCertificate(); String serverKey = config.serverKey(); - String clientCert = config.clientCertificate(); - String clientKey = config.clientKey(); boolean configChanged = false; - configChanged |= writeFile(clientCertificateFile, clientCert); - configChanged |= writeFile(clientKeyFile, clientKey); configChanged |= writeFile(serverCertificateFile, serverCert); configChanged |= writeFile(serverKeyFile, serverKey); configChanged |= writeFile(nginxConf, nginxConfig( - clientCertificateFile, - clientKeyFile, + credentialsProvider.certificateFile(), + credentialsProvider.keyFile(), serverCertificateFile, serverKeyFile, URI.create(config.mTlsEndpoint()), |