diff options
author | Morten Tokle <mortent@verizonmedia.com> | 2021-05-21 09:59:15 +0200 |
---|---|---|
committer | Morten Tokle <mortent@verizonmedia.com> | 2021-05-25 08:01:51 +0200 |
commit | 05c8138b4936a3144e8b95348ebc525148ba709f (patch) | |
tree | 4927e74bc5159c5ee401eb08388c1776b24907bb /config-model/src | |
parent | 7986918b9c04158010fbc0b7c68e76d2c3a53ed3 (diff) |
Include operator certificates in application trust store
Diffstat (limited to 'config-model/src')
3 files changed, 85 insertions, 1 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 e6bf3a835c6..3f77e2010ec 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 @@ -16,6 +16,7 @@ import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.Zone; import java.net.URI; +import java.security.cert.X509Certificate; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -59,6 +60,7 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea private String jvmOmitStackTraceInFastThrowOption; private int numDistributorStripes = 0; private boolean allowDisableMtls = true; + private List<X509Certificate> operatorCertificates = Collections.emptyList(); @Override public ModelContext.FeatureFlags featureFlags() { return this; } @Override public boolean multitenant() { return multitenant; } @@ -98,6 +100,7 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea @Override public String jvmOmitStackTraceInFastThrowOption(ClusterSpec.Type type) { return jvmOmitStackTraceInFastThrowOption; } @Override public int numDistributorStripes() { return numDistributorStripes; } @Override public boolean allowDisableMtls() { return allowDisableMtls; } + @Override public List<X509Certificate> operatorCertificates() { return operatorCertificates; } public TestProperties setFeedConcurrency(double feedConcurrency) { this.feedConcurrency = feedConcurrency; @@ -236,6 +239,11 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea return this; } + public TestProperties setOperatorCertificates(List<X509Certificate> operatorCertificates) { + this.operatorCertificates = List.copyOf(operatorCertificates); + 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/xml/ContainerModelBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java index b477587bcac..4ce0a9c9dbb 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 @@ -34,6 +34,7 @@ import com.yahoo.container.logging.FileConnectionLog; import com.yahoo.osgi.provider.model.ComponentModel; import com.yahoo.search.rendering.RendererRegistry; import com.yahoo.searchdefinition.derived.RankProfileList; +import com.yahoo.security.X509CertificateUtils; import com.yahoo.text.XML; import com.yahoo.vespa.defaults.Defaults; import com.yahoo.vespa.model.AbstractService; @@ -89,6 +90,7 @@ import org.w3c.dom.Element; import org.w3c.dom.Node; import java.net.URI; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -431,6 +433,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { // If the deployment contains certificate/private key reference, setup TLS port HostedSslConnectorFactory connectorFactory; + boolean enableHttp2 = deployState.featureFlags().enableJdiscHttp2(); if (deployState.endpointCertificateSecrets().isPresent()) { boolean authorizeClient = deployState.zone().system().isPublic(); if (authorizeClient && deployState.tlsClientAuthority().isEmpty()) { @@ -444,7 +447,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { .orElse(false); connectorFactory = authorizeClient - ? HostedSslConnectorFactory.withProvidedCertificateAndTruststore(serverName, endpointCertificateSecrets, deployState.tlsClientAuthority().get()) + ? HostedSslConnectorFactory.withProvidedCertificateAndTruststore(serverName, endpointCertificateSecrets, getTlsClientAuthorities(deployState)) : HostedSslConnectorFactory.withProvidedCertificate(serverName, endpointCertificateSecrets, enforceHandshakeClientAuth); } else { connectorFactory = HostedSslConnectorFactory.withDefaultCertificateAndTruststore(serverName); @@ -453,6 +456,19 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { server.addConnector(connectorFactory); } + /* + Return trusted certificates as a PEM encoded string containing the concatenation of + trusted certs from the application package and all operator certificates. + */ + String getTlsClientAuthorities(DeployState deployState) { + List<X509Certificate> trustedCertificates = deployState.tlsClientAuthority() + .map(X509CertificateUtils::certificateListFromPem) + .orElse(Collections.emptyList()); + ArrayList<X509Certificate> x509Certificates = new ArrayList<>(trustedCertificates); + x509Certificates.addAll(deployState.getProperties().operatorCertificates()); + return X509CertificateUtils.toPem(x509Certificates); + } + private static boolean isHostedTenantApplication(ConfigModelContext context) { var deployState = context.getDeployState(); boolean isTesterApplication = deployState.getProperties().applicationId().instance().isTester(); 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 7f862afa1b0..6649087f454 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 @@ -40,6 +40,11 @@ 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.KeyAlgorithm; +import com.yahoo.security.KeyUtils; +import com.yahoo.security.SignatureAlgorithm; +import com.yahoo.security.X509CertificateBuilder; +import com.yahoo.security.X509CertificateUtils; import com.yahoo.security.tls.TlsContext; import com.yahoo.vespa.defaults.Defaults; import com.yahoo.vespa.model.AbstractService; @@ -53,6 +58,7 @@ 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.CoreMatchers; import org.hamcrest.Matchers; import org.hamcrest.core.IsEqual; import org.junit.Rule; @@ -61,8 +67,14 @@ import org.junit.rules.TemporaryFolder; import org.w3c.dom.Element; import org.xml.sax.SAXException; +import javax.security.auth.x500.X500Principal; import java.io.IOException; import java.io.StringReader; +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.cert.X509Certificate; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -82,6 +94,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.Matchers.arrayContainingInAnyOrder; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; @@ -818,6 +831,53 @@ public class ContainerModelBuilderTest extends ContainerModelBuilderTestBase { } @Test + public void operator_certificates_are_joined_with_clients_pem() { + var applicationPackage = new MockApplicationPackage.Builder() + .withRoot(applicationFolder.getRoot()) + .build(); + + KeyPair key = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); + var applicationTrustCert = X509CertificateUtils.toPem( + X509CertificateBuilder + .fromKeypair(key, new X500Principal("CN=application"), Instant.now(), Instant.now().plus(1, ChronoUnit.DAYS), SignatureAlgorithm.SHA512_WITH_ECDSA, BigInteger.valueOf(1)) + .build()); + var operatorCert = X509CertificateBuilder + .fromKeypair(key, new X500Principal("CN=operator"), Instant.now(), Instant.now().plus(1, ChronoUnit.DAYS), SignatureAlgorithm.SHA512_WITH_ECDSA, BigInteger.valueOf(1)) + .build(); + + 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'>", |