aboutsummaryrefslogtreecommitdiffstats
path: root/config-model/src/main/java/com
diff options
context:
space:
mode:
authorMorten Tokle <mortent@yahooinc.com>2022-11-22 13:34:48 +0100
committerBjørn Christian Seime <bjorncs@yahooinc.com>2022-11-22 13:43:04 +0100
commit23ba858ac77a5735c84570a040a1498c044d2a3b (patch)
tree2525f113a78aca9d7dd5828dc6c594a4737a8220 /config-model/src/main/java/com
parentdd070e60aa612e806c83ce7115fce08d777ff910 (diff)
Generate config for clients
Diffstat (limited to 'config-model/src/main/java/com')
-rwxr-xr-xconfig-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java10
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/http/Client.java43
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/CloudDataPlaneFilter.java40
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java79
4 files changed, 165 insertions, 7 deletions
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java
index f5f25c65cd4..774e90c81f4 100755
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java
@@ -52,6 +52,7 @@ import com.yahoo.vespa.model.container.component.chain.ProcessingHandler;
import com.yahoo.vespa.model.container.configserver.ConfigserverCluster;
import com.yahoo.vespa.model.container.docproc.ContainerDocproc;
import com.yahoo.vespa.model.container.docproc.DocprocChains;
+import com.yahoo.vespa.model.container.http.Client;
import com.yahoo.vespa.model.container.http.Http;
import com.yahoo.vespa.model.container.processing.ProcessingChains;
import com.yahoo.vespa.model.container.search.ContainerSearch;
@@ -161,6 +162,7 @@ public abstract class ContainerCluster<CONTAINER extends Container>
private String jvmGCOptions = null;
private boolean deferChangesUntilRestart = false;
+ private List<Client> clients;
public ContainerCluster(AbstractConfigProducer<?> parent, String configSubId, String clusterId, DeployState deployState, boolean zooKeeperLocalhostAffinity) {
this(parent, configSubId, clusterId, deployState, zooKeeperLocalhostAffinity, 1);
@@ -352,6 +354,14 @@ public abstract class ContainerCluster<CONTAINER extends Container>
return http;
}
+ public void setClients(List<Client> clients) {
+ this.clients = clients;
+ }
+
+ public List<Client> getClients() {
+ return clients;
+ }
+
public ContainerDocproc getDocproc() {
return containerDocproc;
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/Client.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/Client.java
new file mode 100644
index 00000000000..f588f4f5962
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/Client.java
@@ -0,0 +1,43 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.model.container.http;
+
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+/**
+ * Represents a client. The client is identified by one of the provided certificates and have a set of permissions.
+ *
+ * @author mortent
+ */
+public class Client {
+ private String id;
+ private List<String> permissions;
+ private List<X509Certificate> certificates;
+
+ public Client(String id, List<String> permissions, List<X509Certificate> certificates) {
+ this.id = id;
+ this.permissions = permissions;
+ this.certificates = certificates;
+ }
+
+// public static Client createLegacyClient(List<X509Certificate> certificates) {
+// return new Client(true, "default", List.of(AclMapping.Action.READ.name(), AclMapping.Action.WRITE.name()), certificates);
+// }
+//
+// public static Client createClient(String id, List<String> permissions, List<X509Certificate> certificates) {
+// return new Client(false, id, permissions, certificates);
+// }
+//
+
+ public String id() {
+ return id;
+ }
+
+ public List<String> permissions() {
+ return permissions;
+ }
+
+ public List<X509Certificate> certificates() {
+ return certificates;
+ }
+}
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
new file mode 100644
index 00000000000..953b7f21985
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/CloudDataPlaneFilter.java
@@ -0,0 +1,40 @@
+// 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.container.bundle.BundleInstantiationSpecification;
+import com.yahoo.jdisc.http.filter.security.cloud.config.CloudDataPlaneFilterConfig;
+import com.yahoo.osgi.provider.model.ComponentModel;
+import com.yahoo.security.X509CertificateUtils;
+import com.yahoo.vespa.model.container.ApplicationContainerCluster;
+import com.yahoo.vespa.model.container.component.SimpleComponent;
+import com.yahoo.vespa.model.container.http.Client;
+
+import java.util.List;
+
+public class CloudDataPlaneFilter extends SimpleComponent implements CloudDataPlaneFilterConfig.Producer {
+
+ private static final String CLASS = "com.yahoo.jdisc.http.filter.security.cloud.CloudDataPlaneFilter";
+ private static final String BUNDLE = "jdisc-security-filters";
+
+ private final ApplicationContainerCluster cluster;
+ private final boolean legacyMode;
+
+ public CloudDataPlaneFilter(ApplicationContainerCluster cluster, boolean legacyMode) {
+ super(new ComponentModel(BundleInstantiationSpecification.fromStrings(CLASS, CLASS, BUNDLE)));
+ this.cluster = cluster;
+ this.legacyMode = legacyMode;
+ }
+
+ @Override
+ public void getConfig(CloudDataPlaneFilterConfig.Builder builder) {
+ List<Client> clients = cluster.getClients();
+ builder.legacyMode(legacyMode);
+ List<CloudDataPlaneFilterConfig.Clients.Builder> clientsList = clients.stream()
+ .map(x -> new CloudDataPlaneFilterConfig.Clients.Builder()
+ .id(x.id())
+ .certificates(X509CertificateUtils.toPem(x.certificates()))
+ .permissions(x.permissions()))
+ .toList();
+ builder.clients(clientsList);
+ }
+}
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 5dc937fe585..1301ab5055b 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
@@ -4,6 +4,7 @@ package com.yahoo.vespa.model.container.xml;
import com.google.common.collect.ImmutableList;
import com.yahoo.component.Version;
import com.yahoo.config.application.Xml;
+import com.yahoo.config.application.api.ApplicationFile;
import com.yahoo.config.application.api.ApplicationPackage;
import com.yahoo.config.application.api.DeployLogger;
import com.yahoo.config.application.api.DeploymentInstanceSpec;
@@ -30,8 +31,10 @@ import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.Zone;
+import com.yahoo.container.jdisc.AclMapping;
import com.yahoo.container.logging.FileConnectionLog;
import com.yahoo.osgi.provider.model.ComponentModel;
+import com.yahoo.path.Path;
import com.yahoo.schema.OnnxModel;
import com.yahoo.schema.derived.RankProfileList;
import com.yahoo.search.rendering.RendererRegistry;
@@ -72,6 +75,7 @@ import com.yahoo.vespa.model.container.component.UserBindingPattern;
import com.yahoo.vespa.model.container.docproc.ContainerDocproc;
import com.yahoo.vespa.model.container.docproc.DocprocChains;
import com.yahoo.vespa.model.container.http.AccessControl;
+import com.yahoo.vespa.model.container.http.Client;
import com.yahoo.vespa.model.container.http.ConnectorFactory;
import com.yahoo.vespa.model.container.http.FilterChains;
import com.yahoo.vespa.model.container.http.Http;
@@ -87,7 +91,10 @@ import com.yahoo.vespa.model.content.StorageGroup;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
+import java.io.IOException;
+import java.io.InputStream;
import java.net.URI;
+import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
@@ -201,6 +208,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
addStatusHandlers(cluster, context.getDeployState().isHosted());
addUserHandlers(deployState, cluster, spec, context);
+ addClients(deployState, spec, cluster, context);
addHttp(deployState, spec, cluster, context);
addAccessLogs(deployState, cluster, spec);
@@ -441,6 +449,54 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
}
}
+ protected void addClients(DeployState deployState, Element spec, ApplicationContainerCluster cluster, ConfigModelContext context) {
+ if (!deployState.isHosted() || !deployState.zone().system().isPublic() || !deployState.featureFlags().enableDataPlaneFilter()) return;
+
+ List<Client> clients;
+ Element clientsElement = XML.getChild(spec, "clients");
+ boolean legacyMode = false;
+ if (clientsElement == null) {
+ Client defaultClient = new Client("default",
+ List.of(AclMapping.Action.READ.name(), AclMapping.Action.WRITE.name()),
+ getCertificates(app.getFile(Path.fromString("security/clients.pem"))));
+ clients = List.of(defaultClient);
+ legacyMode = true;
+ } else {
+ clients = XML.getChildren(clientsElement, "client").stream()
+ .map(this::getCLient)
+ .toList();
+ }
+ cluster.setClients(clients);
+ cluster.addComponent(new CloudDataPlaneFilter(cluster, legacyMode));
+ }
+
+ private Client getCLient(Element clientElement) {
+ String id = XML.attribute("id", clientElement).orElseThrow();
+ List<String> permissions = XML.attribute("permissions", clientElement)
+ .map(p -> p.split(",")).stream()
+ .flatMap(Arrays::stream)
+ .toList();
+
+ List<X509Certificate> x509Certificates = XML.getChildren(clientElement, "certificate").stream()
+ .map(certElem -> Path.fromString(certElem.getAttribute("file")))
+ .map(path -> app.getFile(path))
+ .map(this::getCertificates)
+ .flatMap(Collection::stream)
+ .toList();
+ return new Client(id, permissions, x509Certificates);
+ }
+
+ private List<X509Certificate> getCertificates(ApplicationFile file) {
+ try {
+ InputStream inputStream = file.createInputStream();
+ byte[] bytes = inputStream.readAllBytes();
+ inputStream.close();
+ return X509CertificateUtils.certificateListFromPem(new String(bytes, StandardCharsets.UTF_8));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
private void addDefaultConnectorHostedFilterBinding(ApplicationContainerCluster cluster) {
cluster.getHttp().getAccessControl()
.ifPresent(accessControl -> accessControl.configureDefaultHostedConnector(cluster.getHttp())); ;
@@ -456,7 +512,10 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
boolean proxyProtocolMixedMode = deployState.getProperties().featureFlags().enableProxyProtocolMixedMode();
if (deployState.endpointCertificateSecrets().isPresent()) {
boolean authorizeClient = deployState.zone().system().isPublic();
- if (authorizeClient && deployState.tlsClientAuthority().isEmpty()) {
+ List<X509Certificate> clientCertificates = deployState.featureFlags().enableDataPlaneFilter()
+ ? getClientCertificates(cluster)
+ : deployState.tlsClientAuthority().map(X509CertificateUtils::certificateListFromPem).orElse(List.of());
+ if (authorizeClient && clientCertificates.isEmpty()) {
throw new IllegalArgumentException("Client certificate authority security/clients.pem is missing - " +
"see: https://cloud.vespa.ai/en/security-model#data-plane");
}
@@ -469,7 +528,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
connectorFactory = authorizeClient
? HostedSslConnectorFactory.withProvidedCertificateAndTruststore(
- serverName, endpointCertificateSecrets, getTlsClientAuthorities(deployState), tlsCiphersOverride, proxyProtocolMixedMode, HOSTED_VESPA_DATAPLANE_PORT)
+ serverName, endpointCertificateSecrets, getTlsClientAuthorities(clientCertificates, deployState), tlsCiphersOverride, proxyProtocolMixedMode, HOSTED_VESPA_DATAPLANE_PORT)
: HostedSslConnectorFactory.withProvidedCertificate(
serverName, endpointCertificateSecrets, enforceHandshakeClientAuth, tlsCiphersOverride, proxyProtocolMixedMode, HOSTED_VESPA_DATAPLANE_PORT);
} else {
@@ -479,15 +538,21 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
server.addConnector(connectorFactory);
}
+ // Returns the client certificates defined in
+ private List<X509Certificate> getClientCertificates(ApplicationContainerCluster cluster) {
+ return cluster.getClients()
+ .stream()
+ .map(Client::certificates)
+ .flatMap(Collection::stream)
+ .toList();
+ }
+
/*
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);
+ String getTlsClientAuthorities(List<X509Certificate> applicationCertificates, DeployState deployState) {
+ ArrayList<X509Certificate> x509Certificates = new ArrayList<>(applicationCertificates);
x509Certificates.addAll(deployState.getProperties().operatorCertificates());
return X509CertificateUtils.toPem(x509Certificates);
}