summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessControlTest.java199
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessLogTest.java45
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java335
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTestBase.java17
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/container/xml/SecretStoreTest.java127
5 files changed, 385 insertions, 338 deletions
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessControlTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessControlTest.java
index d676dc29c94..2f962855470 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessControlTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessControlTest.java
@@ -2,10 +2,20 @@
package com.yahoo.vespa.model.container.xml;
import com.yahoo.component.ComponentId;
+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.AthenzDomain;
+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.ConnectorConfig;
+import com.yahoo.path.Path;
+import com.yahoo.security.X509CertificateUtils;
+import com.yahoo.security.tls.TlsContext;
import com.yahoo.vespa.defaults.Defaults;
import com.yahoo.vespa.model.container.ApplicationContainer;
import com.yahoo.vespa.model.container.http.AccessControl;
@@ -13,8 +23,14 @@ import com.yahoo.vespa.model.container.http.ConnectorFactory;
import com.yahoo.vespa.model.container.http.FilterChains;
import com.yahoo.vespa.model.container.http.Http;
import com.yahoo.vespa.model.container.http.ssl.HostedSslConnectorFactory;
+import org.hamcrest.Matchers;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.w3c.dom.Element;
+import java.io.StringReader;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -22,6 +38,8 @@ import java.util.Set;
import java.util.stream.Collectors;
import static com.yahoo.vespa.defaults.Defaults.getDefaults;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -30,9 +48,13 @@ import static org.junit.Assert.fail;
/**
* @author gjoranv
* @author bjorncs
+ * @author mortent
*/
public class AccessControlTest extends ContainerModelBuilderTestBase {
+ @Rule
+ public TemporaryFolder applicationFolder = new TemporaryFolder();
+
@Test
public void access_control_filter_chains_are_set_up() {
Http http = createModelAndGetHttp(
@@ -318,6 +340,182 @@ public class AccessControlTest extends ContainerModelBuilderTestBase {
assertEquals(AccessControl.DEFAULT_CONNECTOR_HOSTED_REQUEST_CHAIN_ID, defaultChain.get());
}
+ @Test
+ public void client_authentication_is_enforced() {
+ Element clusterElem = DomBuilderTest.parse(
+ "<container version='1.0'>",
+ nodesXml,
+ " <http><filtering>" +
+ " <access-control domain=\"vespa\" tls-handshake-client-auth=\"need\"/>" +
+ " </filtering></http>" +
+ "</container>" );
+
+ DeployState state = new DeployState.Builder().properties(
+ new TestProperties()
+ .setHostedVespa(true)
+ .setEndpointCertificateSecrets(Optional.of(new EndpointCertificateSecrets("CERT", "KEY"))))
+ .build();
+ createModel(root, state, null, clusterElem);
+ ApplicationContainer container = (ApplicationContainer)root.getProducer("container/container.0");
+
+ List<ConnectorFactory> connectorFactories = container.getHttp().getHttpServer().get().getConnectorFactories();
+ ConnectorFactory tlsPort = connectorFactories.stream().filter(connectorFactory -> connectorFactory.getListenPort() == 4443).findFirst().orElseThrow();
+
+ ConnectorConfig.Builder builder = new ConnectorConfig.Builder();
+ tlsPort.getConfig(builder);
+
+ ConnectorConfig connectorConfig = new ConnectorConfig(builder);
+ assertTrue(connectorConfig.ssl().enabled());
+ assertEquals(ConnectorConfig.Ssl.ClientAuth.Enum.NEED_AUTH, connectorConfig.ssl().clientAuth());
+ assertEquals("CERT", connectorConfig.ssl().certificate());
+ assertEquals("KEY", connectorConfig.ssl().privateKey());
+ assertEquals(4443, connectorConfig.listenPort());
+
+ assertEquals("Connector must use Athenz truststore in a non-public system.",
+ "/opt/yahoo/share/ssl/certs/athenz_certificate_bundle.pem",
+ connectorConfig.ssl().caCertificateFile());
+ assertTrue(connectorConfig.ssl().caCertificate().isEmpty());
+ }
+
+ @Test
+ public void missing_security_clients_pem_fails_in_public() {
+ Element clusterElem = DomBuilderTest.parse("<container version='1.0' />");
+
+ try {
+ DeployState state = new DeployState.Builder()
+ .properties(
+ new TestProperties()
+ .setHostedVespa(true)
+ .setEndpointCertificateSecrets(Optional.of(new EndpointCertificateSecrets("CERT", "KEY"))))
+ .zone(new Zone(SystemName.Public, Environment.prod, RegionName.defaultName()))
+ .build();
+ createModel(root, state, null, clusterElem);
+ } catch (RuntimeException e) {
+ assertEquals("Client certificate authority security/clients.pem is missing - see: https://cloud.vespa.ai/en/security-model#data-plane",
+ e.getMessage());
+ return;
+ }
+ fail();
+ }
+
+ @Test
+ public void security_clients_pem_is_picked_up() {
+ var applicationPackage = new MockApplicationPackage.Builder()
+ .withRoot(applicationFolder.getRoot())
+ .build();
+
+ applicationPackage.getFile(Path.fromString("security")).createDirectory();
+ applicationPackage.getFile(Path.fromString("security/clients.pem")).writeFile(new StringReader("I am a very nice certificate"));
+
+ var deployState = DeployState.createTestState(applicationPackage);
+
+ Element clusterElem = DomBuilderTest.parse("<container version='1.0' />");
+
+ createModel(root, deployState, null, clusterElem);
+ assertEquals(Optional.of("I am a very nice certificate"), getContainerCluster("container").getTlsClientAuthority());
+ }
+
+ @Test
+ public void operator_certificates_are_joined_with_clients_pem() {
+ var applicationPackage = new MockApplicationPackage.Builder()
+ .withRoot(applicationFolder.getRoot())
+ .build();
+
+ var applicationTrustCert = X509CertificateUtils.toPem(
+ X509CertificateUtils.createSelfSigned("CN=application", Duration.ofDays(1)).certificate());
+ var operatorCert = X509CertificateUtils.createSelfSigned("CN=operator", Duration.ofDays(1)).certificate();
+
+ applicationPackage.getFile(Path.fromString("security")).createDirectory();
+ applicationPackage.getFile(Path.fromString("security/clients.pem")).writeFile(new StringReader(applicationTrustCert));
+
+ var deployState = new DeployState.Builder().properties(
+ new TestProperties()
+ .setOperatorCertificates(List.of(operatorCert))
+ .setHostedVespa(true)
+ .setEndpointCertificateSecrets(Optional.of(new EndpointCertificateSecrets("CERT", "KEY"))))
+ .zone(new Zone(SystemName.PublicCd, Environment.dev, RegionName.defaultName()))
+ .applicationPackage(applicationPackage)
+ .build();
+
+ Element clusterElem = DomBuilderTest.parse("<container version='1.0' />");
+
+ createModel(root, deployState, null, clusterElem);
+
+ ApplicationContainer container = (ApplicationContainer)root.getProducer("container/container.0");
+ List<ConnectorFactory> connectorFactories = container.getHttp().getHttpServer().get().getConnectorFactories();
+ ConnectorFactory tlsPort = connectorFactories.stream().filter(connectorFactory -> connectorFactory.getListenPort() == 4443).findFirst().orElseThrow();
+
+ ConnectorConfig.Builder builder = new ConnectorConfig.Builder();
+ tlsPort.getConfig(builder);
+
+ ConnectorConfig connectorConfig = new ConnectorConfig(builder);
+ var caCerts = X509CertificateUtils.certificateListFromPem(connectorConfig.ssl().caCertificate());
+ assertEquals(2, caCerts.size());
+ List<String> certnames = caCerts.stream()
+ .map(cert -> cert.getSubjectX500Principal().getName())
+ .collect(Collectors.toList());
+ assertThat(certnames, containsInAnyOrder("CN=operator", "CN=application"));
+ }
+
+ @Test
+ public void require_allowed_ciphers() {
+ Element clusterElem = DomBuilderTest.parse(
+ "<container version='1.0'>",
+ nodesXml,
+ "</container>" );
+
+ DeployState state = new DeployState.Builder().properties(new TestProperties().setHostedVespa(true).setEndpointCertificateSecrets(Optional.of(new EndpointCertificateSecrets("CERT", "KEY")))).build();
+ createModel(root, state, null, clusterElem);
+ ApplicationContainer container = (ApplicationContainer)root.getProducer("container/container.0");
+
+ List<ConnectorFactory> connectorFactories = container.getHttp().getHttpServer().get().getConnectorFactories();
+ ConnectorFactory tlsPort = connectorFactories.stream().filter(connectorFactory -> connectorFactory.getListenPort() == 4443).findFirst().orElseThrow();
+ ConnectorConfig.Builder builder = new ConnectorConfig.Builder();
+ tlsPort.getConfig(builder);
+
+ ConnectorConfig connectorConfig = new ConnectorConfig(builder);
+
+ assertThat(connectorConfig.ssl().enabledCipherSuites(), containsInAnyOrder(TlsContext.ALLOWED_CIPHER_SUITES.toArray()));
+ }
+
+ @Test
+ public void providing_endpoint_certificate_secrets_opens_port_4443() {
+ Element clusterElem = DomBuilderTest.parse(
+ "<container version='1.0'>",
+ nodesXml,
+ "</container>" );
+
+ DeployState state = new DeployState.Builder().properties(new TestProperties().setHostedVespa(true).setEndpointCertificateSecrets(Optional.of(new EndpointCertificateSecrets("CERT", "KEY")))).build();
+ createModel(root, state, null, clusterElem);
+ ApplicationContainer container = (ApplicationContainer)root.getProducer("container/container.0");
+
+ // Verify that there are two connectors
+ List<ConnectorFactory> connectorFactories = container.getHttp().getHttpServer().get().getConnectorFactories();
+ assertEquals(2, connectorFactories.size());
+ List<Integer> ports = connectorFactories.stream()
+ .map(ConnectorFactory::getListenPort)
+ .collect(Collectors.toList());
+ assertThat(ports, Matchers.containsInAnyOrder(8080, 4443));
+
+ ConnectorFactory tlsPort = connectorFactories.stream().filter(connectorFactory -> connectorFactory.getListenPort() == 4443).findFirst().orElseThrow();
+
+ ConnectorConfig.Builder builder = new ConnectorConfig.Builder();
+ tlsPort.getConfig(builder);
+
+
+ ConnectorConfig connectorConfig = new ConnectorConfig(builder);
+ assertTrue(connectorConfig.ssl().enabled());
+ assertEquals(ConnectorConfig.Ssl.ClientAuth.Enum.WANT_AUTH, connectorConfig.ssl().clientAuth());
+ assertEquals("CERT", connectorConfig.ssl().certificate());
+ assertEquals("KEY", connectorConfig.ssl().privateKey());
+ assertEquals(4443, connectorConfig.listenPort());
+
+ assertEquals("Connector must use Athenz truststore in a non-public system.",
+ "/opt/yahoo/share/ssl/certs/athenz_certificate_bundle.pem",
+ connectorConfig.ssl().caCertificateFile());
+ assertTrue(connectorConfig.ssl().caCertificate().isEmpty());
+ }
+
private Http createModelAndGetHttp(String... httpElement) {
AthenzDomain tenantDomain = AthenzDomain.from("my-tenant-domain");
DeployState state = new DeployState.Builder().properties(
@@ -327,6 +525,7 @@ public class AccessControlTest extends ContainerModelBuilderTestBase {
.build();
return createModelAndGetHttp(state, httpElement);
}
+
private Http createModelAndGetHttp(DeployState state, String... httpElement) {
List<String> servicesXml = new ArrayList<>();
servicesXml.add("<container version='1.0'>");
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessLogTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessLogTest.java
index 0486d463279..81b6de8f2ee 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessLogTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessLogTest.java
@@ -2,7 +2,14 @@
package com.yahoo.vespa.model.container.xml;
import com.yahoo.component.ComponentId;
+import com.yahoo.config.application.api.ApplicationPackage;
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.Environment;
+import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Zone;
import com.yahoo.container.core.AccessLogConfig;
import com.yahoo.container.logging.ConnectionLogConfig;
import com.yahoo.container.logging.FileConnectionLog;
@@ -13,8 +20,12 @@ import com.yahoo.vespa.model.container.component.Component;
import org.junit.Test;
import org.w3c.dom.Element;
+import java.util.logging.Level;
+
+import static com.yahoo.config.model.test.TestUtil.joinLines;
import static com.yahoo.text.StringUtilities.quote;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -132,4 +143,38 @@ public class AccessLogTest extends ContainerModelBuilderTestBase {
Component<?, ?> fileConnectionLogComponent = getContainerComponent("default", FileConnectionLog.class.getName());
assertNull(fileConnectionLogComponent);
}
+
+ @Test
+ public void hosted_applications_get_a_log_warning_when_overriding_accesslog() {
+ String containerService = joinLines("<container id='foo' version='1.0'>",
+ " <accesslog type='json' fileNamePattern='logs/vespa/qrs/access.%Y%m%d%H%M%S' symlinkName='json_access' />",
+ " <nodes count=\"2\">",
+ " </nodes>",
+ "</container>");
+
+ String deploymentXml = joinLines("<deployment version='1.0'>",
+ " <prod>",
+ " <region>us-east-1</region>",
+ " </prod>",
+ "</deployment>");
+
+ ApplicationPackage applicationPackage = new MockApplicationPackage.Builder()
+ .withServices(containerService)
+ .withDeploymentSpec(deploymentXml)
+ .build();
+
+ TestLogger logger = new TestLogger();
+ DeployState deployState = new DeployState.Builder()
+ .applicationPackage(applicationPackage)
+ .zone(new Zone(Environment.prod, RegionName.from("us-east-1")))
+ .properties(new TestProperties().setHostedVespa(true))
+ .deployLogger(logger)
+ .build();
+ createModel(root, deployState, null, DomBuilderTest.parse(containerService));
+ assertFalse(logger.msgs.isEmpty());
+ assertEquals(Level.WARNING, logger.msgs.get(0).getFirst());
+ assertEquals("Applications are not allowed to override the 'accesslog' element",
+ logger.msgs.get(0).getSecond());
+ }
+
}
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java
index b65570d29cc..3a241d9607f 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java
@@ -7,9 +7,7 @@ import com.yahoo.config.application.api.ApplicationPackage;
import com.yahoo.config.model.NullConfigModelRegistry;
import com.yahoo.config.model.api.ApplicationClusterEndpoint;
import com.yahoo.config.model.api.ContainerEndpoint;
-import com.yahoo.config.model.api.EndpointCertificateSecrets;
import com.yahoo.config.model.api.ModelContext;
-import com.yahoo.config.model.api.TenantSecretStore;
import com.yahoo.config.model.builder.xml.test.DomBuilderTest;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.deploy.TestProperties;
@@ -21,7 +19,6 @@ import com.yahoo.config.model.test.MockRoot;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.Flavor;
import com.yahoo.config.provision.RegionName;
-import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.Zone;
import com.yahoo.config.provisioning.FlavorsConfig;
import com.yahoo.container.ComponentsConfig;
@@ -33,15 +30,10 @@ import com.yahoo.container.handler.VipStatusHandler;
import com.yahoo.container.handler.metrics.MetricsV2Handler;
import com.yahoo.container.handler.observability.ApplicationStatusHandler;
import com.yahoo.container.jdisc.JdiscBindingsConfig;
-import com.yahoo.container.jdisc.secretstore.SecretStoreConfig;
import com.yahoo.container.usability.BindingsOverviewHandler;
-import com.yahoo.jdisc.http.ConnectorConfig;
import com.yahoo.net.HostName;
-import com.yahoo.path.Path;
import com.yahoo.prelude.cluster.QrMonitorConfig;
import com.yahoo.search.config.QrStartConfig;
-import com.yahoo.security.X509CertificateUtils;
-import com.yahoo.security.tls.TlsContext;
import com.yahoo.vespa.defaults.Defaults;
import com.yahoo.vespa.model.AbstractService;
import com.yahoo.vespa.model.VespaModel;
@@ -49,25 +41,17 @@ import com.yahoo.vespa.model.container.ApplicationContainer;
import com.yahoo.vespa.model.container.ApplicationContainerCluster;
import com.yahoo.vespa.model.container.ContainerCluster;
import com.yahoo.vespa.model.container.ContainerModelEvaluation;
-import com.yahoo.vespa.model.container.SecretStore;
import com.yahoo.vespa.model.container.component.Component;
-import com.yahoo.vespa.model.container.http.ConnectorFactory;
import com.yahoo.vespa.model.content.utils.ContentClusterUtils;
import com.yahoo.vespa.model.test.VespaModelTester;
import com.yahoo.vespa.model.test.utils.VespaModelCreatorWithFilePkg;
-import org.hamcrest.Matchers;
-import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import java.io.IOException;
-import java.io.StringReader;
-import java.time.Duration;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.logging.Level;
@@ -80,11 +64,10 @@ import static com.yahoo.vespa.model.container.ContainerCluster.ROOT_HANDLER_BIND
import static com.yahoo.vespa.model.container.ContainerCluster.STATE_HANDLER_BINDING_1;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
-import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasItem;
-import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -103,9 +86,6 @@ import static org.junit.Assert.fail;
*/
public class ContainerModelBuilderTest extends ContainerModelBuilderTestBase {
- @Rule
- public TemporaryFolder applicationFolder = new TemporaryFolder();
-
@Test
public void model_evaluation_bundles_are_deployed() {
createBasicContainerModel();
@@ -556,6 +536,7 @@ public class ContainerModelBuilderTest extends ContainerModelBuilderTestBase {
assertEquals(50.0, qr.shutdown().timeout(), 0.00000000000001);
assertFalse(qr.shutdown().dumpHeapOnTimeout());
}
+
private QrConfig getQrConfig(ModelContext.Properties properties) throws IOException, SAXException {
String servicesXml =
"<services>" +
@@ -588,184 +569,6 @@ public class ContainerModelBuilderTest extends ContainerModelBuilderTestBase {
}
@Test
- public void secret_store_can_be_set_up() {
- Element clusterElem = DomBuilderTest.parse(
- "<container version='1.0'>",
- " <secret-store type='oath-ckms'>",
- " <group name='group1' environment='env1'/>",
- " </secret-store>",
- "</container>");
- createModel(root, clusterElem);
- SecretStore secretStore = getContainerCluster("container").getSecretStore().get();
- assertEquals("group1", secretStore.getGroups().get(0).name);
- assertEquals("env1", secretStore.getGroups().get(0).environment);
- }
-
- @Test
- public void cloud_secret_store_requires_configured_secret_store() {
- Element clusterElem = DomBuilderTest.parse(
- "<container version='1.0'>",
- " <secret-store type='cloud'>",
- " <store id='store'>",
- " <aws-parameter-store account='store1' region='eu-north-1'/>",
- " </store>",
- " </secret-store>",
- "</container>");
- try {
- DeployState state = new DeployState.Builder()
- .properties(new TestProperties().setHostedVespa(true))
- .zone(new Zone(SystemName.Public, Environment.prod, RegionName.defaultName()))
- .build();
- createModel(root, state, null, clusterElem);
- fail("secret store not defined");
- } catch (RuntimeException e) {
- assertEquals("No configured secret store named store1", e.getMessage());
- }
- }
-
-
- @Test
- public void cloud_secret_store_can_be_set_up() {
- Element clusterElem = DomBuilderTest.parse(
- "<container version='1.0'>",
- " <secret-store type='cloud'>",
- " <store id='store'>",
- " <aws-parameter-store account='store1' region='eu-north-1'/>",
- " </store>",
- " </secret-store>",
- "</container>");
-
- DeployState state = new DeployState.Builder()
- .properties(
- new TestProperties()
- .setHostedVespa(true)
- .setTenantSecretStores(List.of(new TenantSecretStore("store1", "1234", "role", Optional.of("externalid")))))
- .zone(new Zone(SystemName.Public, Environment.prod, RegionName.defaultName()))
- .build();
- createModel(root, state, null, clusterElem);
-
- ApplicationContainerCluster container = getContainerCluster("container");
- assertComponentConfigured(container, "com.yahoo.jdisc.cloud.aws.AwsParameterStore");
- CloudSecretStore secretStore = (CloudSecretStore) container.getComponentsMap().get(ComponentId.fromString("com.yahoo.jdisc.cloud.aws.AwsParameterStore"));
-
-
- SecretStoreConfig.Builder configBuilder = new SecretStoreConfig.Builder();
- secretStore.getConfig(configBuilder);
- SecretStoreConfig secretStoreConfig = configBuilder.build();
-
- assertEquals(1, secretStoreConfig.awsParameterStores().size());
- assertEquals("store1", secretStoreConfig.awsParameterStores().get(0).name());
- }
-
- @Test
- public void cloud_secret_store_fails_to_set_up_in_non_public_zone() {
- try {
- Element clusterElem = DomBuilderTest.parse(
- "<container version='1.0'>",
- " <secret-store type='cloud'>",
- " <store id='store'>",
- " <aws-parameter-store account='store1' region='eu-north-1'/>",
- " </store>",
- " </secret-store>",
- "</container>");
-
- DeployState state = new DeployState.Builder()
- .properties(
- new TestProperties()
- .setHostedVespa(true)
- .setTenantSecretStores(List.of(new TenantSecretStore("store1", "1234", "role", Optional.of("externalid")))))
- .zone(new Zone(SystemName.main, Environment.prod, RegionName.defaultName()))
- .build();
- createModel(root, state, null, clusterElem);
- } catch (RuntimeException e) {
- assertEquals("Cloud secret store is not supported in non-public system, see the documentation",
- e.getMessage());
- return;
- }
- fail();
- }
-
- @Test
- public void missing_security_clients_pem_fails_in_public() {
- Element clusterElem = DomBuilderTest.parse("<container version='1.0' />");
-
- try {
- DeployState state = new DeployState.Builder()
- .properties(
- new TestProperties()
- .setHostedVespa(true)
- .setEndpointCertificateSecrets(Optional.of(new EndpointCertificateSecrets("CERT", "KEY"))))
- .zone(new Zone(SystemName.Public, Environment.prod, RegionName.defaultName()))
- .build();
- createModel(root, state, null, clusterElem);
- } catch (RuntimeException e) {
- assertEquals("Client certificate authority security/clients.pem is missing - see: https://cloud.vespa.ai/en/security-model#data-plane",
- e.getMessage());
- return;
- }
- fail();
- }
-
- @Test
- public void security_clients_pem_is_picked_up() {
- var applicationPackage = new MockApplicationPackage.Builder()
- .withRoot(applicationFolder.getRoot())
- .build();
-
- applicationPackage.getFile(Path.fromString("security")).createDirectory();
- applicationPackage.getFile(Path.fromString("security/clients.pem")).writeFile(new StringReader("I am a very nice certificate"));
-
- var deployState = DeployState.createTestState(applicationPackage);
-
- Element clusterElem = DomBuilderTest.parse("<container version='1.0' />");
-
- createModel(root, deployState, null, clusterElem);
- assertEquals(Optional.of("I am a very nice certificate"), getContainerCluster("container").getTlsClientAuthority());
- }
-
- @Test
- public void operator_certificates_are_joined_with_clients_pem() {
- var applicationPackage = new MockApplicationPackage.Builder()
- .withRoot(applicationFolder.getRoot())
- .build();
-
- var applicationTrustCert = X509CertificateUtils.toPem(
- X509CertificateUtils.createSelfSigned("CN=application", Duration.ofDays(1)).certificate());
- var operatorCert = X509CertificateUtils.createSelfSigned("CN=operator", Duration.ofDays(1)).certificate();
-
- applicationPackage.getFile(Path.fromString("security")).createDirectory();
- applicationPackage.getFile(Path.fromString("security/clients.pem")).writeFile(new StringReader(applicationTrustCert));
-
- var deployState = new DeployState.Builder().properties(
- new TestProperties()
- .setOperatorCertificates(List.of(operatorCert))
- .setHostedVespa(true)
- .setEndpointCertificateSecrets(Optional.of(new EndpointCertificateSecrets("CERT", "KEY"))))
- .zone(new Zone(SystemName.PublicCd, Environment.dev, RegionName.defaultName()))
- .applicationPackage(applicationPackage)
- .build();
-
- Element clusterElem = DomBuilderTest.parse("<container version='1.0' />");
-
- createModel(root, deployState, null, clusterElem);
-
- ApplicationContainer container = (ApplicationContainer)root.getProducer("container/container.0");
- List<ConnectorFactory> connectorFactories = container.getHttp().getHttpServer().get().getConnectorFactories();
- ConnectorFactory tlsPort = connectorFactories.stream().filter(connectorFactory -> connectorFactory.getListenPort() == 4443).findFirst().orElseThrow();
-
- ConnectorConfig.Builder builder = new ConnectorConfig.Builder();
- tlsPort.getConfig(builder);
-
- ConnectorConfig connectorConfig = new ConnectorConfig(builder);
- var caCerts = X509CertificateUtils.certificateListFromPem(connectorConfig.ssl().caCertificate());
- assertEquals(2, caCerts.size());
- List<String> certnames = caCerts.stream()
- .map(cert -> cert.getSubjectX500Principal().getName())
- .collect(Collectors.toList());
- assertThat(certnames, containsInAnyOrder("CN=operator", "CN=application"));
- }
-
- @Test
public void environment_vars_are_honoured() {
Element clusterElem = DomBuilderTest.parse(
"<container version='1.0'>",
@@ -816,102 +619,6 @@ public class ContainerModelBuilderTest extends ContainerModelBuilderTestBase {
}
@Test
- public void requireThatProvidingEndpointCertificateSecretsOpensPort4443() {
- Element clusterElem = DomBuilderTest.parse(
- "<container version='1.0'>",
- nodesXml,
- "</container>" );
-
- DeployState state = new DeployState.Builder().properties(new TestProperties().setHostedVespa(true).setEndpointCertificateSecrets(Optional.of(new EndpointCertificateSecrets("CERT", "KEY")))).build();
- createModel(root, state, null, clusterElem);
- ApplicationContainer container = (ApplicationContainer)root.getProducer("container/container.0");
-
- // Verify that there are two connectors
- List<ConnectorFactory> connectorFactories = container.getHttp().getHttpServer().get().getConnectorFactories();
- assertEquals(2, connectorFactories.size());
- List<Integer> ports = connectorFactories.stream()
- .map(ConnectorFactory::getListenPort)
- .collect(Collectors.toList());
- assertThat(ports, Matchers.containsInAnyOrder(8080, 4443));
-
- ConnectorFactory tlsPort = connectorFactories.stream().filter(connectorFactory -> connectorFactory.getListenPort() == 4443).findFirst().orElseThrow();
-
- ConnectorConfig.Builder builder = new ConnectorConfig.Builder();
- tlsPort.getConfig(builder);
-
-
- ConnectorConfig connectorConfig = new ConnectorConfig(builder);
- assertTrue(connectorConfig.ssl().enabled());
- assertEquals(ConnectorConfig.Ssl.ClientAuth.Enum.WANT_AUTH, connectorConfig.ssl().clientAuth());
- assertEquals("CERT", connectorConfig.ssl().certificate());
- assertEquals("KEY", connectorConfig.ssl().privateKey());
- assertEquals(4443, connectorConfig.listenPort());
-
- assertEquals("Connector must use Athenz truststore in a non-public system.",
- "/opt/yahoo/share/ssl/certs/athenz_certificate_bundle.pem",
- connectorConfig.ssl().caCertificateFile());
- assertTrue(connectorConfig.ssl().caCertificate().isEmpty());
- }
-
- @Test
- public void requireThatClientAuthenticationIsEnforced() {
- Element clusterElem = DomBuilderTest.parse(
- "<container version='1.0'>",
- nodesXml,
- " <http><filtering>" +
- " <access-control domain=\"vespa\" tls-handshake-client-auth=\"need\"/>" +
- " </filtering></http>" +
- "</container>" );
-
- DeployState state = new DeployState.Builder().properties(
- new TestProperties()
- .setHostedVespa(true)
- .setEndpointCertificateSecrets(Optional.of(new EndpointCertificateSecrets("CERT", "KEY"))))
- .build();
- createModel(root, state, null, clusterElem);
- ApplicationContainer container = (ApplicationContainer)root.getProducer("container/container.0");
-
- List<ConnectorFactory> connectorFactories = container.getHttp().getHttpServer().get().getConnectorFactories();
- ConnectorFactory tlsPort = connectorFactories.stream().filter(connectorFactory -> connectorFactory.getListenPort() == 4443).findFirst().orElseThrow();
-
- ConnectorConfig.Builder builder = new ConnectorConfig.Builder();
- tlsPort.getConfig(builder);
-
- ConnectorConfig connectorConfig = new ConnectorConfig(builder);
- assertTrue(connectorConfig.ssl().enabled());
- assertEquals(ConnectorConfig.Ssl.ClientAuth.Enum.NEED_AUTH, connectorConfig.ssl().clientAuth());
- assertEquals("CERT", connectorConfig.ssl().certificate());
- assertEquals("KEY", connectorConfig.ssl().privateKey());
- assertEquals(4443, connectorConfig.listenPort());
-
- assertEquals("Connector must use Athenz truststore in a non-public system.",
- "/opt/yahoo/share/ssl/certs/athenz_certificate_bundle.pem",
- connectorConfig.ssl().caCertificateFile());
- assertTrue(connectorConfig.ssl().caCertificate().isEmpty());
- }
-
- @Test
- public void require_allowed_ciphers() {
- Element clusterElem = DomBuilderTest.parse(
- "<container version='1.0'>",
- nodesXml,
- "</container>" );
-
- DeployState state = new DeployState.Builder().properties(new TestProperties().setHostedVespa(true).setEndpointCertificateSecrets(Optional.of(new EndpointCertificateSecrets("CERT", "KEY")))).build();
- createModel(root, state, null, clusterElem);
- ApplicationContainer container = (ApplicationContainer)root.getProducer("container/container.0");
-
- List<ConnectorFactory> connectorFactories = container.getHttp().getHttpServer().get().getConnectorFactories();
- ConnectorFactory tlsPort = connectorFactories.stream().filter(connectorFactory -> connectorFactory.getListenPort() == 4443).findFirst().orElseThrow();
- ConnectorConfig.Builder builder = new ConnectorConfig.Builder();
- tlsPort.getConfig(builder);
-
- ConnectorConfig connectorConfig = new ConnectorConfig(builder);
-
- assertThat(connectorConfig.ssl().enabledCipherSuites(), containsInAnyOrder(TlsContext.ALLOWED_CIPHER_SUITES.toArray()));
- }
-
- @Test
public void cluster_with_zookeeper() {
Function<Integer, String> servicesXml = (nodeCount) -> "<container version='1.0' id='default'>" +
"<nodes count='" + nodeCount + "'/>" +
@@ -995,44 +702,6 @@ public class ContainerModelBuilderTest extends ContainerModelBuilderTestBase {
logger.msgs.get(1).getSecond());
}
- @Test
- public void logs_accesslog_not_overidable_in_hosted() {
- String containerService = joinLines("<container id='foo' version='1.0'>",
- " <accesslog type='json' fileNamePattern='logs/vespa/qrs/access.%Y%m%d%H%M%S' symlinkName='json_access' />",
- " <nodes count=\"2\">",
- " </nodes>",
- "</container>");
-
- String deploymentXml = joinLines("<deployment version='1.0'>",
- " <prod>",
- " <region>us-east-1</region>",
- " </prod>",
- "</deployment>");
-
- ApplicationPackage applicationPackage = new MockApplicationPackage.Builder()
- .withServices(containerService)
- .withDeploymentSpec(deploymentXml)
- .build();
-
- TestLogger logger = new TestLogger();
- DeployState deployState = new DeployState.Builder()
- .applicationPackage(applicationPackage)
- .zone(new Zone(Environment.prod, RegionName.from("us-east-1")))
- .properties(new TestProperties().setHostedVespa(true))
- .deployLogger(logger)
- .build();
- createModel(root, deployState, null, DomBuilderTest.parse(containerService));
- assertFalse(logger.msgs.isEmpty());
- assertEquals(Level.WARNING, logger.msgs.get(0).getFirst());
- assertEquals("Applications are not allowed to override the 'accesslog' element",
- logger.msgs.get(0).getSecond());
- }
-
- private void assertComponentConfigured(ApplicationContainerCluster cluster, String componentId) {
- Component<?, ?> component = cluster.getComponentsMap().get(ComponentId.fromString(componentId));
- assertNotNull(component);
- }
-
private void assertComponentConfigured(ApplicationContainer container, String id) {
assertTrue(container.getComponents().getComponents().stream().anyMatch(component -> id.equals(component.getComponentId().getName())));
}
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTestBase.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTestBase.java
index 7e8852ce85a..3d7b17d37e0 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTestBase.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTestBase.java
@@ -22,6 +22,8 @@ import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
+import static org.junit.Assert.assertNotNull;
+
/**
* Utility functions for testing the ContainerModelBuilder. Note that XML validation will
* not be done when using this class
@@ -51,6 +53,11 @@ public abstract class ContainerModelBuilderTestBase {
protected MockRoot root;
+ @Before
+ public void prepareTest() {
+ root = new MockRoot("root");
+ }
+
protected void createBasicContainerModel() {
Element clusterElem = DomBuilderTest.parse("<container id='default' version='1.0' />");
createModel(root, clusterElem);
@@ -83,11 +90,6 @@ public abstract class ContainerModelBuilderTestBase {
search.initializeSearchChains(Collections.emptyMap());
}
- @Before
- public void prepareTest() {
- root = new MockRoot("root");
- }
-
protected ComponentsConfig componentsConfig() {
return root.getConfig(ComponentsConfig.class, "default");
}
@@ -109,4 +111,9 @@ public abstract class ContainerModelBuilderTestBase {
ComponentId.fromString(componentId));
}
+ void assertComponentConfigured(ApplicationContainerCluster cluster, String componentId) {
+ Component<?, ?> component = cluster.getComponentsMap().get(ComponentId.fromString(componentId));
+ assertNotNull(component);
+ }
+
}
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/SecretStoreTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/SecretStoreTest.java
new file mode 100644
index 00000000000..8c6eee72cde
--- /dev/null
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/SecretStoreTest.java
@@ -0,0 +1,127 @@
+package com.yahoo.vespa.model.container.xml;
+
+import com.yahoo.component.ComponentId;
+import com.yahoo.config.model.api.TenantSecretStore;
+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.provision.Environment;
+import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.Zone;
+import com.yahoo.container.jdisc.secretstore.SecretStoreConfig;
+import com.yahoo.vespa.model.container.ApplicationContainerCluster;
+import com.yahoo.vespa.model.container.SecretStore;
+import org.junit.Test;
+import org.w3c.dom.Element;
+
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * @author tokle
+ */
+public class SecretStoreTest extends ContainerModelBuilderTestBase {
+
+ @Test
+ public void secret_store_can_be_set_up() {
+ Element clusterElem = DomBuilderTest.parse(
+ "<container version='1.0'>",
+ " <secret-store type='oath-ckms'>",
+ " <group name='group1' environment='env1'/>",
+ " </secret-store>",
+ "</container>");
+ createModel(root, clusterElem);
+ SecretStore secretStore = getContainerCluster("container").getSecretStore().get();
+ assertEquals("group1", secretStore.getGroups().get(0).name);
+ assertEquals("env1", secretStore.getGroups().get(0).environment);
+ }
+
+ @Test
+ public void cloud_secret_store_requires_configured_secret_store() {
+ Element clusterElem = DomBuilderTest.parse(
+ "<container version='1.0'>",
+ " <secret-store type='cloud'>",
+ " <store id='store'>",
+ " <aws-parameter-store account='store1' region='eu-north-1'/>",
+ " </store>",
+ " </secret-store>",
+ "</container>");
+ try {
+ DeployState state = new DeployState.Builder()
+ .properties(new TestProperties().setHostedVespa(true))
+ .zone(new Zone(SystemName.Public, Environment.prod, RegionName.defaultName()))
+ .build();
+ createModel(root, state, null, clusterElem);
+ fail("secret store not defined");
+ } catch (RuntimeException e) {
+ assertEquals("No configured secret store named store1", e.getMessage());
+ }
+ }
+
+
+ @Test
+ public void cloud_secret_store_can_be_set_up() {
+ Element clusterElem = DomBuilderTest.parse(
+ "<container version='1.0'>",
+ " <secret-store type='cloud'>",
+ " <store id='store'>",
+ " <aws-parameter-store account='store1' region='eu-north-1'/>",
+ " </store>",
+ " </secret-store>",
+ "</container>");
+
+ DeployState state = new DeployState.Builder()
+ .properties(
+ new TestProperties()
+ .setHostedVespa(true)
+ .setTenantSecretStores(List.of(new TenantSecretStore("store1", "1234", "role", Optional.of("externalid")))))
+ .zone(new Zone(SystemName.Public, Environment.prod, RegionName.defaultName()))
+ .build();
+ createModel(root, state, null, clusterElem);
+
+ ApplicationContainerCluster container = getContainerCluster("container");
+ assertComponentConfigured(container, "com.yahoo.jdisc.cloud.aws.AwsParameterStore");
+ CloudSecretStore secretStore = (CloudSecretStore) container.getComponentsMap().get(ComponentId.fromString("com.yahoo.jdisc.cloud.aws.AwsParameterStore"));
+
+
+ SecretStoreConfig.Builder configBuilder = new SecretStoreConfig.Builder();
+ secretStore.getConfig(configBuilder);
+ SecretStoreConfig secretStoreConfig = configBuilder.build();
+
+ assertEquals(1, secretStoreConfig.awsParameterStores().size());
+ assertEquals("store1", secretStoreConfig.awsParameterStores().get(0).name());
+ }
+
+ @Test
+ public void cloud_secret_store_fails_to_set_up_in_non_public_zone() {
+ try {
+ Element clusterElem = DomBuilderTest.parse(
+ "<container version='1.0'>",
+ " <secret-store type='cloud'>",
+ " <store id='store'>",
+ " <aws-parameter-store account='store1' region='eu-north-1'/>",
+ " </store>",
+ " </secret-store>",
+ "</container>");
+
+ DeployState state = new DeployState.Builder()
+ .properties(
+ new TestProperties()
+ .setHostedVespa(true)
+ .setTenantSecretStores(List.of(new TenantSecretStore("store1", "1234", "role", Optional.of("externalid")))))
+ .zone(new Zone(SystemName.main, Environment.prod, RegionName.defaultName()))
+ .build();
+ createModel(root, state, null, clusterElem);
+ } catch (RuntimeException e) {
+ assertEquals("Cloud secret store is not supported in non-public system, see the documentation",
+ e.getMessage());
+ return;
+ }
+ fail();
+ }
+
+}