package com.yahoo.vespa.model.application.validation; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.model.NullConfigModelRegistry; import com.yahoo.config.model.api.EndpointCertificateSecrets; import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.deploy.TestProperties; import com.yahoo.config.model.test.MockApplicationPackage; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.Zone; import com.yahoo.path.Path; import com.yahoo.security.KeyAlgorithm; import com.yahoo.security.KeyUtils; import com.yahoo.security.SignatureAlgorithm; import com.yahoo.security.X509CertificateBuilder; import com.yahoo.security.X509CertificateUtils; import com.yahoo.vespa.model.VespaModel; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.xml.sax.SAXException; import javax.security.auth.x500.X500Principal; import java.io.File; import java.io.IOException; import java.io.StringReader; import java.math.BigInteger; import java.security.KeyPair; import java.security.cert.X509Certificate; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Map; import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; public class CloudDataPlaneFilterValidatorTest { @TempDir public File applicationFolder; @Test void validator_accepts_distinct_client_certificates() throws IOException, SAXException { String certFile1 = "security/foo.pem"; String certFile2 = "security/bar.pem"; String servicesXml = """ """.formatted(certFile1, certFile2); DeployState deployState = createDeployState(servicesXml, Map.of( certFile1, List.of(createCertificate("foo")), certFile2, List.of(createCertificate("bar")))); VespaModel model = new VespaModel(new NullConfigModelRegistry(), deployState); new CloudDataPlaneFilterValidator().validate(model, deployState); } @Test void validator_rejects_duplicate_client_certificates_different_files() throws IOException, SAXException { String certFile1 = "security/a.pem"; String certFile2 = "security/b.pem"; X509Certificate certificate = createCertificate("a"); String servicesXml = """ """.formatted(certFile1, certFile2); DeployState deployState = createDeployState(servicesXml, Map.of( certFile1, List.of(certificate), certFile2, List.of(certificate))); VespaModel model = new VespaModel(new NullConfigModelRegistry(), deployState); IllegalArgumentException illegalArgumentException = Assertions.assertThrows(IllegalArgumentException.class, () -> new CloudDataPlaneFilterValidator().validate(model, deployState)); assertEquals( "Duplicate certificate(s) detected in files: [%s, %s]. Certificate subject of duplicates: [%s]".formatted(certFile1, certFile2, certificate.getSubjectX500Principal().getName()), illegalArgumentException.getMessage()); } @Test void validator_rejects_duplicate_client_certificates_same_file() throws IOException, SAXException { String certFile1 = "security/a.pem"; X509Certificate certificate = createCertificate("a"); String servicesXml = """ """.formatted(certFile1); DeployState deployState = createDeployState(servicesXml, Map.of(certFile1, List.of(certificate, certificate))); VespaModel model = new VespaModel(new NullConfigModelRegistry(), deployState); IllegalArgumentException illegalArgumentException = Assertions.assertThrows(IllegalArgumentException.class, () -> new CloudDataPlaneFilterValidator().validate(model, deployState)); assertEquals( "Duplicate certificate(s) detected in files: [%s]. Certificate subject of duplicates: [%s]".formatted(certFile1, certificate.getSubjectX500Principal().getName()), illegalArgumentException.getMessage()); } private DeployState createDeployState(String servicesXml, Map> certificates) { ApplicationPackage applicationPackage = new MockApplicationPackage.Builder() .withRoot(applicationFolder) .withServices(servicesXml) .build(); applicationPackage.getFile(Path.fromString("security")).createDirectory(); certificates.forEach((file, certList) -> applicationPackage.getFile(Path.fromString(file)).writeFile(new StringReader(X509CertificateUtils.toPem(certList)))); return new DeployState.Builder() .applicationPackage(applicationPackage) .properties( new TestProperties() .setEndpointCertificateSecrets(Optional.of(new EndpointCertificateSecrets("CERT", "KEY"))) .setHostedVespa(true)) .zone(new Zone(SystemName.PublicCd, Environment.dev, RegionName.defaultName())) .build(); } static X509Certificate createCertificate(String cn) throws IOException { KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); X500Principal subject = new X500Principal("CN=" + cn); return X509CertificateBuilder .fromKeypair( keyPair, subject, Instant.now(), Instant.now().plus(1, ChronoUnit.DAYS), SignatureAlgorithm.SHA512_WITH_ECDSA, BigInteger.valueOf(1)) .build(); } }