diff options
author | Morten Tokle <mortent@yahooinc.com> | 2022-12-01 11:19:16 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-12-01 11:19:16 +0100 |
commit | 0da74401d91e543f2f2505fd650e170792d58b31 (patch) | |
tree | 98839ee137dd2c30c41390a466e99c9289e964f3 /config-model/src/main/java/com | |
parent | ced78a22d4eb39927845db9a2446b988ba1f5802 (diff) | |
parent | a17e5f4a90299dc9256d7b1f2aed7ebf51c7f082 (diff) |
Merge pull request #25059 from vespa-engine/mortent/validate-clients
Add client validations
Diffstat (limited to 'config-model/src/main/java/com')
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); } |