Custom CA Certificates (Python)
Overview
You can configure Python container images to trust custom Certificate Authority (CA) certificates for TLS connections. Python libraries have different trust store behaviors compared to other OpenSSL-based images:
- urllib (built-in) reads CAs from both of these locations:
/etc/pki/tls/cert.pem- the main CA bundle file/etc/pki/tls/certs/- directory with hashed certificate symlinks
- requests (third-party) ignores
/etc/pkientirely and requires theREQUESTS_CA_BUNDLEenvironment variable to use system CAs
Note: The requests library is not included in the standard Python image. To use it, you need to build a derived image:
FROM quay.io/hummingbird/python:latest
RUN ["pip3", "install", "requests"]
podman build -t localhost/my-python-with-requests .
There are two approaches for custom CAs depending on your needs:
- Custom bundle file - Replace system CAs entirely with your own bundle
- Add custom CAs - Merge your custom CAs with the system defaults
Approach 1: Custom Bundle File
Use this when you want to trust only your custom CAs and block all default CAs. This is a common case with OpenShift’s custom PKI bundle.
Mount your CA bundle to /etc/pki/tls/cert.pem, set $REQUESTS_CA_BUNDLE to that, and block the hashed directory with an empty directory:
Custom bundle with Podman
podman run --rm \
-v /path/to/ca.crt:/etc/pki/tls/cert.pem:ro,Z \
--tmpfs /etc/pki/tls/certs:notmpcopyup \
-e REQUESTS_CA_BUNDLE=/etc/pki/tls/cert.pem \
localhost/my-python-with-requests python3 -c "
import urllib.request
print(urllib.request.urlopen('https://your-server/').status)
import requests
print(requests.get('https://your-server/').status_code)
"
Note: Podman requires the :notmpcopyup option as mounting tmpfs behaves like an overlay. Docker’s tmpfs creates an empty directory by default, and does not know that option, so drop that option with Docker.
Custom bundle with Kubernetes
apiVersion: v1
kind: ConfigMap
metadata:
name: custom-ca-bundle
data:
cert.pem: |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
... more certificates if needed ...
---
apiVersion: v1
kind: Pod
spec:
containers:
- name: app
# Use quay.io/hummingbird/python for urllib only
# Use your derived image with requests installed for both libraries
image: registry.example.com/my-python-with-requests:latest
env:
- name: REQUESTS_CA_BUNDLE
value: /etc/pki/tls/cert.pem
volumeMounts:
- name: custom-ca
mountPath: /etc/pki/tls/cert.pem
subPath: cert.pem
readOnly: true
- name: empty-certs
mountPath: /etc/pki/tls/certs
readOnly: true
volumes:
- name: custom-ca
configMap:
name: custom-ca-bundle
- name: empty-certs
emptyDir: {}
Custom bundle on OpenShift with CA injection
On OpenShift, the cluster admin may have already added your organization’s CA certificates to the cluster-wide trust store. You can use OpenShift’s automatic CA injection feature to make these certificates available to your pods.
Create a ConfigMap with the config.openshift.io/inject-trusted-cabundle=true label. OpenShift will automatically populate it with the cluster CA bundle as a key named ca-bundle.crt:
# OpenShift will inject a "ca-bundle.crt" into this automatically
apiVersion: v1
kind: ConfigMap
metadata:
name: trusted-ca
labels:
config.openshift.io/inject-trusted-cabundle: "true"
---
apiVersion: v1
kind: Pod
spec:
containers:
- name: app
# Use quay.io/hummingbird/python for urllib only
# Use your derived image with requests installed for both libraries
image: registry.example.com/my-python-with-requests:latest
env:
- name: REQUESTS_CA_BUNDLE
value: /etc/pki/tls/cert.pem
volumeMounts:
- name: trusted-ca
mountPath: /etc/pki/tls/cert.pem
subPath: ca-bundle.crt
readOnly: true
- name: empty-certs
mountPath: /etc/pki/tls/certs
readOnly: true
volumes:
- name: trusted-ca
configMap:
name: trusted-ca
- name: empty-certs
emptyDir: {}
The key detail is using subPath: ca-bundle.crt to mount OpenShift’s injected file to the correct location (/etc/pki/tls/cert.pem) that Python expects.
See the Configuring a custom PKI OpenShift documentation for details on cluster-wide CA configuration.
Approach 2: Add Custom CAs
Use this when you want to merge your custom CAs with the system defaults.
Mount your CA certificate as an individual file in /etc/pki/tls/certs/ with a properly hashed filename. The filename must be the output of openssl x509 -noout -subject_hash -in ca.crt followed by .0.
Also set $REQUESTS_CA_BUNDLE to that directory:
Custom CA file with Podman
# Generate the hashed filename
CA_HASH=$(openssl x509 -noout -subject_hash -in ca.crt)
echo "Hashed filename: ${CA_HASH}.0"
# Mount the CA certificate as an individual file in the certs directory
podman run --rm \
-v ./ca.crt:/etc/pki/tls/certs/${CA_HASH}.0:ro,Z \
-e REQUESTS_CA_BUNDLE=/etc/pki/tls/certs/ \
localhost/my-python-with-requests python3 -c "
import urllib.request
print(urllib.request.urlopen('https://your-server/').status)
print(urllib.request.urlopen('https://google.com/').status) # Default CAs still work
import requests
print(requests.get('https://your-server/').status_code)
print(requests.get('https://google.com/').status_code) # Default CAs still work
"
Custom CA file with Kubernetes
apiVersion: v1
kind: ConfigMap
metadata:
name: custom-ca
data:
ca.crt: |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
---
apiVersion: v1
kind: Pod
spec:
containers:
- name: app
# Use quay.io/hummingbird/python for urllib only
# Use your derived image with requests installed for both libraries
image: registry.example.com/my-python-with-requests:latest
env:
- name: REQUESTS_CA_BUNDLE
value: /etc/pki/tls/certs/
volumeMounts:
- name: custom-ca
# REQUIRED: replace example hash with: `openssl x509 -noout -subject_hash -in ca.crt` and append .0
mountPath: /etc/pki/tls/certs/12abc456.0
subPath: ca.crt
readOnly: true
volumes:
- name: custom-ca
configMap:
name: custom-ca