summaryrefslogtreecommitdiffstats
path: root/config-model/src/main/java/com
diff options
context:
space:
mode:
authorMorten Tokle <mortent@yahooinc.com>2022-12-01 11:19:16 +0100
committerGitHub <noreply@github.com>2022-12-01 11:19:16 +0100
commit0da74401d91e543f2f2505fd650e170792d58b31 (patch)
tree98839ee137dd2c30c41390a466e99c9289e964f3 /config-model/src/main/java/com
parentced78a22d4eb39927845db9a2446b988ba1f5802 (diff)
parenta17e5f4a90299dc9256d7b1f2aed7ebf51c7f082 (diff)
Merge pull request #25059 from vespa-engine/mortent/validate-clients
Add client validations
Diffstat (limited to 'config-model/src/main/java/com')
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/application/validation/CloudDataPlaneFilterValidator.java71
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java1
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/CertificateRemovalChangeValidator.java47
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java12
4 files changed, 125 insertions, 6 deletions
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/CloudDataPlaneFilterValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/CloudDataPlaneFilterValidator.java
new file mode 100644
index 00000000000..60705ad9b51
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/CloudDataPlaneFilterValidator.java
@@ -0,0 +1,71 @@
+package com.yahoo.vespa.model.application.validation;
+
+import com.yahoo.config.application.api.ApplicationPackage;
+import com.yahoo.config.model.deploy.DeployState;
+import com.yahoo.io.IOUtils;
+import com.yahoo.io.reader.NamedReader;
+import com.yahoo.path.Path;
+import com.yahoo.security.X509CertificateUtils;
+import com.yahoo.vespa.model.VespaModel;
+
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+public class CloudDataPlaneFilterValidator extends Validator {
+
+ private static final Logger log = Logger.getLogger(CloudDataPlaneFilterValidator.class.getName());
+
+ @Override
+ public void validate(VespaModel model, DeployState deployState) {
+ if (!deployState.isHosted()) return;
+ if (!deployState.zone().system().isPublic()) return;
+ if (!deployState.featureFlags().enableDataPlaneFilter()) return;
+
+ validateUniqueCertificates(deployState);
+ }
+
+ private void validateUniqueCertificates(DeployState deployState) {
+ List<NamedReader> certFiles = deployState.getApplicationPackage().getFiles(ApplicationPackage.SECURITY_DIR, ".pem");
+
+ Map<String, List<X509Certificate>> configuredCertificates = certFiles.stream()
+ .collect(Collectors.toMap(NamedReader::getName, CloudDataPlaneFilterValidator::readCertificates));
+
+ Set<X509Certificate> duplicates = new HashSet<>();
+ Set<X509Certificate> globalUniqueCerts = new HashSet<>();
+ for (Map.Entry<String, List<X509Certificate>> certificateFile : configuredCertificates.entrySet()) {
+ List<X509Certificate> duplicatesInFile = certificateFile.getValue().stream()
+ .filter(i -> !globalUniqueCerts.add(i))
+ .toList();
+ duplicates.addAll(duplicatesInFile);
+ }
+ if (!duplicates.isEmpty()) {
+ List<String> filesWithDuplicates = configuredCertificates.entrySet().stream()
+ .filter(entry -> entry.getValue().stream().anyMatch(duplicates::contains))
+ .map(Map.Entry::getKey)
+ .map(Path::fromString)
+ .map(Path::getName)
+ .map(p -> ApplicationPackage.SECURITY_DIR.append(p).getRelative())
+ .sorted()
+ .toList();
+ throw new IllegalArgumentException("Duplicate certificate(s) detected in files: %s. Certificate subject of duplicates: %s"
+ .formatted(filesWithDuplicates.toString(),
+ duplicates.stream().map(cert -> cert.getSubjectX500Principal().getName()).toList().toString()));
+ }
+ }
+
+ private static List<X509Certificate> readCertificates(NamedReader reader) {
+ try {
+ return X509CertificateUtils.certificateListFromPem(IOUtils.readAll(reader));
+ } catch (IOException e) {
+ log.warning("Exception reading certificate list from application package. File: %s, exception message: %s"
+ .formatted(reader.getName(), e.getMessage()));
+ throw new RuntimeException("Error reading certificates from application package", e);
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java
index 68b5adcc84f..b81b3524c67 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java
@@ -86,6 +86,7 @@ public class Validation {
new CloudWatchValidator().validate(model, deployState);
new QuotaValidator().validate(model, deployState);
new UriBindingsValidator().validate(model, deployState);
+ new CloudDataPlaneFilterValidator().validate(model, deployState);
additionalValidators.forEach(v -> v.validate(model, deployState));
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/CertificateRemovalChangeValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/CertificateRemovalChangeValidator.java
new file mode 100644
index 00000000000..50fd6572bcc
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/CertificateRemovalChangeValidator.java
@@ -0,0 +1,47 @@
+package com.yahoo.vespa.model.application.validation.change;
+
+import com.yahoo.config.application.api.ValidationId;
+import com.yahoo.config.application.api.ValidationOverrides;
+import com.yahoo.config.model.api.ConfigChangeAction;
+import com.yahoo.vespa.model.VespaModel;
+import com.yahoo.vespa.model.container.http.Client;
+
+import java.security.cert.X509Certificate;
+import java.time.Instant;
+import java.util.Collection;
+import java.util.List;
+
+public class CertificateRemovalChangeValidator implements ChangeValidator {
+ @Override
+ public List<ConfigChangeAction> validate(VespaModel current, VespaModel next, ValidationOverrides overrides, Instant now) {
+
+ current.getContainerClusters()
+ .forEach((clusterId, currentCluster) -> {
+ validateClients(clusterId, currentCluster.getClients(), next.getContainerClusters().get(clusterId).getClients(), overrides, now);
+ });
+
+ return List.of();
+ }
+
+ void validateClients(String clusterId, List<Client> current, List<Client> next, ValidationOverrides overrides, Instant now) {
+
+ List<X509Certificate> currentCertificates = current.stream()
+ .map(Client::certificates)
+ .flatMap(Collection::stream)
+ .toList();
+ List<X509Certificate> nextCertificates = next.stream()
+ .map(Client::certificates)
+ .flatMap(Collection::stream)
+ .toList();
+
+ List<X509Certificate> missingCerts = currentCertificates.stream().filter(cert -> !nextCertificates.contains(cert)).toList();
+ if (!missingCerts.isEmpty()) {
+ overrides.invalid(ValidationId.certificateRemoval,
+ "Data plane certificate(s) from cluster '" + clusterId + "' is removed " +
+ "(removed certificates: " + missingCerts.stream().map(x509Certificate -> x509Certificate.getSubjectX500Principal().getName()).toList() + ") " +
+ "This can cause client connection issues.",
+ now);
+ }
+
+ }
+}
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 79d8512e447..007e8401c70 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
@@ -38,6 +38,7 @@ import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.Zone;
import com.yahoo.container.bundle.BundleInstantiationSpecification;
import com.yahoo.container.logging.FileConnectionLog;
+import com.yahoo.io.IOUtils;
import com.yahoo.osgi.provider.model.ComponentModel;
import com.yahoo.path.Path;
import com.yahoo.schema.OnnxModel;
@@ -100,9 +101,8 @@ import org.w3c.dom.Element;
import org.w3c.dom.Node;
import java.io.IOException;
-import java.io.InputStream;
+import java.io.Reader;
import java.net.URI;
-import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
@@ -529,10 +529,10 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
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));
+ Reader reader = file.createReader();
+ String certPem = IOUtils.readAll(reader);
+ reader.close();
+ return X509CertificateUtils.certificateListFromPem(certPem);
} catch (IOException e) {
throw new RuntimeException(e);
}