From 172128afece1d218ad16f4b7415c1f2bf9663d08 Mon Sep 17 00:00:00 2001 From: Bjørn Christian Seime Date: Tue, 18 Jul 2023 15:55:54 +0200 Subject: Add separate connector for token endpoint --- .../yahoo/config/model/deploy/TestProperties.java | 7 +++ .../model/container/http/ssl/CloudSslProvider.java | 12 ++--- .../http/ssl/HostedSslConnectorFactory.java | 2 +- .../model/container/xml/CloudDataPlaneFilter.java | 18 +------ .../container/xml/CloudTokenDataPlaneFilter.java | 61 ++++++++++++++++++++++ .../model/container/xml/ContainerModelBuilder.java | 61 +++++++++++++++------- 6 files changed, 117 insertions(+), 44 deletions(-) create mode 100644 config-model/src/main/java/com/yahoo/vespa/model/container/xml/CloudTokenDataPlaneFilter.java (limited to 'config-model/src/main/java/com/yahoo') 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 2b55b1f1d10..66a23c79fbb 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 @@ -88,6 +88,7 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea private boolean allowMoreThanOneContentGroupDown = false; private boolean enableConditionalPutRemoveWriteRepair = false; private List dataplaneTokens; + private boolean enableDataplaneProxy; @Override public ModelContext.FeatureFlags featureFlags() { return this; } @Override public boolean multitenant() { return multitenant; } @@ -148,6 +149,7 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea @Override public boolean allowMoreThanOneContentGroupDown(ClusterSpec.Id id) { return allowMoreThanOneContentGroupDown; } @Override public boolean enableConditionalPutRemoveWriteRepair() { return enableConditionalPutRemoveWriteRepair; } @Override public List dataplaneTokens() { return dataplaneTokens; } + @Override public boolean enableDataplaneProxy() { return enableDataplaneProxy; } public TestProperties sharedStringRepoNoReclaim(boolean sharedStringRepoNoReclaim) { this.sharedStringRepoNoReclaim = sharedStringRepoNoReclaim; @@ -393,6 +395,11 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea return this; } + public TestProperties setEnableDataplaneProxy(boolean enable) { + this.enableDataplaneProxy = enable; + 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/CloudSslProvider.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/CloudSslProvider.java index b231a4ad847..ab163719aac 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; @@ -16,10 +14,6 @@ import static com.yahoo.jdisc.http.ConnectorConfig.Ssl.ClientAuth; * @author andreer */ 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; private final String caCertificatePath; @@ -28,7 +22,7 @@ public class CloudSslProvider extends SslProvider { 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); + super("cloud-ssl-provider@", servername, componentClass(enableTokenSupport), null); this.privateKey = privateKey; this.certificate = certificate; this.caCertificatePath = caCertificatePath; @@ -37,7 +31,9 @@ public class CloudSslProvider extends SslProvider { } private static String componentClass(boolean enableTokenSupport) { - return enableTokenSupport ? TOKEN_COMPONENT_CLASS : MTLSONLY_COMPONENT_CLASS; + return enableTokenSupport + ? "com.yahoo.jdisc.http.ssl.impl.CloudTokenSslContextProvider" + : "com.yahoo.jdisc.http.ssl.impl.ConfiguredSslContextFactoryProvider"; } @Override 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 243d14a006f..cebe08288f6 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 @@ -81,7 +81,7 @@ public class HostedSslConnectorFactory extends ConnectorFactory { final List remoteAddressHeaders = new ArrayList<>(); final List remotePortHeaders = new ArrayList<>(); SslClientAuth clientAuth; - List tlsCiphersOverride; + List tlsCiphersOverride = List.of(); boolean proxyProtocolEnabled; boolean proxyProtocolMixedMode; Duration endpointConnectionTtl; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/CloudDataPlaneFilter.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/CloudDataPlaneFilter.java index efa5ee01506..2d0d47288d1 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/CloudDataPlaneFilter.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/CloudDataPlaneFilter.java @@ -5,7 +5,6 @@ import com.yahoo.component.ComponentSpecification; import com.yahoo.component.chain.dependencies.Dependencies; import com.yahoo.component.chain.model.ChainedComponentModel; import com.yahoo.config.model.deploy.DeployState; -import com.yahoo.config.provision.DataplaneToken; import com.yahoo.container.bundle.BundleInstantiationSpecification; import com.yahoo.jdisc.http.filter.security.cloud.config.CloudDataPlaneFilterConfig; import com.yahoo.security.X509CertificateUtils; @@ -13,7 +12,6 @@ import com.yahoo.vespa.model.container.ApplicationContainerCluster; import com.yahoo.vespa.model.container.http.Client; import com.yahoo.vespa.model.container.http.Filter; -import java.time.Instant; import java.util.Collection; import java.util.List; @@ -24,15 +22,11 @@ class CloudDataPlaneFilter extends Filter implements CloudDataPlaneFilterConfig. private final Collection clients; private final boolean clientsLegacyMode; - private final String tokenContext; CloudDataPlaneFilter(ApplicationContainerCluster cluster, DeployState state) { super(model()); this.clients = List.copyOf(cluster.getClients()); this.clientsLegacyMode = cluster.clientsLegacyMode(); - // Token domain must be identical to the domain used for generating the tokens - this.tokenContext = "Vespa Cloud tenant data plane:%s" - .formatted(state.getProperties().applicationId().tenant().value()); } private static ChainedComponentModel model() { @@ -51,21 +45,11 @@ class CloudDataPlaneFilter extends Filter implements CloudDataPlaneFilterConfig. .map(x -> new CloudDataPlaneFilterConfig.Clients.Builder() .id(x.id()) .certificates(x.certificates().stream().map(X509CertificateUtils::toPem).toList()) - .tokens(tokensConfig(x.tokens())) .permissions(x.permissions())) .toList(); - builder.clients(clientsCfg).legacyMode(false).tokenContext(tokenContext); + builder.clients(clientsCfg).legacyMode(false); } } - private static List tokensConfig(Collection tokens) { - return tokens.stream() - .map(token -> new CloudDataPlaneFilterConfig.Clients.Tokens.Builder() - .id(token.tokenId()) - .fingerprints(token.versions().stream().map(DataplaneToken.Version::fingerprint).toList()) - .checkAccessHashes(token.versions().stream().map(DataplaneToken.Version::checkAccessHash).toList()) - .expirations(token.versions().stream().map(v -> v.expiration().map(Instant::toString).orElse("")).toList())) - .toList(); - } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/CloudTokenDataPlaneFilter.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/CloudTokenDataPlaneFilter.java new file mode 100644 index 00000000000..5b57682e759 --- /dev/null +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/CloudTokenDataPlaneFilter.java @@ -0,0 +1,61 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.model.container.xml; + +import com.yahoo.component.ComponentSpecification; +import com.yahoo.component.chain.dependencies.Dependencies; +import com.yahoo.component.chain.model.ChainedComponentModel; +import com.yahoo.config.model.deploy.DeployState; +import com.yahoo.config.provision.DataplaneToken; +import com.yahoo.container.bundle.BundleInstantiationSpecification; +import com.yahoo.jdisc.http.filter.security.cloud.config.CloudTokenDataPlaneFilterConfig; +import com.yahoo.vespa.model.container.ApplicationContainerCluster; +import com.yahoo.vespa.model.container.http.Client; +import com.yahoo.vespa.model.container.http.Filter; + +import java.time.Instant; +import java.util.Collection; +import java.util.List; + +class CloudTokenDataPlaneFilter extends Filter implements CloudTokenDataPlaneFilterConfig.Producer { + private final Collection clients; + private final String tokenContext; + + CloudTokenDataPlaneFilter(ApplicationContainerCluster cluster, DeployState state) { + super(model()); + this.clients = List.copyOf(cluster.getClients()); + // Token domain must be identical to the domain used for generating the tokens + this.tokenContext = "Vespa Cloud tenant data plane:%s" + .formatted(state.getProperties().applicationId().tenant().value()); + } + + private static ChainedComponentModel model() { + return new ChainedComponentModel( + new BundleInstantiationSpecification( + new ComponentSpecification("com.yahoo.jdisc.http.filter.security.cloud.CloudTokenDataPlaneFilter"), + null, + new ComponentSpecification("jdisc-security-filters")), + Dependencies.emptyDependencies()); + } + + @Override + public void getConfig(CloudTokenDataPlaneFilterConfig.Builder builder) { + var clientsCfg = clients.stream() + .map(x -> new CloudTokenDataPlaneFilterConfig.Clients.Builder() + .id(x.id()) + .tokens(tokensConfig(x.tokens())) + .permissions(x.permissions())) + .toList(); + builder.clients(clientsCfg).tokenContext(tokenContext); + } + + private static List tokensConfig(Collection tokens) { + return tokens.stream() + .map(token -> new CloudTokenDataPlaneFilterConfig.Clients.Tokens.Builder() + .id(token.tokenId()) + .fingerprints(token.versions().stream().map(DataplaneToken.Version::fingerprint).toList()) + .checkAccessHashes(token.versions().stream().map(DataplaneToken.Version::checkAccessHash).toList()) + .expirations(token.versions().stream().map(v -> v.expiration().map(Instant::toString).orElse("")).toList())) + .toList(); + } + +} 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 d9b725bae40..a4a373a89a0 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 @@ -458,8 +458,9 @@ public class ContainerModelBuilder extends ConfigModelBuilder { addHostedImplicitHttpIfNotPresent(deployState, cluster); addHostedImplicitAccessControlIfNotPresent(deployState, cluster); addDefaultConnectorHostedFilterBinding(cluster); - addAdditionalHostedConnector(deployState, cluster); + addCloudMtlsConnector(deployState, cluster); addCloudDataPlaneFilter(deployState, cluster); + addCloudTokenSupport(deployState, cluster); } } @@ -596,7 +597,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder { .ifPresent(accessControl -> accessControl.configureDefaultHostedConnector(cluster.getHttp())); ; } - private void addAdditionalHostedConnector(DeployState state, ApplicationContainerCluster cluster) { + private void addCloudMtlsConnector(DeployState state, ApplicationContainerCluster cluster) { JettyHttpServer server = cluster.getHttp().getHttpServer().get(); String serverName = server.getComponentId().getName(); @@ -624,22 +625,6 @@ public class ContainerModelBuilder extends ConfigModelBuilder { .orElse(false); builder.clientAuth(needAuth ? SslClientAuth.NEED : SslClientAuth.WANT); } - - 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 - if (enableTokenSupport) { - cluster.addSimpleComponent(DataplaneProxyCredentials.class); - cluster.addSimpleComponent(DataplaneProxyService.class); - - var dataplaneProxy = new DataplaneProxy( - getMtlsDataplanePort(state), - endpointCert.certificate(), - endpointCert.key()); - cluster.addComponent(dataplaneProxy); - builder.tokenEndpoint(true); - } } else { builder.clientAuth(SslClientAuth.WANT_WITH_ENFORCER); } @@ -648,6 +633,46 @@ public class ContainerModelBuilder extends ConfigModelBuilder { server.addConnector(connectorFactory); } + private void addCloudTokenSupport(DeployState state, ApplicationContainerCluster cluster) { + var server = cluster.getHttp().getHttpServer().get(); + boolean enableTokenSupport = state.isHosted() && state.zone().system().isPublic() + && state.featureFlags().enableDataplaneProxy() + && cluster.getClients().stream().anyMatch(c -> !c.tokens().isEmpty()); + if (!enableTokenSupport) return; + var endpointCert = state.endpointCertificateSecrets().orElseThrow(); + int tokenPort = getTokenDataplanePort(state).orElseThrow(); + + // Set up component to generate proxy cert if token support is enabled + cluster.addSimpleComponent(DataplaneProxyCredentials.class); + cluster.addSimpleComponent(DataplaneProxyService.class); + var dataplaneProxy = new DataplaneProxy( + getMtlsDataplanePort(state), + endpointCert.certificate(), + endpointCert.key()); + cluster.addComponent(dataplaneProxy); + + // Setup dedicated connector + var connector = HostedSslConnectorFactory.builder(server.getComponentId().getName()+"-token", tokenPort) + .tokenEndpoint(true) + .proxyProtocol(false, false) + .endpointCertificate(endpointCert) + .remoteAddressHeader("X-Forwarded-For") + .remotePortHeader("X-Forwarded-Port") + .clientAuth(SslClientAuth.NEED) + .build(); + server.addConnector(connector); + + // Setup token filter chain + var tokenChain = new HttpFilterChain("cloud-token-data-plane-secure", HttpFilterChain.Type.SYSTEM); + tokenChain.addInnerComponent(new CloudTokenDataPlaneFilter(cluster, state)); + cluster.getHttp().getFilterChains().add(tokenChain); + + // Set as default filter for token port + cluster.getHttp().getHttpServer().orElseThrow().getConnectorFactories().stream() + .filter(c -> c.getListenPort() == tokenPort).findAny().orElseThrow() + .setDefaultRequestFilterChain(tokenChain.getComponentId()); + } + // Returns the client certificates of the clients defined for an application cluster private List getClientCertificates(ApplicationContainerCluster cluster) { return cluster.getClients() -- cgit v1.2.3