Sub-Module 1.5: Custom Security Configurations
|
This workshop environment has been optimized for learning efficiency. Required tools, directories, sample applications, and configurations have been pre-installed to focus on core hardened image concepts rather than repetitive setup tasks. |
Introduction
Beginning with a baseline that reduces the toil associated with identifying, researching, and, when possible, manually remediating CVEs while tracking exceptions is a good start to reducing friction on the path to production.
However this isn’t the only security concern common to containerized application delivery. Nor is it the only control available when using Hardened Images. In this module we’ll look at a few examples of how to customize hardened images for specific security requirements like custom certificates and regulatory compliance.
Adding certificate authority bundles to images
One of the common things developers need to deal with is custom SSL certificates. Organizations will often have internal certificates or authorities (CAs) that need to be used to provide appropriate chains of trust for signing.
For this lab, we’ll rely on Caddy’s built in TLS support. We can activate this with a custom Caddyfile, enabling the default SSL port and using the internal directive to turn on it’s CA. We can rebuild our previous caddy-server with this new configuration and rebuild the image.
Step 1: Examine SSL Configuration
A Caddy configuration with TLS support is available in the webserver directory. Let’s examine how SSL is configured:
cat ~/webserver/Caddyfile
{
http_port 8080
https_port 8443 (1)
}
localhost {
tls internal (2)
root * /usr/share/caddy
file_server
}
| 1 | New SSL Port: Listening on HTTP (8080) and HTTPS (8443) |
| 2 | Internal CA: Automatic self-signed certificate generation with tls internal |
Custom Caddyfile configuration
The other two lines are the same as the shipped default setting the path caddy will serve files from.
Step 2: Build SSL-enabled Caddy image
Create a new Containerfile to add this custom Caddyfile and build a new webserver.
cat > ~/webserver/Containerfile << 'EOF'
FROM quay.io/hummingbird/caddy:latest
COPY Caddyfile /etc/caddy/Caddyfile
COPY index.html /usr/share/caddy/
EOF
podman build -t caddy:ssl -f ~/webserver/Containerfile ~/webserver
Step 3: Run and test SSL Caddy server
Let’s only expose the HTTPS port this time.
podman run -d --name caddy-ssl -p 8443:8443 -v ~/webserver:/usr/share/caddy:ro,Z caddy:ssl
With the new SSL enabled Caddy site running, we can test against the new port
podman run --net=host --rm -it quay.io/hummingbird/curl https://localhost:8443
As expected, we get an error since curl can’t verify the issuer of the SSL certificate presnted by Caddy.
curl: (60) SSL certificate OpenSSL verify result: unable to get local issuer certificate (20) More details here: https://curl.se/docs/sslcerts.html curl failed to verify the legitimacy of the server and therefore could not establish a secure connection to it. To learn more about this situation and how to fix it, please visit the webpage mentioned above.
Step 4: Extract CA certificate and key
We can get the certificate and key for the internal CA from the running container and create a PEM file from them. This is similar to what you’d need for your internal organization’s CA.
podman cp caddy-ssl:/data/caddy/pki/authorities/local/root.key .
podman cp caddy-ssl:/data/caddy/pki/authorities/local/root.crt .
cat root.key root.crt > ca.pem
Step 5: Build custom curl with CA bundle
Using our internal CA as part of the trusted chain, we’d need to embed it in the image for our applications or tools. We’ll use the builder variant of the curl image to create a custom internal curl we could use to validate custom certificates in a pipeline.
This multi-stage pattern is the same we used for the quarkus application. This keeps all of the work of creating the new trust store in our builder and only adds the files we need to our final curl tool image.
cat > ~/Containerfile.pem << 'EOF'
FROM quay.io/hummingbird/curl:latest-builder as builder
# Copy the certificate to the image
COPY ca.pem /tmp/ (1)
# Temporarily switch to root to add the CA certificate to the trust store
USER root
RUN trust anchor /tmp/ca.pem (2)
USER ${CONTAINER_DEFAULT_USER}
# Runtime stage:
# Copy the trust store from the builder image to the runtime image
FROM quay.io/hummingbird/curl:latest
COPY --from=builder /etc/pki/ca-trust/extracted /etc/pki/ca-trust/extracted (3)
EOF
| 1 | Copy the custom CA pem to the build stage |
| 2 | Include the custom CA in our trusted root |
| 3 | Only copy the PKI files into the final image |
Now build our new tool with the internal CA trusted.
podman build -t curl:local-ca -f ~/Containerfile.pem ~
Baselines for special use cases
In addition to the default and builder variants already discussed, we also have variants designed around specific security profiles. One example is the fips variant, aimed at satifying the needs of a containerized application that must meet the Federal Information Processing Standards (FIPS) 140-3 standard for security and interoperability.
While not this particular regulatory requirement may not be universal, it serves as a useful example of how we are exploring additional compliance measures on top of the baseline images and how these variants combine.
The fips variant changes the algorithms available in the container and also uses the validated FIPS provider delivered in RHEL. Note that while the algorithms are always enforced, the underlying host or cluster must also be operating in a FIPS mode to ensure full compliance. Like the default, a -builder variant exists to support multi-stage and other build patternss.
Step 7: Check FIPS mode on host
In this lab, the host and cluster are not in FIPS mode, however the container restrictions will be enforced regardless. A 0 in the fips_enabled parameter means the kernel boot parameter was not set.
For full FIPS 140-3 compliance (required for FedRAMP, DoD, and other regulatory frameworks):
-
Container must use
-fipsvariant (provides validated crypto modules) -
Host/cluster must enable FIPS mode (kernel boot parameter
fips=1) -
Hardware may need FIPS-validated TPM (for some compliance levels)
We can still demonstrate how the container restricts algorithms as the image enforcement operates separately. Production deployments requiring certification would need both container AND host in FIPS mode.
cat /proc/sys/crypto/fips_enabled
Step 8: Create test Python Containerfile
We’ve built a python application to demonstrate the differences an application would see based on the use of a default or fips variant. The only difference between our two containers is the source image.
|
The |
# Create standard Python Containerfile
cat > ~/fips/Containerfile << 'EOF'
FROM quay.io/hummingbird/python:3.14
COPY test-fips.py .
# Switch back to the default user to install and run the application
USER ${CONTAINER_DEFAULT_USER}
# Appropriately set the stop signal for the python interpreter executed as PID 1
STOPSIGNAL SIGINT
ENTRYPOINT ["python", "./test-fips.py"]
EOF
Step 9: Create FIPS Python Containerfile
# Create FIPS-enabled Python Containerfile
cat > ~/fips/Containerfile.fips << 'EOF'
FROM quay.io/hummingbird/python:3.14-fips
COPY test-fips.py .
# Switch back to the default user to install and run the application
USER ${CONTAINER_DEFAULT_USER}
# Appropriately set the stop signal for the python interpreter executed as PID 1
STOPSIGNAL SIGINT
ENTRYPOINT ["python", "./test-fips.py"]
EOF
Step 10: Build and compare both images
We can build these and tag them so we can tell them apart
podman build -t fips:no -f ~/fips/Containerfile ~/fips
podman build -t fips:yes -f ~/fips/Containerfile.fips ~/fips
Step 11: Run and compare FIPS enforcement
Running our application, we can see that MD5 and other methods considered less secure are disallowed in our fips based container.
podman run --rm fips:no
============================================================ FIPS Validated Crypto Library Test ============================================================ Python version: 3.14.3 OpenSSL version: OpenSSL 3.5.5 27 Jan 2026 FIPS provider: not active (1) ============================================================ Running FIPS Validation Checks ============================================================ ✓ PASS - FIPS-Approved Algorithms (2) All FIPS-approved algorithms available (SHA-256, HMAC-SHA256, MD5 (usedforsecurity=False), AES-256-GCM cipher) ✗ FAIL - Disallowed Algorithms Blocked (3) Failures: MD5 via hashlib.new() was allowed; MD5 via hashlib.md5() was allowed; HMAC-MD5 was allowed; CHACHA20-POLY1305 cipher was allowed ✗ NOT FIPS CAPABLE
The test program highlights a few details about the algorithms and providers available to an application in this image.
| 1 | The specialized compliant module for OpenSSL is not present in the default image |
| 2 | The specific algorithms approved for use in FIPS environments are available |
| 3 | The insecure algorithms like MD5 have not been disabled |
Running this image in a FIPS environment would not comply with the standard.
podman run --rm fips:yes
============================================================ FIPS Validated Crypto Library Test ============================================================ Python version: 3.14.3 OpenSSL version: OpenSSL 3.5.5 27 Jan 2026 (1) FIPS provider: active (provider 3.0.7-395c1a240fbfffd8) (2) ============================================================ Running FIPS Validation Checks ============================================================ ✓ PASS - FIPS-Approved Algorithms All FIPS-approved algorithms available (SHA-256, HMAC-SHA256, MD5 (usedforsecurity=False), AES-256-GCM cipher) ✓ PASS - Disallowed Algorithms Blocked (3) All disallowed algorithms properly blocked (4 algorithms) ✓ FIPS CAPABLE
Here the test programs shows us new details of the inner workings of the fips variant.
| 1 | The main OpenSSL version is the same as in the default image |
| 2 | The compliant module is present and reporting a version different than the main OpenSSL package (which is expected) |
| 3 | The image has blocked the disallowed algorithms. |
This image could then be used as part of a FIPS environment.
Summary
Congratulations! You’ve completed Sub-Module 1.5 and learned how to customize hardened images for specific security requirements.
✅ Added custom CA bundles to images for internal certificate trust
✅ Built custom curl tool with embedded certificates
✅ Explored FIPS variants for regulatory compliance
✅ Compared algorithm enforcement between default and FIPS images
✅ Understood multi-level compliance requirements (container + host)
Custom Certificate Authority:
-
Multi-stage builds isolate CA bundle creation from runtime
-
trust anchorcommand integrates CAs into system trust store -
Pattern applies to any internal or custom certificates
FIPS 140-3 Compliance:
-
Image-level:
-fipsvariant enforces approved algorithms -
Host-level: Kernel must boot with
fips=1parameter -
Hardware-level: Some compliance levels require FIPS-validated TPM
Algorithm Enforcement:
-
FIPS provider blocks insecure algorithms (MD5, ChaCha20-Poly1305)
-
Approved algorithms (SHA-256, AES-256-GCM, HMAC-SHA256) remain available
-
Container enforcement works independently of host FIPS mode
Variant Combinations:
-
-fipsand-fips-buildervariants available for all runtime images -
Same multi-stage build patterns apply
-
Compliance controls stack with minimal image benefits
Next Steps
In Sub-Module 1.6: SELinux Hardening with udica, you’ll learn how to generate custom SELinux policies for distroless containers, adding kernel-level mandatory access control to complement the image-level security you’ve built.
Troubleshooting
Issue: Certificate verification still fails
# Verify the CA bundle was copied correctly
podman run --rm curl:local-ca ls -la /etc/pki/ca-trust/extracted/
# Check if the trust anchor command ran
podman history curl:local-ca | grep "trust anchor"
Issue: FIPS image allows disallowed algorithms
# Verify you're using the -fips variant
podman inspect fips:yes | grep -i fips
# Rebuild with explicit FIPS variant
podman build --build-arg VARIANT=fips -t fips:yes -f ~/fips/Containerfile.fips ~/fips
Issue: Caddy SSL server won’t start
# Check Caddy logs
podman logs caddy-ssl
# Verify Caddyfile syntax
podman run --rm -v ~/webserver:/etc/caddy:ro {hummingbird-registry}/caddy caddy validate --config /etc/caddy/Caddyfile