summaryrefslogtreecommitdiffstats
path: root/config-model
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorncs@vespa.ai>2024-01-04 16:21:14 +0100
committerBjørn Christian Seime <bjorncs@vespa.ai>2024-01-05 09:44:02 +0100
commit0b091e54c3e5ff1c9afa0cc7ba6e8a4c73783fd9 (patch)
treeb3e7c9a2769015958478f68c4f77b99308681254 /config-model
parent690db20944acf2fa551c4b648959aae51496b40e (diff)
Add validator detecting applications that will fail on BouncyCastle v1.77
Diffstat (limited to 'config-model')
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/application/validation/CloudClientsValidator.java56
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java1
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/application/validation/CloudClientsValidatorTest.java50
-rw-r--r--config-model/src/test/resources/cloud-clients-validator/cert-with-empty-sequence-of-extensions.pem9
-rw-r--r--config-model/src/test/resources/cloud-clients-validator/valid-cert.pem9
5 files changed, 125 insertions, 0 deletions
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/CloudClientsValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/CloudClientsValidator.java
new file mode 100644
index 00000000000..2882489ef0a
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/CloudClientsValidator.java
@@ -0,0 +1,56 @@
+package com.yahoo.vespa.model.application.validation;
+
+import com.yahoo.config.model.deploy.DeployState;
+import org.bouncycastle.asn1.x509.TBSCertificate;
+
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.logging.Level;
+
+/**
+ * Validates that trusted data plane certificates are valid
+ *
+ * @author bjorncs
+ */
+public class CloudClientsValidator implements Validator {
+
+ @Override
+ public void validate(Validation.Context ctx) {
+ if (!ctx.deployState().isHosted()) return;
+ ctx.model().getContainerClusters().forEach((clusterName, cluster) -> {
+ for (var client : cluster.getClients()) {
+ client.certificates().forEach(cert -> validateCertificate(clusterName, client.id(), cert, ctx.deployState()));
+ }
+ });
+ }
+
+ static void validateCertificate(String clusterName, String clientId, X509Certificate cert, DeployState state) {
+ try {
+ var extensions = TBSCertificate.getInstance(cert.getTBSCertificate()).getExtensions();
+ if (extensions == null) return; // Certificate without any extensions is okay
+ if (extensions.getExtensionOIDs().length == 0) {
+ /*
+ BouncyCastle 1.77 no longer accepts certificates having an empty sequence of extensions.
+ Earlier releases violated the ASN.1 specification as the specification forbids empty extension sequence.
+ See https://github.com/bcgit/bc-java/issues/1479.
+
+ Detect such certificates and issue a warning for now.
+ Validation will be implicitly enforced once we upgrade BouncyCastle past 1.76.
+ */
+ var message = "The certificate's ASN.1 structure contains an empty sequence of extensions, " +
+ "which is a violation of the ASN.1 specification. " +
+ "Please update the application package with a new certificate, " +
+ "e.g by generating a new one using the Vespa CLI `$ vespa auth cert`. " +
+ "Such certificate will no longer be accepted in near future.";
+ state.getDeployLogger().log(Level.WARNING, errorMessage(clusterName, clientId, message));
+ }
+ } catch (CertificateEncodingException e) {
+ throw new IllegalArgumentException(errorMessage(clusterName, clientId, e.getMessage()), e);
+ }
+ }
+
+ private static String errorMessage(String clusterName, String clientId, String message) {
+ return "Client **%s** defined for cluster **%s** contains an invalid certificate: %s"
+ .formatted(clientId, clusterName, message);
+ }
+}
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 edaaf7b206d..dc7a2651e1f 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
@@ -113,6 +113,7 @@ public class Validation {
new JvmHeapSizeValidator().validate(execution);
new InfrastructureDeploymentValidator().validate(execution);
new EndpointCertificateSecretsValidator().validate(execution);
+ new CloudClientsValidator().validate(execution);
}
private static void validateFirstTimeDeployment(Execution execution) {
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/CloudClientsValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/CloudClientsValidatorTest.java
new file mode 100644
index 00000000000..cc6cb4e7f88
--- /dev/null
+++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/CloudClientsValidatorTest.java
@@ -0,0 +1,50 @@
+package com.yahoo.vespa.model.application.validation;
+
+import com.yahoo.config.model.deploy.DeployState;
+import com.yahoo.security.X509CertificateUtils;
+import com.yahoo.vespa.model.test.utils.DeployLoggerStub;
+import org.junit.jupiter.api.Test;
+
+import java.security.cert.X509Certificate;
+
+import static com.yahoo.yolean.Exceptions.uncheck;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * @author bjorncs
+ */
+class CloudClientsValidatorTest {
+
+ @Test
+ void logs_deployment_warning_on_certificate_with_empty_sequence_of_extensions() {
+ // Test should fail on BouncyCastle 1.77 or later
+
+ var logger = new DeployLoggerStub();
+ var state = new DeployState.Builder().deployLogger(logger).build();
+ var cert = readTestCertificate("cert-with-empty-sequence-of-extensions.pem");
+ CloudClientsValidator.validateCertificate("default", "my-feed-client", cert, state);
+ var expected = "Client **my-feed-client** defined for cluster **default** contains an invalid certificate: " +
+ "The certificate's ASN.1 structure contains an empty sequence of extensions, " +
+ "which is a violation of the ASN.1 specification. " +
+ "Please update the application package with a new certificate, " +
+ "e.g by generating a new one using the Vespa CLI `$ vespa auth cert`. " +
+ "Such certificate will no longer be accepted in near future.";
+ assertEquals(expected, logger.getLast().message);
+ }
+
+ @Test
+ void accepts_valid_certificate() {
+ var logger = new DeployLoggerStub();
+ var state = new DeployState.Builder().deployLogger(logger).build();
+ var cert = readTestCertificate("valid-cert.pem");
+ assertDoesNotThrow(() -> CloudClientsValidator.validateCertificate("default", "my-feed-client", cert, state));
+ assertEquals(0, logger.entries.size());
+ }
+
+ private static X509Certificate readTestCertificate(String filename) {
+ return X509CertificateUtils.fromPem(new String(uncheck(
+ () -> CloudClientsValidatorTest.class.getResourceAsStream(
+ "/cloud-clients-validator/%s".formatted(filename)).readAllBytes())));
+ }
+}
diff --git a/config-model/src/test/resources/cloud-clients-validator/cert-with-empty-sequence-of-extensions.pem b/config-model/src/test/resources/cloud-clients-validator/cert-with-empty-sequence-of-extensions.pem
new file mode 100644
index 00000000000..1942c12b28e
--- /dev/null
+++ b/config-model/src/test/resources/cloud-clients-validator/cert-with-empty-sequence-of-extensions.pem
@@ -0,0 +1,9 @@
+-----BEGIN CERTIFICATE-----
+MIIBPDCB46ADAgECAhEAhdEB3eHnsQxTdcYcClVpkzAKBggqhkjOPQQDAjAeMRww
+GgYDVQQDExNjbG91ZC52ZXNwYS5leGFtcGxlMB4XDTIyMTIyMTE4NTg0MloXDTMy
+MTIxODE4NTg0MlowHjEcMBoGA1UEAxMTY2xvdWQudmVzcGEuZXhhbXBsZTBZMBMG
+ByqGSM49AgEGCCqGSM49AwEHA0IABOPzpGlb+4HvgsRT5Ic6gmzYqAE2GQrgfi5z
+txf8yzoi5YqgEG6utFhjleQ5bUusDhMtrfOJoBL5VZxrQccmwsCjAjAAMAoGCCqG
+SM49BAMCA0gAMEUCIQCuNXMk5lsb9lF2IloYZB2wAHme/xAOyQ2arWzZf6BH2wIg
+dEsmbGhel9MLlfPVQjeUwCJha/XD7xfWW6IaL+hI5TQ=
+-----END CERTIFICATE-----
diff --git a/config-model/src/test/resources/cloud-clients-validator/valid-cert.pem b/config-model/src/test/resources/cloud-clients-validator/valid-cert.pem
new file mode 100644
index 00000000000..aebec508772
--- /dev/null
+++ b/config-model/src/test/resources/cloud-clients-validator/valid-cert.pem
@@ -0,0 +1,9 @@
+-----BEGIN CERTIFICATE-----
+MIIBNzCB3qADAgECAhB00FXOPRoixiJghCsT2FVOMAoGCCqGSM49BAMCMB4xHDAa
+BgNVBAMTE2Nsb3VkLnZlc3BhLmV4YW1wbGUwHhcNMjQwMTA0MTM0MTMwWhcNMzQw
+MTAxMTM0MTMwWjAeMRwwGgYDVQQDExNjbG91ZC52ZXNwYS5leGFtcGxlMFkwEwYH
+KoZIzj0CAQYIKoZIzj0DAQcDQgAEts/fYf1H+aOW4xZHtcxX2YvMWojzU4HvHw1b
+9Zc+7OoUcoqv9dTZMVaYj3J8Z3A73wNn5rhjPrI4sKtI5KN6sjAKBggqhkjOPQQD
+AgNIADBFAiEAqgs4QouJOf6ny48o5c6EZSTB3+iNyZr+23JXKwnYuUkCIFRtE736
+BJ5KdCPpI4jS611HgeLLlJmgF2524Gz4EpjH
+-----END CERTIFICATE-----