diff options
author | Øyvind Grønnesby <oyving@verizonmedia.com> | 2019-08-14 14:46:50 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-08-14 14:46:50 +0200 |
commit | 73757bb8992ce337cb26640834f928577fe9125a (patch) | |
tree | 6341e9bf8d230a41e76781993e452648b1da8762 /config-model | |
parent | 0a4669d973810e02fd7a6be10b5a1ee03142d5b4 (diff) | |
parent | 6db214234310b8e2841d964c610cf5dcf01b747b (diff) |
Merge pull request #10238 from vespa-engine/ogronnesby/propagate-ca-in-config
Add the CA certificates to configuration and read it from app package
Diffstat (limited to 'config-model')
9 files changed, 116 insertions, 12 deletions
diff --git a/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java b/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java index 86c20bf96af..e8e12888768 100644 --- a/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java +++ b/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java @@ -24,6 +24,7 @@ import com.yahoo.config.model.provision.SingleNodeProvisioner; import com.yahoo.config.model.test.MockApplicationPackage; import com.yahoo.config.provision.Rotation; import com.yahoo.config.provision.Zone; +import com.yahoo.io.IOUtils; import com.yahoo.io.reader.NamedReader; import com.yahoo.searchdefinition.RankProfileRegistry; import com.yahoo.searchdefinition.SearchBuilder; @@ -39,8 +40,10 @@ import com.yahoo.vespa.model.container.search.SemanticRules; import com.yahoo.vespa.model.search.SearchDefinition; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.Reader; +import java.io.UncheckedIOException; import java.time.Instant; import java.util.Collection; import java.util.Collections; @@ -263,6 +266,22 @@ public class DeployState implements ConfigDefinitionStore { public Optional<TlsSecrets> tlsSecrets() { return properties.tlsSecrets(); } + public Optional<String> tlsClientAuthority() { + var caFile = applicationPackage.getClientSecurityFile(); + if (caFile.exists()) { + try { + var caPem = IOUtils.readAll(caFile.createReader()); + return Optional.of(caPem); + } catch (FileNotFoundException e) { + return Optional.empty(); + } catch (IOException e) { + throw new UncheckedIOException("Failed reading certificate from application: " + caFile.getPath(), e); + } + } else { + return Optional.empty(); + } + } + public static class Builder { private ApplicationPackage applicationPackage = MockApplicationPackage.createEmpty(); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainer.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainer.java index d27812a80dd..82f0c87d074 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainer.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainer.java @@ -26,11 +26,11 @@ public final class ApplicationContainer extends Container { private final boolean isHostedVespa; - public ApplicationContainer(AbstractConfigProducer parent, String name, int index, boolean isHostedVespa, Optional<TlsSecrets> tlsSecrets) { - this(parent, name, false, index, isHostedVespa, tlsSecrets); + public ApplicationContainer(AbstractConfigProducer parent, String name, int index, boolean isHostedVespa, Optional<TlsSecrets> tlsSecrets, Optional<String> tlsCa) { + this(parent, name, false, index, isHostedVespa, tlsSecrets, tlsCa); } - public ApplicationContainer(AbstractConfigProducer parent, String name, boolean retired, int index, boolean isHostedVespa, Optional<TlsSecrets> tlsSecrets) { + public ApplicationContainer(AbstractConfigProducer parent, String name, boolean retired, int index, boolean isHostedVespa, Optional<TlsSecrets> tlsSecrets, Optional<String> tlsCa) { super(parent, name, retired, index); this.isHostedVespa = isHostedVespa; @@ -40,8 +40,17 @@ public final class ApplicationContainer extends Container { JettyHttpServer server = Optional.ofNullable(getHttp()) .map(Http::getHttpServer) .orElse(getDefaultHttpServer()); - server.addConnector(new ConnectorFactory(connectorName, 4443, - new ConfiguredDirectSslProvider(server.getComponentId().getName(), tlsSecrets.get().key(), tlsSecrets.get().certificate(), null, null))); + + var sslProvider = new ConfiguredDirectSslProvider( + server.getComponentId().getName(), + tlsSecrets.get().key(), + tlsSecrets.get().certificate(), + null, + tlsCa.orElse(null), + null + ); + + server.addConnector(new ConnectorFactory(connectorName, 4443, sslProvider)); } addComponent(getFS4ResourcePool()); // TODO Remove when FS4 based search protocol is gone } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java index 473971c5e7a..2b9cd18d64b 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java @@ -56,6 +56,8 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat private ContainerModelEvaluation modelEvaluation; private Optional<TlsSecrets> tlsSecrets; + private Optional<String> tlsClientAuthority; + private boolean useTlsClientAuthority = false; private final boolean enableGroupingSessionCache; private MbusParams mbusParams; @@ -65,6 +67,7 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat super(parent, subId, name, deployState); this.tlsSecrets = deployState.tlsSecrets(); + this.tlsClientAuthority = deployState.tlsClientAuthority(); this.enableGroupingSessionCache = deployState.getProperties().enableGroupingSessionCache(); restApiGroup = new ConfigProducerGroup<>(this, "rest-api"); servletGroup = new ConfigProducerGroup<>(this, "servlet"); @@ -183,6 +186,10 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat return tlsSecrets; } + public Optional<String> getTlsClientAuthority() { + return tlsClientAuthority; + } + public boolean enableGroupingSessionCache() { return enableGroupingSessionCache; } @@ -205,6 +212,10 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat null)))); } + public void useTlsClientAuthority(boolean value) { + this.useTlsClientAuthority = value; + } + public static class MbusParams { // the amount of the maxpendingbytes to process concurrently, typically 0.2 (20%) final Double maxConcurrentFactor; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/ConfiguredDirectSslProvider.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/ConfiguredDirectSslProvider.java index 28dba3331d3..b47aa501ece 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/ConfiguredDirectSslProvider.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/ConfiguredDirectSslProvider.java @@ -26,9 +26,10 @@ public class ConfiguredDirectSslProvider extends SimpleComponent implements Conn private final String privateKey; private final String certificate; private final String caCertificatePath; + private final String caCertificate; private final ConnectorConfig.Ssl.ClientAuth.Enum clientAuthentication; - public ConfiguredDirectSslProvider(String servername, String privateKey, String certificate, String caCertificatePath, String clientAuthentication) { + public ConfiguredDirectSslProvider(String servername, String privateKey, String certificate, String caCertificatePath, String caCertificate, String clientAuthentication) { super(new ComponentModel( new BundleInstantiationSpecification(new ComponentId(COMPONENT_ID_PREFIX+servername), fromString(COMPONENT_CLASS), @@ -36,6 +37,7 @@ public class ConfiguredDirectSslProvider extends SimpleComponent implements Conn this.privateKey = privateKey; this.certificate = certificate; this.caCertificatePath = caCertificatePath; + this.caCertificate = caCertificate; this.clientAuthentication = mapToConfigEnum(clientAuthentication); } @@ -45,6 +47,7 @@ public class ConfiguredDirectSslProvider extends SimpleComponent implements Conn builder.ssl.privateKey(privateKey); builder.ssl.certificate(certificate); builder.ssl.caCertificateFile(Optional.ofNullable(caCertificatePath).orElse("")); + builder.ssl.caCertificate(Optional.ofNullable(caCertificate).orElse("")); builder.ssl.clientAuth(clientAuthentication); } 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 2bfb1da9dcb..00aa5423087 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 @@ -192,9 +192,21 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { addClientProviders(deployState, spec, cluster); addServerProviders(deployState, spec, cluster); + addTlsClientAuthority(deployState, spec, cluster); + addAthensCopperArgos(cluster, context); // Must be added after nodes. } + private void addTlsClientAuthority(DeployState deployState, Element spec, ApplicationContainerCluster cluster) { + var clientAuthorized = XML.getChild(spec, "client-authorize"); + if (clientAuthorized != null) { + if (deployState.tlsClientAuthority().isEmpty()) { + throw new RuntimeException("client-authorize set, but security/clients.pem is missing"); + } + cluster.useTlsClientAuthority(true); + } + } + private void addSecretStore(ApplicationContainerCluster cluster, Element spec) { Element secretStoreElement = XML.getChild(spec, "secret-store"); if (secretStoreElement != null) { @@ -440,7 +452,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { } private void addStandaloneNode(ApplicationContainerCluster cluster) { - ApplicationContainer container = new ApplicationContainer(cluster, "standalone", cluster.getContainers().size(), cluster.isHostedVespa(), cluster.getTlsSecrets()); + ApplicationContainer container = new ApplicationContainer(cluster, "standalone", cluster.getContainers().size(), cluster.isHostedVespa(), cluster.getTlsSecrets(), cluster.getTlsClientAuthority()); cluster.addContainers(Collections.singleton(container)); } @@ -506,7 +518,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { Element nodesElement = XML.getChild(containerElement, "nodes"); Element rotationsElement = XML.getChild(containerElement, "rotations"); if (nodesElement == null) { // default single node on localhost - ApplicationContainer node = new ApplicationContainer(cluster, "container.0", 0, cluster.isHostedVespa(), cluster.getTlsSecrets()); + ApplicationContainer node = new ApplicationContainer(cluster, "container.0", 0, cluster.isHostedVespa(), cluster.getTlsSecrets(), cluster.getTlsClientAuthority()); HostResource host = allocateSingleNodeHost(cluster, log, containerElement, context); node.setHostResource(host); node.initService(context.getDeployLogger()); @@ -695,7 +707,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { List<ApplicationContainer> nodes = new ArrayList<>(); for (Map.Entry<HostResource, ClusterMembership> entry : hosts.entrySet()) { String id = "container." + entry.getValue().index(); - ApplicationContainer container = new ApplicationContainer(cluster, id, entry.getValue().retired(), entry.getValue().index(), cluster.isHostedVespa(), cluster.getTlsSecrets()); + ApplicationContainer container = new ApplicationContainer(cluster, id, entry.getValue().retired(), entry.getValue().index(), cluster.isHostedVespa(), cluster.getTlsSecrets(), cluster.getTlsClientAuthority()); container.setHostResource(entry.getKey()); container.initService(deployLogger); nodes.add(container); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerServiceBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerServiceBuilder.java index 46271d3c0a2..c976a7fb153 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerServiceBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerServiceBuilder.java @@ -22,7 +22,14 @@ public class ContainerServiceBuilder extends VespaDomBuilder.DomConfigProducerBu @Override protected ApplicationContainer doBuild(DeployState deployState, AbstractConfigProducer parent, Element nodeElem) { - return new ApplicationContainer(parent, id, index, deployState.isHosted(), deployState.tlsSecrets()); + return new ApplicationContainer( + parent, + id, + index, + deployState.isHosted(), + deployState.tlsSecrets(), + deployState.tlsClientAuthority() + ); } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/Content.java b/config-model/src/main/java/com/yahoo/vespa/model/content/Content.java index 8eda707be99..f028f0ac0cc 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/Content.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/Content.java @@ -324,7 +324,7 @@ public class Content extends ConfigModel { if (!processedHosts.contains(host)) { String containerName = String.valueOf(searchNode.getDistributionKey()); ApplicationContainer docprocService = new ApplicationContainer(indexingCluster, containerName, index, - modelContext.getDeployState().isHosted(), modelContext.getDeployState().tlsSecrets()); + modelContext.getDeployState().isHosted(), modelContext.getDeployState().tlsSecrets(), Optional.empty()); index++; docprocService.useDynamicPorts(); docprocService.setHostResource(host); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java index ac85a958ed5..89169c44079 100755 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java @@ -252,7 +252,7 @@ public class ContainerClusterTest { } private static void addContainer(DeployLogger deployLogger, ApplicationContainerCluster cluster, String name, String hostName) { - ApplicationContainer container = new ApplicationContainer(cluster, name, 0, cluster.isHostedVespa(), cluster.getTlsSecrets()); + ApplicationContainer container = new ApplicationContainer(cluster, name, 0, cluster.isHostedVespa(), cluster.getTlsSecrets(), cluster.getTlsClientAuthority()); container.setHostResource(new HostResource(new Host(null, hostName))); container.initService(deployLogger); cluster.addContainer(container); 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 8b92e1091ca..e4fcf6305ed 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 @@ -25,6 +25,7 @@ import com.yahoo.container.servlet.ServletConfigConfig; import com.yahoo.container.usability.BindingsOverviewHandler; import com.yahoo.jdisc.http.ServletPathsConfig; 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.vespa.model.AbstractService; @@ -35,13 +36,18 @@ import com.yahoo.vespa.model.container.SecretStore; import com.yahoo.vespa.model.container.component.Component; import com.yahoo.vespa.model.content.utils.ContentClusterUtils; import com.yahoo.vespa.model.test.utils.VespaModelCreatorWithFilePkg; +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.File; import java.io.IOException; +import java.io.StringReader; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.logging.Level; import java.util.stream.Collectors; @@ -72,6 +78,8 @@ import static org.junit.Assert.fail; * @author gjoranv */ public class ContainerModelBuilderTest extends ContainerModelBuilderTestBase { + @Rule + public TemporaryFolder applicationFolder = new TemporaryFolder(); @Test public void deprecated_jdisc_tag_is_allowed() { @@ -645,6 +653,41 @@ public class ContainerModelBuilderTest extends ContainerModelBuilderTestBase { } @Test + public void client_ca_carts_fail_with_missing_clients_pem() { + Element clusterElem = DomBuilderTest.parse( + "<container version='1.0'>", + " <client-authorize />", + "</container>"); + try { + createModel(root, clusterElem); + } catch (RuntimeException e) { + assertEquals(e.getMessage(), "client-authorize set, but security/clients.pem is missing"); + return; + } + fail(); + } + + @Test + public void client_ca_carts_succeeds_with_client_authorize_and_clients_pem() { + 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'>", + " <client-authorize />", + "</container>"); + + createModel(root, deployState, null, clusterElem); + assertEquals(Optional.of("I am a very nice certificate"), getContainerCluster("container").getTlsClientAuthority()); + } + + @Test public void environment_vars_are_honoured() { Element clusterElem = DomBuilderTest.parse( "<container version='1.0'>", |