aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorncs@yahooinc.com>2023-07-18 15:55:54 +0200
committerBjørn Christian Seime <bjorncs@yahooinc.com>2023-07-19 16:12:37 +0200
commit172128afece1d218ad16f4b7415c1f2bf9663d08 (patch)
tree65980fab05cbe6b62115d0775898bf9c3518d389
parentb630d4ed852ba0ad802667995f3f8238db2c9c3f (diff)
Add separate connector for token endpoint
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java7
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/CloudSslProvider.java12
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java2
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/CloudDataPlaneFilter.java18
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/CloudTokenDataPlaneFilter.java61
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java61
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/container/xml/CloudDataPlaneFilterTest.java45
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/container/xml/CloudTokenDataPlaneFilterTest.java105
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/ssl/impl/CloudTokenSslContextProvider.java (renamed from container-core/src/main/java/com/yahoo/jdisc/http/ssl/impl/CloudSslContextProvider.java)17
9 files changed, 228 insertions, 100 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 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<DataplaneToken> 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<DataplaneToken> 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<String> remoteAddressHeaders = new ArrayList<>();
final List<String> remotePortHeaders = new ArrayList<>();
SslClientAuth clientAuth;
- List<String> tlsCiphersOverride;
+ List<String> 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<Client> 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<CloudDataPlaneFilterConfig.Clients.Tokens.Builder> tokensConfig(Collection<DataplaneToken> 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("<none>")).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<Client> 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<CloudTokenDataPlaneFilterConfig.Clients.Tokens.Builder> tokensConfig(Collection<DataplaneToken> 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("<none>")).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<ContainerModel> {
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<ContainerModel> {
.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<ContainerModel> {
.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<ContainerModel> {
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<X509Certificate> getClientCertificates(ApplicationContainerCluster cluster) {
return cluster.getClients()
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/CloudDataPlaneFilterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/CloudDataPlaneFilterTest.java
index 02ff7b8a03f..94d92b355f9 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/CloudDataPlaneFilterTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/CloudDataPlaneFilterTest.java
@@ -6,7 +6,6 @@ import com.yahoo.config.model.builder.xml.test.DomBuilderTest;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.deploy.TestProperties;
import com.yahoo.config.model.test.MockApplicationPackage;
-import com.yahoo.config.provision.DataplaneToken;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
@@ -35,17 +34,14 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyPair;
import java.security.cert.X509Certificate;
-import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
-import java.util.Collection;
import java.util.List;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertIterableEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -89,7 +85,6 @@ public class CloudDataPlaneFilterTest extends ContainerModelBuilderTestBase {
CloudDataPlaneFilterConfig.Clients client = clients.get(0);
assertEquals("foo", client.id());
assertIterableEquals(List.of("read", "write"), client.permissions());
- assertTrue(client.tokens().isEmpty());
assertIterableEquals(List.of(X509CertificateUtils.toPem(certificate)), client.certificates());
ConnectorConfig connectorConfig = connectorConfig();
@@ -123,43 +118,6 @@ public class CloudDataPlaneFilterTest extends ContainerModelBuilderTestBase {
}
@Test
- void generates_correct_config_for_tokens() throws IOException {
- var certFile = securityFolder.resolve("foo.pem");
- var clusterElem = DomBuilderTest.parse(
- """
- <container version='1.0'>
- <clients>
- <client id="foo" permissions="read,write">
- <certificate file="%s"/>
- </client>
- <client id="bar" permissions="read">
- <token id="my-token"/>
- </client>
- </clients>
- </container>
- """
- .formatted(applicationFolder.toPath().relativize(certFile).toString()));
- createCertificate(certFile);
- buildModel(clusterElem);
-
- var cfg = root.getConfig(CloudDataPlaneFilterConfig.class, cloudDataPlaneFilterConfigId);
- var tokenClient = cfg.clients().stream().filter(c -> c.id().equals("bar")).findAny().orElse(null);
- assertNotNull(tokenClient);
- assertEquals(List.of("read"), tokenClient.permissions());
- assertTrue(tokenClient.certificates().isEmpty());
- var expectedTokenCfg = tokenConfig(
- "my-token", List.of("myfingerprint1", "myfingerprint2"), List.of("myaccesshash1", "myaccesshash2"),
- List.of("<none>", "2243-10-17T00:00:00Z"));
- assertEquals(List.of(expectedTokenCfg), tokenClient.tokens());
- }
-
- private static CloudDataPlaneFilterConfig.Clients.Tokens tokenConfig(
- String id, Collection<String> fingerprints, Collection<String> accessCheckHashes, Collection<String> expirations) {
- return new CloudDataPlaneFilterConfig.Clients.Tokens.Builder()
- .id(id).fingerprints(fingerprints).checkAccessHashes(accessCheckHashes).expirations(expirations).build();
- }
-
- @Test
public void it_rejects_files_without_certificates() throws IOException {
Path certFile = securityFolder.resolve("foo.pem");
Element clusterElem = DomBuilderTest.parse(
@@ -231,9 +189,6 @@ public class CloudDataPlaneFilterTest extends ContainerModelBuilderTestBase {
.properties(
new TestProperties()
.setEndpointCertificateSecrets(Optional.of(new EndpointCertificateSecrets("CERT", "KEY")))
- .setDataplaneTokens(List.of(new DataplaneToken("my-token", List.of(
- new DataplaneToken.Version("myfingerprint1", "myaccesshash1", Optional.empty()),
- new DataplaneToken.Version("myfingerprint2", "myaccesshash2", Optional.of(Instant.EPOCH.plus(Duration.ofDays(100000))))))))
.setHostedVespa(true))
.zone(new Zone(SystemName.PublicCd, Environment.dev, RegionName.defaultName()))
.build();
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/CloudTokenDataPlaneFilterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/CloudTokenDataPlaneFilterTest.java
new file mode 100644
index 00000000000..15e1d61c951
--- /dev/null
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/CloudTokenDataPlaneFilterTest.java
@@ -0,0 +1,105 @@
+// 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.config.model.api.EndpointCertificateSecrets;
+import com.yahoo.config.model.builder.xml.test.DomBuilderTest;
+import com.yahoo.config.model.deploy.DeployState;
+import com.yahoo.config.model.deploy.TestProperties;
+import com.yahoo.config.model.test.MockApplicationPackage;
+import com.yahoo.config.provision.DataplaneToken;
+import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.Zone;
+import com.yahoo.jdisc.http.filter.security.cloud.config.CloudTokenDataPlaneFilterConfig;
+import com.yahoo.vespa.model.container.ContainerModel;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.w3c.dom.Element;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+
+import static com.yahoo.vespa.model.container.xml.CloudDataPlaneFilterTest.createCertificate;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class CloudTokenDataPlaneFilterTest extends ContainerModelBuilderTestBase {
+
+ @TempDir
+ public File applicationFolder;
+
+ Path securityFolder;
+ private static final String filterConfigId = "container/filters/chain/cloud-token-data-plane-secure/component/" +
+ "com.yahoo.jdisc.http.filter.security.cloud.CloudTokenDataPlaneFilter";
+
+ @BeforeEach
+ public void setup() throws IOException {
+ securityFolder = applicationFolder.toPath().resolve("security");
+ Files.createDirectories(securityFolder);
+ }
+
+ @Test
+ void generates_correct_config_for_tokens() throws IOException {
+ var certFile = securityFolder.resolve("foo.pem");
+ var clusterElem = DomBuilderTest.parse(
+ """
+ <container version='1.0'>
+ <clients>
+ <client id="foo" permissions="read,write">
+ <certificate file="%s"/>
+ </client>
+ <client id="bar" permissions="read">
+ <token id="my-token"/>
+ </client>
+ </clients>
+ </container>
+ """
+ .formatted(applicationFolder.toPath().relativize(certFile).toString()));
+ createCertificate(certFile);
+ buildModel(clusterElem);
+
+ var cfg = root.getConfig(CloudTokenDataPlaneFilterConfig.class, filterConfigId);
+ var tokenClient = cfg.clients().stream().filter(c -> c.id().equals("bar")).findAny().orElse(null);
+ assertNotNull(tokenClient);
+ assertEquals(List.of("read"), tokenClient.permissions());
+ var expectedTokenCfg = tokenConfig(
+ "my-token", List.of("myfingerprint1", "myfingerprint2"), List.of("myaccesshash1", "myaccesshash2"),
+ List.of("<none>", "2243-10-17T00:00:00Z"));
+ assertEquals(List.of(expectedTokenCfg), tokenClient.tokens());
+ }
+
+ private static CloudTokenDataPlaneFilterConfig.Clients.Tokens tokenConfig(
+ String id, Collection<String> fingerprints, Collection<String> accessCheckHashes, Collection<String> expirations) {
+ return new CloudTokenDataPlaneFilterConfig.Clients.Tokens.Builder()
+ .id(id).fingerprints(fingerprints).checkAccessHashes(accessCheckHashes).expirations(expirations).build();
+ }
+
+ public List<ContainerModel> buildModel(Element... clusterElem) {
+ var applicationPackage = new MockApplicationPackage.Builder()
+ .withRoot(applicationFolder)
+ .build();
+
+ DeployState state = new DeployState.Builder()
+ .applicationPackage(applicationPackage)
+ .properties(
+ new TestProperties()
+ .setEnableDataplaneProxy(true)
+ .setEndpointCertificateSecrets(Optional.of(new EndpointCertificateSecrets("CERT", "KEY")))
+ .setDataplaneTokens(List.of(new DataplaneToken("my-token", List.of(
+ new DataplaneToken.Version("myfingerprint1", "myaccesshash1", Optional.empty()),
+ new DataplaneToken.Version("myfingerprint2", "myaccesshash2", Optional.of(Instant.EPOCH.plus(Duration.ofDays(100000))))))))
+ .setHostedVespa(true))
+ .zone(new Zone(SystemName.PublicCd, Environment.dev, RegionName.defaultName()))
+ .build();
+ return createModel(root, state, null, clusterElem);
+ }
+}
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/CloudTokenSslContextProvider.java
index cdfd4aa938e..fe71d1b24c6 100644
--- 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/CloudTokenSslContextProvider.java
@@ -1,6 +1,7 @@
// 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.component.annotation.Inject;
import com.yahoo.jdisc.http.ConnectorConfig;
import com.yahoo.jdisc.http.server.jetty.DataplaneProxyCredentials;
@@ -14,29 +15,23 @@ import java.util.Optional;
*
* @author mortent
*/
-public class CloudSslContextProvider extends ConfiguredSslContextFactoryProvider {
+public class CloudTokenSslContextProvider extends ConfiguredSslContextFactoryProvider {
private final DataplaneProxyCredentials dataplaneProxyCredentials;
- public CloudSslContextProvider(ConnectorConfig connectorConfig, DataplaneProxyCredentials dataplaneProxyCredentials) {
+ @Inject
+ public CloudTokenSslContextProvider(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);
+ return Optional.of(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);
- }
}
}