Skip to content

Commit

Permalink
Add opt encrypted communication for MSSQL
Browse files Browse the repository at this point in the history
  • Loading branch information
michalvavrik committed May 23, 2024
1 parent 374e6b9 commit cb36645
Show file tree
Hide file tree
Showing 18 changed files with 464 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,16 @@ protected ServiceContext getContext() {
@Override
public void init(Annotation annotation) {
Container metadata = (Container) annotation;
this.image = PropertiesUtils.resolveProperty(metadata.image());
this.command = metadata.command();
this.expectedLog = PropertiesUtils.resolveProperty(metadata.expectedLog());
this.port = metadata.port();
this.portDockerHostToLocalhost = metadata.portDockerHostToLocalhost();
init(metadata.image(), metadata.command(), metadata.expectedLog(), metadata.port(),
metadata.portDockerHostToLocalhost());
}

protected void init(String image, String[] command, String expectedLog, int port, boolean portDockerHostToLocalhost) {
this.image = PropertiesUtils.resolveProperty(image);
this.command = command;
this.expectedLog = PropertiesUtils.resolveProperty(expectedLog);
this.port = port;
this.portDockerHostToLocalhost = portDockerHostToLocalhost;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import static io.quarkus.test.utils.PropertiesUtils.RESOURCE_WITH_DESTINATION_PREFIX_MATCHER;
import static io.quarkus.test.utils.PropertiesUtils.RESOURCE_WITH_DESTINATION_SPLIT_CHAR;
import static io.quarkus.test.utils.PropertiesUtils.SECRET_PREFIX;
import static io.quarkus.test.utils.PropertiesUtils.SECRET_WITH_DESTINATION_PREFIX;
import static io.quarkus.test.utils.PropertiesUtils.SLASH;

import java.nio.file.Files;
Expand All @@ -28,6 +29,7 @@
import io.quarkus.test.logging.TestContainersLoggingHandler;
import io.quarkus.test.services.URILike;
import io.quarkus.test.utils.DockerUtils;
import io.quarkus.test.utils.FileUtils;

public abstract class DockerContainerManagedResource implements ManagedResource {

Expand Down Expand Up @@ -131,6 +133,11 @@ private Map<String, String> resolveProperties() {
if (isResource(entry.getValue())) {
value = entry.getValue().replace(RESOURCE_PREFIX, StringUtils.EMPTY);
addFileToContainer(value);
} else if (isSecretWithDestinationPath(entry.getValue())) {
value = entry.getValue().replace(SECRET_WITH_DESTINATION_PREFIX, StringUtils.EMPTY);
String destinationPath = value.split(RESOURCE_WITH_DESTINATION_SPLIT_CHAR)[0];
String fileName = value.split(RESOURCE_WITH_DESTINATION_SPLIT_CHAR)[1];
addFileToContainer(destinationPath, fileName);
} else if (isResourceWithDestinationPath(entry.getValue())) {
value = entry.getValue().replace(RESOURCE_WITH_DESTINATION_PREFIX, StringUtils.EMPTY);
if (!value.matches(RESOURCE_WITH_DESTINATION_PREFIX_MATCHER)) {
Expand Down Expand Up @@ -163,8 +170,17 @@ private void addFileToContainer(String filePath) {
}

private void addFileToContainer(String destinationPath, String hostFilePath) {
var filePath = FileUtils.findTargetFile(Path.of("target"), hostFilePath);
String containerFullPath = destinationPath + SLASH + hostFilePath;
innerContainer.withClasspathResourceMapping(hostFilePath, containerFullPath, BindMode.READ_ONLY);
if (filePath.isEmpty()) {
innerContainer.withClasspathResourceMapping(hostFilePath, containerFullPath, BindMode.READ_ONLY);
} else {
innerContainer.withCopyFileToContainer(MountableFile.forHostPath(filePath.get()), containerFullPath);
}
}

private static boolean isSecretWithDestinationPath(String key) {
return key.startsWith(SECRET_WITH_DESTINATION_PREFIX);
}

private boolean isResourceWithDestinationPath(String key) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public ServiceContext withTestScopeConfigProperty(String key, String value) {
return this;
}

Map<String, String> getConfigPropertiesWithTestScope() {
public Map<String, String> getConfigPropertiesWithTestScope() {
return configPropertiesWithTestScope;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.quarkus.test.scenarios.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.junit.jupiter.api.extension.ExtendWith;

@Inherited
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(DisabledOnFipsAndJava17Condition.class)
public @interface DisabledOnFipsAndJava17 {
/**
* Why is the annotated test class or test method disabled.
*/
String reason() default "";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.quarkus.test.scenarios.annotations;

import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionContext;

public class DisabledOnFipsAndJava17Condition implements ExecutionCondition {

/**
* We set environment variable "FIPS" to "fips" in our Jenkins jobs when FIPS are enabled.
*/
private static final String FIPS_ENABLED = "fips";
private static final int JAVA_17 = 17;

@Override
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
if (isFipsEnabledEnvironment() && isJava17()) {
return ConditionEvaluationResult.disabled("The test is running in FIPS enabled environment with Java 17");
}

return ConditionEvaluationResult.enabled("The test is not running in FIPS enabled environment with Java 17");
}

private static boolean isFipsEnabledEnvironment() {
return FIPS_ENABLED.equals(System.getenv().get("FIPS"));
}

private static boolean isJava17() {
return JAVA_17 == Runtime.version().feature();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,21 @@
import static io.quarkus.test.services.Certificate.Format.PKCS12;
import static io.quarkus.test.utils.PropertiesUtils.DESTINATION_TO_FILENAME_SEPARATOR;
import static io.quarkus.test.utils.PropertiesUtils.SECRET_WITH_DESTINATION_PREFIX;
import static io.quarkus.test.utils.TestExecutionProperties.isKubernetesPlatform;
import static io.quarkus.test.utils.TestExecutionProperties.isOpenshiftPlatform;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -27,7 +29,6 @@
import java.util.Random;
import java.util.stream.Collectors;

import io.quarkus.test.utils.FileUtils;
import me.escoffier.certs.CertificateGenerator;
import me.escoffier.certs.CertificateRequest;
import me.escoffier.certs.JksCertificateFiles;
Expand Down Expand Up @@ -60,27 +61,40 @@ interface PemCertificate extends Certificate {

}

static Certificate of(String prefix, io.quarkus.test.services.Certificate.Format format, String password, Path targetDir,
ContainerMountStrategy containerMountStrategy, boolean createPkcs12TsForPem) {
return of(new CertificateOptions(prefix, format, password, false, false, false,
new io.quarkus.test.services.Certificate.ClientCertificate[0],
targetDir, containerMountStrategy, createPkcs12TsForPem));
}

static Certificate of(String prefix, io.quarkus.test.services.Certificate.Format format, String password) {
return of(prefix, format, password, false, false, false, new io.quarkus.test.services.Certificate.ClientCertificate[0]);
return of(prefix, format, password, createCertsTempDir(prefix), new DefaultContainerMountStrategy(prefix), false);
}

static Certificate of(String prefix, io.quarkus.test.services.Certificate.Format format, String password,
boolean keystoreProps, boolean truststoreProps, boolean keystoreManagementInterfaceProps,
io.quarkus.test.services.Certificate.ClientCertificate[] clientCertificates) {
return of(new CertificateOptions(prefix, format, password, keystoreProps, truststoreProps,
keystoreManagementInterfaceProps,
clientCertificates, createCertsTempDir(prefix), new DefaultContainerMountStrategy(prefix), false));
}

private static Certificate of(CertificateOptions o) {
Map<String, String> props = new HashMap<>();
CertificateGenerator generator = new CertificateGenerator(createCertsTempDir(prefix), false);
CertificateGenerator generator = new CertificateGenerator(o.localTargetDir(), false);
String serverTrustStoreLocation = null;
String serverKeyStoreLocation = null;
String keyLocation = null;
String certLocation = null;
List<ClientCertificate> generatedClientCerts = new ArrayList<>();
String[] cnAttrs = collectCommonNames(clientCertificates);
var unknownClientCn = getUnknownClientCnAttr(clientCertificates, cnAttrs);
String[] cnAttrs = collectCommonNames(o.clientCertificates());
var unknownClientCn = getUnknownClientCnAttr(o.clientCertificates(), cnAttrs);

// 1. GENERATE FIRST CERTIFICATE AND SERVER KEYSTORE AND TRUSTSTORE
boolean withClientCerts = cnAttrs.length > 0;
String cn = withClientCerts ? cnAttrs[0] : "localhost";
final CertificateRequest request = createCertificateRequest(prefix, format, password, withClientCerts, cn);
final CertificateRequest request = createCertificateRequest(o.prefix(), o.format(), o.password(), withClientCerts, cn);
try {
var certFile = generator.generate(request).get(0);
if (certFile instanceof Pkcs12CertificateFiles pkcs12CertFile) {
Expand All @@ -96,17 +110,32 @@ static Certificate of(String prefix, io.quarkus.test.services.Certificate.Format
} else if (certFile instanceof PemCertificateFiles pemCertsFile) {
keyLocation = getPathOrNull(pemCertsFile.keyFile());
certLocation = getPathOrNull(pemCertsFile.certFile());
if (isOpenshiftPlatform() || isKubernetesPlatform()) {
if (o.createPkcs12TsForPem()) {
// PKCS12 truststore
serverTrustStoreLocation = createPkcs12TruststoreForPem(pemCertsFile.trustStore(), o.password(), cn);
} else {
// ca-cert
serverTrustStoreLocation = getPathOrNull(pemCertsFile.trustStore());
}
if (o.containerMountStrategy().mountToContainer()) {
if (certLocation != null) {
certLocation = makeFileMountPathUnique(prefix, certLocation);
// mount certificate to the pod
props.put(getRandomPropKey("crt"), toSecretProperty(certLocation));
var containerMountPath = o.containerMountStrategy().certPath(certLocation);
if (o.containerMountStrategy().containerShareMountPathWithApp()) {
certLocation = containerMountPath;
}

// mount certificate to the container
props.put(getRandomPropKey("crt"), toSecretProperty(containerMountPath));
}

if (keyLocation != null) {
keyLocation = makeFileMountPathUnique(prefix, keyLocation);
// mount private key to the pod
props.put(getRandomPropKey("key"), toSecretProperty(keyLocation));
var containerMountPath = o.containerMountStrategy().keyPath(keyLocation);
if (o.containerMountStrategy().containerShareMountPathWithApp()) {
keyLocation = containerMountPath;
}

// mount private key to the container
props.put(getRandomPropKey("key"), toSecretProperty(containerMountPath));
}
}
} else if (certFile instanceof JksCertificateFiles jksCertFile) {
Expand All @@ -126,18 +155,18 @@ static Certificate of(String prefix, io.quarkus.test.services.Certificate.Format

// 2. IF THERE IS MORE THAN ONE CLIENT CERTIFICATE, GENERATE OTHERS
if (withClientCerts && cnAttrs.length > 1) {
if (format != PKCS12) {
if (o.format() != PKCS12) {
throw new IllegalArgumentException(
"Generation of more than one client certificate is only implemented for PKCS12.");
}
var correctClientTruststore = Path.of(generatedClientCerts.get(0).truststorePath()).toFile();
for (int i = 1; i < cnAttrs.length; i++) {
var clientCn = cnAttrs[i];
var clientPrefix = clientCn + "-" + prefix;
var clientRequest = createCertificateRequest(clientPrefix, format, password, true, clientCn);
var clientPrefix = clientCn + "-" + o.prefix();
var clientRequest = createCertificateRequest(clientPrefix, o.format(), o.password(), true, clientCn);
try {
var clientCertFile = (Pkcs12CertificateFiles) generator.generate(clientRequest).get(0);
fixGeneratedClientCerts(clientPrefix, password, clientCertFile, correctClientTruststore,
fixGeneratedClientCerts(clientPrefix, o.password(), clientCertFile, correctClientTruststore,
serverTrustStoreLocation, unknownClientCn, clientCn);
generatedClientCerts
.add(new ClientCertificateImpl(clientCn, getPathOrNull(clientCertFile.clientKeyStoreFile()),
Expand All @@ -150,36 +179,45 @@ static Certificate of(String prefix, io.quarkus.test.services.Certificate.Format

// 3. PREPARE QUARKUS APPLICATION CONFIGURATION PROPERTIES
if (serverTrustStoreLocation != null) {
if (isOpenshiftPlatform() || isKubernetesPlatform()) {
// mount truststore to the pod
props.put(getRandomPropKey("truststore"), toSecretProperty(serverTrustStoreLocation));
if (o.containerMountStrategy().mountToContainer()) {
var containerMountPath = o.containerMountStrategy().truststorePath(serverTrustStoreLocation);
if (o.containerMountStrategy().containerShareMountPathWithApp()) {
serverTrustStoreLocation = containerMountPath;
}

// mount truststore to the container
props.put(getRandomPropKey("truststore"), toSecretProperty(containerMountPath));
}
if (truststoreProps) {
if (o.truststoreProps()) {
props.put("quarkus.http.ssl.certificate.trust-store-file", serverTrustStoreLocation);
props.put("quarkus.http.ssl.certificate.trust-store-file-type", format.toString());
props.put("quarkus.http.ssl.certificate.trust-store-password", password);
props.put("quarkus.http.ssl.certificate.trust-store-file-type", o.format().toString());
props.put("quarkus.http.ssl.certificate.trust-store-password", o.password());
}
}
if (serverKeyStoreLocation != null) {
if (isOpenshiftPlatform() || isKubernetesPlatform()) {
serverKeyStoreLocation = makeFileMountPathUnique(prefix, serverKeyStoreLocation);
// mount keystore to the pod
props.put(getRandomPropKey("keystore"), toSecretProperty(serverKeyStoreLocation));
if (o.containerMountStrategy().mountToContainer()) {
var containerMountPath = o.containerMountStrategy().keystorePath(serverKeyStoreLocation);
if (o.containerMountStrategy().containerShareMountPathWithApp()) {
serverKeyStoreLocation = containerMountPath;
}

// mount keystore to the container
props.put(getRandomPropKey("keystore"), toSecretProperty(containerMountPath));
}
if (keystoreProps) {
if (o.keystoreProps()) {
props.put("quarkus.http.ssl.certificate.key-store-file", serverKeyStoreLocation);
props.put("quarkus.http.ssl.certificate.key-store-file-type", format.toString());
props.put("quarkus.http.ssl.certificate.key-store-password", password);
props.put("quarkus.http.ssl.certificate.key-store-file-type", o.format().toString());
props.put("quarkus.http.ssl.certificate.key-store-password", o.password());
}
if (keystoreManagementInterfaceProps) {
if (o.keystoreManagementInterfaceProps()) {
props.put("quarkus.management.ssl.certificate.key-store-file", serverKeyStoreLocation);
props.put("quarkus.management.ssl.certificate.key-store-file-type", format.toString());
props.put("quarkus.management.ssl.certificate.key-store-password", password);
props.put("quarkus.management.ssl.certificate.key-store-file-type", o.format().toString());
props.put("quarkus.management.ssl.certificate.key-store-password", o.password());
}
}

return new CertificateImpl(serverKeyStoreLocation, serverTrustStoreLocation, Map.copyOf(props),
List.copyOf(generatedClientCerts), password, format.toString(), keyLocation, certLocation, prefix);
List.copyOf(generatedClientCerts), o.password(), o.format().toString(), keyLocation, certLocation, o.prefix());
}

private static String getUnknownClientCnAttr(io.quarkus.test.services.Certificate.ClientCertificate[] clientCertificates,
Expand Down Expand Up @@ -275,14 +313,7 @@ private static String getPathOrNull(Path file) {
return null;
}

private static String makeFileMountPathUnique(String prefix, String storeLocation) {
var newTempCertDir = createCertsTempDir(prefix);
var storeFile = Path.of(storeLocation).toFile();
FileUtils.copyFileTo(storeFile, newTempCertDir);
return newTempCertDir.resolve(storeFile.getName()).toAbsolutePath().toString();
}

private static Path createCertsTempDir(String prefix) {
static Path createCertsTempDir(String prefix) {
Path certsDir;
try {
certsDir = Files.createTempDirectory(prefix + "-certs");
Expand All @@ -291,4 +322,26 @@ private static Path createCertsTempDir(String prefix) {
}
return certsDir;
}

private static String createPkcs12TruststoreForPem(Path caCertPath, String password, String alias) {
try {
CertificateFactory fact = CertificateFactory.getInstance("X.509");
try (FileInputStream is = new FileInputStream(caCertPath.toFile())) {
X509Certificate cer = (X509Certificate) fact.generateCertificate(is);

var newTruststorePath = Files.createTempFile("pem-12-truststore", ".p12");

try (OutputStream truststoreOs = Files.newOutputStream(newTruststorePath)) {
KeyStore truststore = KeyStore.getInstance("PKCS12");
truststore.load(null, password.toCharArray());
truststore.setCertificateEntry(alias, cer);
truststore.store(truststoreOs, password.toCharArray());
}

return newTruststorePath.toAbsolutePath().toString();
}
} catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException e) {
throw new RuntimeException("Failed to create PKCS12 truststore", e);
}
}
}
Loading

0 comments on commit cb36645

Please sign in to comment.