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 22, 2024
1 parent 36560af commit 1e28035
Show file tree
Hide file tree
Showing 17 changed files with 429 additions and 32 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,30 @@
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";

@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 17 == Runtime.version().feature();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@
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 Down Expand Up @@ -60,15 +64,33 @@ interface PemCertificate extends Certificate {

}

static Certificate of(String prefix, io.quarkus.test.services.Certificate.Format format, String password, Path targetDir,
ContainerMountPathStrategy containerMountPathStrategy, boolean mountToContainer, String cn,
boolean createPkcs12TsForPem) {
return of(prefix, format, password, false, false, false, new io.quarkus.test.services.Certificate.ClientCertificate[0],
targetDir, containerMountPathStrategy, mountToContainer, cn, 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 DefaultContainerMountPathStrategy(prefix),
isOpenshiftPlatform() || isKubernetesPlatform(), null, 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(prefix, format, password, keystoreProps, truststoreProps, keystoreManagementInterfaceProps,
clientCertificates, createCertsTempDir(prefix), new DefaultContainerMountPathStrategy(prefix),
isOpenshiftPlatform() || isKubernetesPlatform(), null, 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, Path localTargetDir,
ContainerMountPathStrategy containerMountPathStrategy, boolean mountToContainer, String cn,
boolean createPkcs12TsForPem) {
Map<String, String> props = new HashMap<>();
CertificateGenerator generator = new CertificateGenerator(createCertsTempDir(prefix), false);
CertificateGenerator generator = new CertificateGenerator(localTargetDir, false);
String serverTrustStoreLocation = null;
String serverKeyStoreLocation = null;
String keyLocation = null;
Expand All @@ -79,7 +101,9 @@ static Certificate of(String prefix, io.quarkus.test.services.Certificate.Format

// 1. GENERATE FIRST CERTIFICATE AND SERVER KEYSTORE AND TRUSTSTORE
boolean withClientCerts = cnAttrs.length > 0;
String cn = withClientCerts ? cnAttrs[0] : "localhost";
if (cn == null) {
cn = withClientCerts ? cnAttrs[0] : "localhost";
}
final CertificateRequest request = createCertificateRequest(prefix, format, password, withClientCerts, cn);
try {
var certFile = generator.generate(request).get(0);
Expand All @@ -96,17 +120,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 (createPkcs12TsForPem) {
// PKCS12 truststore
serverTrustStoreLocation = createPkcs12TruststoreForPem(pemCertsFile.trustStore(), password, cn);
} else {
// ca-cert
serverTrustStoreLocation = getPathOrNull(pemCertsFile.trustStore());
}
if (mountToContainer) {
if (certLocation != null) {
certLocation = makeFileMountPathUnique(prefix, certLocation);
// mount certificate to the pod
props.put(getRandomPropKey("crt"), toSecretProperty(certLocation));
var containerMountPath = containerMountPathStrategy.certPath(certLocation);
if (containerMountPathStrategy.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 = containerMountPathStrategy.keyPath(keyLocation);
if (containerMountPathStrategy.containerShareMountPathWithApp()) {
keyLocation = containerMountPath;
}

// mount private key to the container
props.put(getRandomPropKey("key"), toSecretProperty(containerMountPath));
}
}
} else if (certFile instanceof JksCertificateFiles jksCertFile) {
Expand Down Expand Up @@ -150,9 +189,14 @@ 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 (mountToContainer) {
var containerMountPath = containerMountPathStrategy.truststorePath(serverTrustStoreLocation);
if (containerMountPathStrategy.containerShareMountPathWithApp()) {
serverTrustStoreLocation = containerMountPath;
}

// mount truststore to the container
props.put(getRandomPropKey("truststore"), toSecretProperty(containerMountPath));
}
if (truststoreProps) {
props.put("quarkus.http.ssl.certificate.trust-store-file", serverTrustStoreLocation);
Expand All @@ -161,10 +205,14 @@ static Certificate of(String prefix, io.quarkus.test.services.Certificate.Format
}
}
if (serverKeyStoreLocation != null) {
if (isOpenshiftPlatform() || isKubernetesPlatform()) {
serverKeyStoreLocation = makeFileMountPathUnique(prefix, serverKeyStoreLocation);
// mount keystore to the pod
props.put(getRandomPropKey("keystore"), toSecretProperty(serverKeyStoreLocation));
if (mountToContainer) {
var containerMountPath = containerMountPathStrategy.keystorePath(serverKeyStoreLocation);
if (containerMountPathStrategy.containerShareMountPathWithApp()) {
serverKeyStoreLocation = containerMountPath;
}

// mount keystore to the container
props.put(getRandomPropKey("keystore"), toSecretProperty(containerMountPath));
}
if (keystoreProps) {
props.put("quarkus.http.ssl.certificate.key-store-file", serverKeyStoreLocation);
Expand Down Expand Up @@ -275,14 +323,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 +332,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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ public interface CertificateBuilder {

Certificate findCertificateByPrefix(String prefix);

static CertificateBuilder of(Certificate certificate) {
return new CertificateBuilderImp(List.of(certificate));
}

static CertificateBuilder of(io.quarkus.test.services.Certificate[] certificates) {
if (certificates == null || certificates.length == 0) {
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.quarkus.test.security.certificate;

public interface ContainerMountPathStrategy {

String truststorePath(String currentLocation);

String keystorePath(String currentLocation);

String keyPath(String currentLocation);

String certPath(String currentLocation);

/**
* Whether container destination path is also path used by Quarkus application when accessing these certs.
* Simply put it, if 'yes' is returned, we are probably mounting certs to the Quarkus application pod.
*/
boolean containerShareMountPathWithApp();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package io.quarkus.test.security.certificate;

import static io.quarkus.test.security.certificate.Certificate.createCertsTempDir;

import java.nio.file.Path;

import io.quarkus.test.utils.FileUtils;

class DefaultContainerMountPathStrategy implements ContainerMountPathStrategy {

private final String prefix;

DefaultContainerMountPathStrategy(String prefix) {
this.prefix = prefix;
}

@Override
public String truststorePath(String currentLocation) {
// no point of making both keystore and truststore unique, one of them is enough
return currentLocation;
}

@Override
public String keystorePath(String currentLocation) {
return makeFileMountPathUnique(prefix, currentLocation);
}

@Override
public String keyPath(String currentLocation) {
return makeFileMountPathUnique(prefix, currentLocation);
}

@Override
public String certPath(String currentLocation) {
return makeFileMountPathUnique(prefix, currentLocation);
}

@Override
public boolean containerShareMountPathWithApp() {
return true;
}

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

0 comments on commit 1e28035

Please sign in to comment.