Configuring Docker TLS Certificates for Galasa¶
Introduction¶
This guide explains how to configure Galasa to connect to Docker engines that are protected with TLS (Transport Layer Security) certificates. Docker daemons can be configured to require client certificate authentication, which provides secure communication between Galasa and the Docker engine.
What You'll Learn¶
- How to create a Certificate Authority (CA) for signing certificates
- How to generate server certificates for your Docker daemon
- How to generate client certificates for Galasa
- How to configure Docker to use TLS
- How to store certificates in Galasa's Credentials Store
- How to configure Galasa to use the certificates
Prerequisites¶
- OpenSSL installed on your system
- Linux/macOS: Usually pre-installed or available via package manager
- Windows: Download from Win32/Win64 OpenSSL or use Git for Windows which includes OpenSSL
- Docker installed and running
- Java keytool (included with JDK)
- Access to modify Docker daemon configuration
- Galasa framework installed
Note for Windows users: This guide uses command-line examples. You can use:
- PowerShell (recommended) - Modern Windows shell with better scripting
- Command Prompt (cmd) - Traditional Windows shell
- Git Bash - Unix-like environment on Windows (includes OpenSSL)
Step 1: Create a Certificate Authority (CA)¶
The Certificate Authority is used to sign both the server and client certificates, establishing trust between Docker and Galasa.
1.1 Generate CA Private Key¶
Important: Keep ca-key.pem secure! Anyone with this key can create trusted certificates.
1.2 Generate CA Certificate¶
Step 2: Create Server Certificates for Docker Daemon¶
The Docker daemon needs a server certificate to prove its identity to clients.
2.1 Generate Server Private Key¶
2.2 Create Certificate Signing Request (CSR)¶
Replace the IP address with your Docker host's IP address or hostname:
2.3 Configure Subject Alternative Names (SAN)¶
Create a file named extfile.cnf with the following content (replace IP addresses as needed):
2.4 Sign the Server Certificate¶
Step 3: Create Client Certificates for Galasa¶
Galasa needs client certificates to authenticate to the Docker daemon.
3.1 Generate Client Private Key¶
3.2 Create Client CSR¶
3.3 Configure Client Extensions¶
Create extfile-client.cnf:
3.4 Sign the Client Certificate¶
3.5 Verify Certificate Chain¶
Both should output: OK
Step 4: Configure Docker Daemon¶
Note: This step is primarily for Linux systems. Windows Docker Desktop has a different configuration approach (see note below).
4.1 Copy Server Certificates to Docker¶
# Create Docker certificate directory
sudo mkdir -p /etc/docker/certs
# Copy server certificates
sudo cp ca.pem /etc/docker/certs/
sudo cp server-cert.pem /etc/docker/certs/
sudo cp server-key.pem /etc/docker/certs/
# Set ownership and permissions
sudo chown root:root /etc/docker/certs/*
sudo chmod 0400 /etc/docker/certs/server-key.pem
sudo chmod 0444 /etc/docker/certs/ca.pem /etc/docker/certs/server-cert.pem
# Docker Desktop on macOS - certificates go in user directory
mkdir -p ~/.docker/certs
# Copy server certificates
cp ca.pem ~/.docker/certs/
cp server-cert.pem ~/.docker/certs/
cp server-key.pem ~/.docker/certs/
# Set permissions
chmod 0400 ~/.docker/certs/server-key.pem
chmod 0444 ~/.docker/certs/ca.pem ~/.docker/certs/server-cert.pem
Docker Desktop on Windows uses a GUI for configuration. For Windows Server with Docker Engine:
4.2 Configure Docker Daemon¶
Edit /etc/docker/daemon.json (create if it doesn't exist):
Docker Desktop settings are in ~/.docker/daemon.json:
Note: Port 2376 is the standard Docker TLS port. Port 2375 is for unencrypted connections (not recommended).
4.3 Restart Docker Daemon¶
4.4 Test Docker TLS Connection¶
# Test with Invoke-WebRequest (should fail without certificates)
Invoke-WebRequest -Uri "https://$DOCKER_HOST:2376/version"
# Test with certificates (requires certificate setup in PowerShell)
# Or use curl if available (Git for Windows includes curl)
curl --cacert ca.pem `
--cert cert.pem `
--key key.pem `
"https://$DOCKER_HOST:2376/version"
You should see Docker version information in JSON format.
Step 5: Create PKCS12 KeyStore for Galasa¶
Galasa stores certificates in a Java KeyStore format. We'll create a PKCS12 KeyStore containing both the client certificate and CA certificate.
5.1 Create KeyStore with Client Certificate¶
Important: Replace changeit with a strong password!
5.2 Import into Final KeyStore¶
5.3 Import CA Certificate¶
5.4 Verify KeyStore Contents¶
You should see:
- Entry 1:
docker-client(PrivateKeyEntry) - Your client certificate and private key - Entry 2:
docker-ca(trustedCertEntry) - The CA certificate
Alternative: Create JKS KeyStore for Galasa¶
While PKCS12 is the recommended format, you can also use the legacy JKS (Java KeyStore) format. Note that JKS is deprecated in newer Java versions, but is still supported by Galasa.
Why Choose JKS?¶
- Legacy compatibility: Required for older Java applications
- Existing infrastructure: You may already have JKS keystores
- Organizational standards: Some organizations mandate JKS format
Why PKCS12 is Recommended¶
- Industry standard: PKCS12 is the modern, cross-platform standard
- Better security: Stronger encryption algorithms
- Future-proof: JKS is deprecated in Java 9+
- Wider support: Works with non-Java tools
Creating a JKS KeyStore¶
Option A: Create JKS Directly from Certificates¶
# Step 1: Convert client certificate and key to PKCS12 first
openssl pkcs12 -export \
-in cert.pem \
-inkey key.pem \
-out temp-client.p12 \
-name "docker-client" \
-password pass:changeit
# Step 2: Convert PKCS12 to JKS
keytool -importkeystore \
-srckeystore temp-client.p12 \
-srcstoretype PKCS12 \
-srcstorepass changeit \
-destkeystore docker-tls.jks \
-deststoretype JKS \
-deststorepass changeit
# Step 3: Import CA certificate
keytool -importcert \
-alias docker-ca \
-file ca.pem \
-keystore docker-tls.jks \
-storepass changeit \
-storetype JKS \
-noprompt
# Step 4: Clean up temporary file
rm temp-client.p12
# Step 1: Convert client certificate and key to PKCS12 first
openssl pkcs12 -export `
-in cert.pem `
-inkey key.pem `
-out temp-client.p12 `
-name "docker-client" `
-password pass:changeit
# Step 2: Convert PKCS12 to JKS
keytool -importkeystore `
-srckeystore temp-client.p12 `
-srcstoretype PKCS12 `
-srcstorepass changeit `
-destkeystore docker-tls.jks `
-deststoretype JKS `
-deststorepass changeit
# Step 3: Import CA certificate
keytool -importcert `
-alias docker-ca `
-file ca.pem `
-keystore docker-tls.jks `
-storepass changeit `
-storetype JKS `
-noprompt
# Step 4: Clean up temporary file
rm temp-client.p12
Option B: Convert Existing PKCS12 to JKS¶
If you already have a PKCS12 KeyStore from Step 5:
Verify JKS KeyStore¶
Using JKS with Galasa¶
When using JKS format, you'll need to:
- Base64 encode the JKS file (see Step 6 below)
- Set the KeyStore type to JKS in Galasa configuration:
secure.credentials.DOCKER_TLS.keystore=MIIKEQIBAzCCCdcGCSqGSIb3DQE...
secure.credentials.DOCKER_TLS.password=changeit
secure.credentials.DOCKER_TLS.type=JKS
Important: Set type=JKS instead of type=PKCS12
JKS Limitations¶
Be aware of these JKS limitations:
- Deprecated: JKS is deprecated since Java 9
- Weaker encryption: Uses older encryption algorithms
- Limited compatibility: Not supported by non-Java tools
- No secret keys: JKS cannot store symmetric keys (only certificates and private keys)
Migration from JKS to PKCS12¶
If you need to migrate from JKS to PKCS12:
# Convert JKS to PKCS12
keytool -importkeystore \
-srckeystore docker-tls.jks \
-srcstoretype JKS \
-srcstorepass changeit \
-destkeystore docker-tls.p12 \
-deststoretype PKCS12 \
-deststorepass changeit
# Update Galasa configuration
# Change type=JKS to type=PKCS12
# Re-encode the PKCS12 file to base64 (see Step 6)
# Convert JKS to PKCS12
keytool -importkeystore `
-srckeystore docker-tls.jks `
-srcstoretype JKS `
-srcstorepass changeit `
-destkeystore docker-tls.p12 `
-deststoretype PKCS12 `
-deststorepass changeit
# Update Galasa configuration
# Change type=JKS to type=PKCS12
# Re-encode the PKCS12 file to base64 (see Step 6)
Step 6: Base64 Encode the KeyStore¶
Galasa stores KeyStore data as base64-encoded text in the Credentials Store.
6.1 Encode the KeyStore¶
6.2 Create Single-Line Base64 (Required for API)¶
For easier copying and API usage:
Step 7: Configure Galasa¶
7.1 Store Credentials in Galasa¶
Galasa provides two methods for storing KeyStore credentials:
Option A: File-Based Credentials Store¶
Edit your Galasa properties file:
- Linux/macOS:
~/.galasa/cps.properties - Windows:
%USERPROFILE%\.galasa\cps.properties
# Docker TLS credentials
secure.credentials.DOCKER_TLS.keystore=MIIKEQIBAzCCCdcGCSqGSIb3DQE...
secure.credentials.DOCKER_TLS.password=changeit
secure.credentials.DOCKER_TLS.type=PKCS12
To get the base64 content:
Option B: ETCD Credentials Store¶
For Galasa services, you can use the Secrets REST API to manage KeyStore credentials:
# Prepare the secret payload
cat > docker-tls-secret.json <<EOF
{
"name": "DOCKER_TLS",
"description": "Docker TLS certificates for PRIMARY engine",
"type": "KeyStore",
"data": {
"keystore": "$(cat docker-tls-oneline.b64)",
"password": "changeit",
"type": "PKCS12"
},
"encoding": "base64"
}
EOF
# Create the secret using the API
curl -X POST https://your-galasa-api/secrets \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d @docker-tls-secret.json
# Or update an existing secret
curl -X PUT https://your-galasa-api/secrets/DOCKER_TLS \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d @docker-tls-secret.json
# Retrieve the secret (values will be redacted unless you have permission)
curl -X GET https://your-galasa-api/secrets/DOCKER_TLS \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
# Delete the secret if needed
curl -X DELETE https://your-galasa-api/secrets/DOCKER_TLS \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
# Prepare the secret payload
$keystoreData = Get-Content docker-tls-oneline.b64 -Raw
$secretPayload = @{
name = "DOCKER_TLS"
description = "Docker TLS certificates for PRIMARY engine"
type = "KeyStore"
data = @{
keystore = $keystoreData
password = "changeit"
type = "PKCS12"
}
encoding = "base64"
} | ConvertTo-Json -Depth 10
$secretPayload | Out-File -FilePath docker-tls-secret.json -Encoding UTF8
# Create the secret using the API
curl -X POST https://your-galasa-api/secrets `
-H "Content-Type: application/json" `
-H "Authorization: Bearer YOUR_JWT_TOKEN" `
-d "@docker-tls-secret.json"
# Or update an existing secret
curl -X PUT https://your-galasa-api/secrets/DOCKER_TLS `
-H "Content-Type: application/json" `
-H "Authorization: Bearer YOUR_JWT_TOKEN" `
-d "@docker-tls-secret.json"
# Retrieve the secret (values will be redacted unless you have permission)
curl -X GET https://your-galasa-api/secrets/DOCKER_TLS `
-H "Authorization: Bearer YOUR_JWT_TOKEN"
# Delete the secret if needed
curl -X DELETE https://your-galasa-api/secrets/DOCKER_TLS `
-H "Authorization: Bearer YOUR_JWT_TOKEN"
Important Notes for Secrets API:
- The KeyStore data must be base64-encoded (without any prefix)
- The
encodingfield must be set to"base64" - The
typefield indataspecifies the KeyStore format (PKCS12 or JKS) - Authentication is required via JWT token
- Eager validation: The KeyStore is validated when created/updated - you'll get immediate feedback if:
- The KeyStore data is corrupted or invalid
- The password is incorrect
- The KeyStore type doesn't match the actual format
- The KeyStore cannot be loaded
- The base64 encoding is invalid
7.2 Configure Docker Engine Properties¶
Add to your CPS properties:
# Docker engine configuration
docker.engine.PRIMARY.hostname=192.168.1.100
docker.engine.PRIMARY.port=2376
docker.engine.PRIMARY.credentials.id=DOCKER_TLS
Note: When credentials.id is specified, Galasa automatically uses HTTPS.
7.3 Example Test Code¶
A Galasa test with a protected Docker engine
import dev.galasa.Test;
import dev.galasa.docker.DockerContainer;
import dev.galasa.docker.IDockerContainer;
@Test
public class DockerTlsTest {
@Logger public Log logger;
@DockerContainer(image = "alpine:latest", dockerEngineTag = "PRIMARY")
public IDockerContainer container;
@Test
public void testDockerConnection() throws Exception {
// Container will be provisioned using TLS connection
String output = container.exec("echo", "Hello from secure Docker!");
logger.info(output);
}
}
Step 8: Test the Connection¶
8.1 Verify Galasa Configuration¶
8.2 Run a Simple Galasa Test¶
Create a minimal test to verify the connection:
@Test
public void verifyDockerConnection() throws Exception {
// This will fail if TLS is not configured correctly
assertThat(container).isNotNull();
assertThat(container.getDockerEngineHost()).isEqualTo("192.168.1.100");
}
8.3 Check Galasa Logs¶
Look for successful TLS handshake messages in the Galasa logs:
INFO: Configuring HTTPS for Docker engine PRIMARY
INFO: Successfully loaded KeyStore credentials: DOCKER_TLS
INFO: Docker engine PRIMARY connected via HTTPS
Common Issues and Solutions¶
Issue 1: "Certificate does not match the root of the given chain"¶
Cause: The CA certificate is not properly included in the KeyStore as a trusted certificate.
Solution:
Issue 2: "unable to find valid certification path to requested target"¶
Cause: The KeyStore doesn't contain the CA certificate needed to validate the server certificate.
Solution:
- Verify the CA certificate is in the KeyStore (see Issue 1)
- Ensure the server certificate was signed by the same CA
- Check that the CA certificate is marked as a trusted certificate entry
Issue 3: "Connection refused" to Docker daemon¶
Cause: Docker daemon is not listening on the TLS port, or firewall is blocking the connection.
Solution:
# Check if Docker is listening on port 2376
netstat -an | Select-String "2376"
# Check Docker daemon configuration (Docker Desktop)
Get-Content "$env:APPDATA\Docker\settings.json"
# Check firewall rules
Get-NetFirewallRule | Where-Object {$_.DisplayName -like "*Docker*"}
# Restart Docker service
Restart-Service docker
# Or for Docker Desktop, restart from system tray
Issue 4: "Invalid keystore value provided"¶
Cause: The KeyStore data in Galasa configuration is not valid base64-encoded data.
Solution:
Ensure the KeyStore data is properly base64-encoded without any prefix:
For file-based configuration:
# Wrong (with prefix):
secure.credentials.DOCKER_TLS.keystore=base64:MIIKEQIBAzCCCdcGCSqGSIb3DQE...
# Correct (plain base64):
secure.credentials.DOCKER_TLS.keystore=MIIKEQIBAzCCCdcGCSqGSIb3DQE...
For Secrets REST API:
{
"data": {
"keystore": "MIIKEQIBAzCCCdcGCSqGSIb3DQE...",
"password": "changeit",
"type": "PKCS12"
},
"encoding": "base64"
}
Note: The KeyStore value should be plain base64-encoded data without any "base64:" prefix in both file-based and API configurations.
Issue 5: "Unsupported KeyStore type"¶
Cause: Galasa only supports PKCS12 and JKS KeyStore formats.
Solution:
Issue 6: "Hostname verification failed"¶
Cause: The hostname in the Docker engine configuration doesn't match the Subject Alternative Names (SAN) in the server certificate.
Solution:
-
Verify the SAN in the server certificate:
-
Ensure your
docker.engine.PRIMARY.hostnamematches one of the SANs - If needed, regenerate the server certificate with correct SANs
Issue 7: Docker daemon fails to start after TLS configuration¶
Cause: Invalid certificate paths or permissions in daemon.json.
Solution:
# Check Docker logs
Get-EventLog -LogName Application -Source Docker -Newest 50
# Or for Docker Desktop:
Get-Content "$env:LOCALAPPDATA\Docker\log.txt" -Tail 50
# Verify certificate files exist
Get-ChildItem "$env:ProgramData\docker\certs.d\" -Recurse
# Verify daemon.json syntax
Get-Content "$env:ProgramData\docker\config\daemon.json" | ConvertFrom-Json
# Test Docker daemon configuration
dockerd --validate
Issue 8: "Failed to load KeyStore" or "Password incorrect"¶
Cause: The password in Galasa configuration doesn't match the KeyStore password, or the KeyStore data is corrupted.
Solution:
# Test KeyStore password locally
keytool -list -keystore docker-tls.p12 -storepass YOUR_PASSWORD
# If using Secrets API, the validation happens immediately:
# - POST/PUT requests will return 400 Bad Request with error details
# - Check the error message for specific issues
# Update Galasa configuration with correct password
secure.credentials.DOCKER_TLS.password=YOUR_PASSWORD
Security Best Practices¶
1. Protect Private Keys¶
# Remove inheritance and set explicit permissions for current user only
$files = @("ca-key.pem", "server-key.pem", "key.pem")
foreach ($file in $files) {
$acl = Get-Acl $file
$acl.SetAccessRuleProtection($true, $false)
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
$env:USERNAME, "Read", "Allow"
)
$acl.SetAccessRule($rule)
Set-Acl $file $acl
}
# Store CA key offline after certificate generation
Move-Item ca-key.pem "C:\SecureStorage\"
2. Use Strong Passwords¶
3. Certificate Rotation¶
# Check certificate expiry date
openssl x509 -in cert.pem -noout -enddate
# Alternative: View certificate details
$cert = Get-PfxCertificate -FilePath docker-tls.p12
Write-Host "Certificate expires: $($cert.NotAfter)"
# Set up a reminder using Task Scheduler
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-File C:\Scripts\check-cert-expiry.ps1"
$trigger = New-ScheduledTaskTrigger -Daily -At 9am
Register-ScheduledTask -TaskName "CheckDockerCertExpiry" -Action $action -Trigger $trigger
4. Limit Certificate Scope¶
- Use separate CAs for different environments (dev, test, prod)
- Create unique client certificates for each Galasa instance
- Revoke compromised certificates immediately
5. Network Security¶
# Restrict Docker TLS port to specific IPs
New-NetFirewallRule -DisplayName "Docker TLS - Allow Subnet" `
-Direction Inbound `
-LocalPort 2376 `
-Protocol TCP `
-RemoteAddress 192.168.1.0/24 `
-Action Allow
New-NetFirewallRule -DisplayName "Docker TLS - Block Others" `
-Direction Inbound `
-LocalPort 2376 `
-Protocol TCP `
-Action Block