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:
    1. /etc/pki/tls/cert.pem - the main CA bundle file
    2. /etc/pki/tls/certs/ - directory with hashed certificate symlinks
  • requests (third-party) ignores /etc/pki entirely and requires the REQUESTS_CA_BUNDLE environment 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:

  1. Custom bundle file - Replace system CAs entirely with your own bundle
  2. 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: {}

On OpenShift, the admin may already have added your organization’s CA(s) to the cluster-wide trust store. Then you can build a config.openshift.io/inject-trusted-cabundle labelled ConfigMap with that bundle for you. See the Configuring a custom PKI OpenShift documentation for details.

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