diff options
14 files changed, 83 insertions, 21 deletions
diff --git a/config-model-api/abi-spec.json b/config-model-api/abi-spec.json index 9f86fe4dea2..8d83d16a272 100644 --- a/config-model-api/abi-spec.json +++ b/config-model-api/abi-spec.json @@ -111,6 +111,7 @@ "public java.util.List getQueryProfileFiles()", "public java.util.List getQueryProfileTypeFiles()", "public java.util.List getPageTemplateFiles()", + "public com.yahoo.config.application.api.ApplicationFile getClientSecurityFile()", "public abstract java.lang.String getHostSource()", "public abstract java.lang.String getServicesSource()", "public abstract java.util.Optional getDeployment()", @@ -148,6 +149,7 @@ "public static final com.yahoo.path.Path RULES_DIR", "public static final com.yahoo.path.Path DEPLOYMENT_FILE", "public static final com.yahoo.path.Path VALIDATION_OVERRIDES", + "public static final com.yahoo.path.Path SECURITY_DIR", "public static final java.lang.String SD_NAME_SUFFIX", "public static final java.lang.String RANKEXPRESSION_NAME_SUFFIX", "public static final java.lang.String RULES_NAME_SUFFIX", diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationPackage.java b/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationPackage.java index 06f8034453d..5cd119dcf65 100644 --- a/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationPackage.java +++ b/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationPackage.java @@ -27,7 +27,6 @@ import java.util.Map; import java.util.Optional; import java.util.jar.JarEntry; import java.util.jar.JarFile; -import java.util.stream.Collectors; /** * Represents an application package, that is, used as input when creating a VespaModel and as @@ -73,6 +72,8 @@ public interface ApplicationPackage { Path DEPLOYMENT_FILE = Path.fromString("deployment.xml"); Path VALIDATION_OVERRIDES = Path.fromString("validation-overrides.xml"); + Path SECURITY_DIR = Path.fromString("security"); + String SD_NAME_SUFFIX = ".sd"; String RANKEXPRESSION_NAME_SUFFIX = ".expression"; String RULES_NAME_SUFFIX = ".sr"; @@ -178,6 +179,9 @@ public interface ApplicationPackage { /** Does {@link #getFiles} on the page template directory and gets all xml files */ default List<NamedReader> getPageTemplateFiles() { return getFiles(PAGE_TEMPLATES_DIR,".xml"); } + /** Returns handle for the file containing client certificate authorities */ + default ApplicationFile getClientSecurityFile() { return getFile(SECURITY_DIR.append("clients.pem")); } + //For generating error messages String getHostSource(); String getServicesSource(); diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/TlsSecrets.java b/config-model-api/src/main/java/com/yahoo/config/model/api/TlsSecrets.java index 3cb4cedcbac..6a8b5a237ab 100644 --- a/config-model-api/src/main/java/com/yahoo/config/model/api/TlsSecrets.java +++ b/config-model-api/src/main/java/com/yahoo/config/model/api/TlsSecrets.java @@ -1,30 +1,30 @@ // Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.config.model.api; - public class TlsSecrets { +public class TlsSecrets { public static final TlsSecrets MISSING = new TlsSecrets(); - private final String certificate; + private final String certificate; private final String key; - private TlsSecrets() { - this(null,null); + private TlsSecrets() { + this(null, null); } - public TlsSecrets(String certificate, String key) { + public TlsSecrets(String certificate, String key) { this.certificate = certificate; this.key = key; } - public String certificate() { + public String certificate() { return certificate; } - public String key() { + public String key() { return key; } - public boolean isMissing() { + public boolean isMissing() { return this == MISSING; } } 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 977946cbf71..46e380e1ebd 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 48f7fa3c1a2..f00b617de46 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 @@ -22,11 +22,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; @@ -36,8 +36,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)); } } 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..5656299b302 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,7 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat private ContainerModelEvaluation modelEvaluation; private Optional<TlsSecrets> tlsSecrets; + private Optional<String> tlsClientAuthority; private final boolean enableGroupingSessionCache; private MbusParams mbusParams; @@ -65,6 +66,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 +185,10 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat return tlsSecrets; } + public Optional<String> getTlsClientAuthority() { + return tlsClientAuthority; + } + public boolean enableGroupingSessionCache() { return enableGroupingSessionCache; } 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..a7a4e0fb540 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 @@ -440,7 +440,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 +506,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 +695,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/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClient.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClient.java index 484124991d9..a99a2a2917d 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClient.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClient.java @@ -202,6 +202,9 @@ public class ZooKeeperClient { writeDir(app.getFile(ApplicationPackage.MODELS_GENERATED_REPLICATED_DIR), getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH).append(ApplicationPackage.MODELS_GENERATED_REPLICATED_DIR), true); + writeDir(app.getFile(ApplicationPackage.SECURITY_DIR), + getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH).append(ApplicationPackage.SECURITY_DIR), + true); } private void writeDir(ApplicationFile file, Path zooKeeperAppPath, boolean recurse) throws IOException { diff --git a/jdisc_http_service/abi-spec.json b/jdisc_http_service/abi-spec.json index f915dc1e8c1..b06250c4593 100644 --- a/jdisc_http_service/abi-spec.json +++ b/jdisc_http_service/abi-spec.json @@ -82,6 +82,7 @@ "public com.yahoo.jdisc.http.ConnectorConfig$Ssl$Builder certificateFile(java.lang.String)", "public com.yahoo.jdisc.http.ConnectorConfig$Ssl$Builder certificate(java.lang.String)", "public com.yahoo.jdisc.http.ConnectorConfig$Ssl$Builder caCertificateFile(java.lang.String)", + "public com.yahoo.jdisc.http.ConnectorConfig$Ssl$Builder caCertificate(java.lang.String)", "public com.yahoo.jdisc.http.ConnectorConfig$Ssl$Builder clientAuth(com.yahoo.jdisc.http.ConnectorConfig$Ssl$ClientAuth$Enum)", "public com.yahoo.jdisc.http.ConnectorConfig$Ssl build()" ], @@ -137,6 +138,7 @@ "public java.lang.String certificateFile()", "public java.lang.String certificate()", "public java.lang.String caCertificateFile()", + "public java.lang.String caCertificate()", "public com.yahoo.jdisc.http.ConnectorConfig$Ssl$ClientAuth$Enum clientAuth()" ], "fields": [] diff --git a/jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.connector.def b/jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.connector.def index c6c6fad345b..9b6fb5401e2 100644 --- a/jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.connector.def +++ b/jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.connector.def @@ -69,7 +69,14 @@ ssl.certificateFile string default="" ssl.certificate string default="" # with trusted CA certificates in PEM format. Used to verify clients +# - this is the name of a file on the local container file system +# - only one of caCertificateFile and caCertificate ssl.caCertificateFile string default="" +# with trusted CA certificates in PEM format. Used to verify clients +# - this is the actual certificates instead of a pointer to the file +# - only one of caCertificateFile and caCertificate +ssl.caCertificate string default="" + # Client authentication mode. See SSLEngine.getNeedClientAuth()/getWantClientAuth() for details. ssl.clientAuth enum { DISABLED, WANT_AUTH, NEED_AUTH } default=DISABLED |