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();
}
}