Integrating Hashicorp Vault with Cert Manager and F5 NGINX Ingress Controller

Overview

Managing TLS certificates manually can be tedious and error-prone. This quick-start guide simplifies the process by integrating HashiCorp Vault as a certificate authority in Kubernetes, using Jetstack’s cert-manager for automation and F5 NGINX Ingress Controller for secure traffic management. With this setup, certificates are issued and renewed automatically, reducing manual effort and improving security. By the end, your Kubernetes environment will be equipped with a streamlined, hands-free TLS certificate management system.

Prerequisites

  • Kubernetes Cluster (e.g., Minikube, AKS, EKS, GKE)
  • NGINX Ingress Controller (Installation Guide)
  • kubectl and Helm CLI

Key resource names for easy reference

ComponentResource Name
Vault helm releasevault
Vault podvault-0
Vault service endpointhttp://vault.default:8200
Domainexample.com
PKI roleexample-dot-com
Kubernetes service accountissuer
Kubernetes secretissuer-token
Cert manager issuervault-issuer
Certificateexample-com
TLS secretexample-com-tls
NGINX ingressexample-ingress

 

🤓 Step-by-step guide

Deploy vault in Kubernetes
#Install vault helm chart 
helm repo add hashicorp https://helm.releases.hashicorp.com 
helm repo update 
helm install vault hashicorp/vault --set "injector.enabled=false" 

# Initialize and unseal vault 
kubectl exec vault-0 -- vault operator init -key-shares=1 -key-threshold=1 -format=json > init-keys.json 
VAULT_UNSEAL_KEY=$(cat init-keys.json | jq -r ".unseal_keys_b64[]") 
kubectl exec vault-0 -- vault operator unseal $VAULT_UNSEAL_KEY 

# Verify vault deployment, result will be pod vault-0 running 
kubectl get pods 

# Capture the root token to login, you should receive a success message 
VAULT_ROOT_TOKEN=$(cat init-keys.json | jq -r ".root_token") 
kubectl exec vault-0 -- vault login $VAULT_ROOT_TOKEN
Configure PKI secrets engine
# Start an interactive Shell in pod vault-0 
kubectl exec --stdin=true --tty=true vault-0 -- /bin/sh 

# Enable PKI default path 
vault secrets enable pki 

# Configure max cert validity time 
vault secrets tune -max-lease-ttl=8760h pki 

# Generate root cert 
vault write pki/root/generate/internal \
    common_name=example.com \
    ttl=8760h

# Configure PKI endpoints
vault write pki/config/urls \
    issuing_certificates="http://vault.default:8200/v1/pki/ca" \
    crl_distribution_points="http://vault.default:8200/v1/pki/crl"

# Configure a role name
vault write pki/roles/example-dot-com \
    allowed_domains=example.com \
    allow_subdomains=true \
    max_ttl=72h

# Create a policy called "pki" for the PKI secrets engine 
vault policy write pki - <<EOF
path "pki*"                        { capabilities = ["read", "list"] }
path "pki/sign/example-dot-com"    { capabilities = ["create", "update"] }
path "pki/issue/example-dot-com"   { capabilities = ["create"] }
EOF

# Exit the vault-0 pod 
exit
Configure Kubernetes authentication
# Start the interactive shall in pod vault-0 
kubectl exec --stdin=true --tty=true vault-0 -- /bin/sh 

# Enable Kubernetes authentication 
vault auth enable kubernetes 

# Configure the Kubernetes authentication method to use location of the Kubernetes API 
vault write auth/kubernetes/config \
    kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443"

# Create a Kubernetes authentication role 
vault write auth/kubernetes/role/issuer \
    bound_service_account_names=issuer \
    bound_service_account_namespaces=default \
    policies=pki \
    ttl=20m

# Exit pod vault-0 
exit
Deploy cert manager
# Install Jetstack's cert-manager version 1.12.3 
kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v1.12.3/cert-manager.crds.yaml 

# Create cert-manager namespace 
kubectl create namespace cert-manager 

# Install with helm 
helm repo add jetstack https://charts.jetstack.io 
helm repo update 
helm install cert-manager \
    --namespace cert-manager \
    --version v1.12.3 \
  jetstack/cert-manager

# Get cert manager pods 
kubectl get pods --namespace cert-manager
Configure an issuer and generate a certificate
# Create service account in the default namespace 
kubectl create serviceaccount issuer 

# Create a secret definition 
cat >> issuer-secret.yaml <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: issuer-token-lmzpj
  annotations:
    kubernetes.io/service-account.name: issuer
type: kubernetes.io/service-account-token
EOF

# Create issuer secret 
kubectl apply -f issuer-secret.yaml 

# Get all secrets in the default namespace 
kubectl get secrets 

# Create variable to capture the value of issuer secret 
ISSUER_SECRET_REF=$(kubectl get secrets --output=json | jq -r '.items[].metadata | select(.name|startswith("issuer-token-")).name') 

# Define issuer file
cat > vault-issuer.yaml <<EOF
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: vault-issuer
  namespace: default
spec:
  vault:
    server: http://vault.default:8200
    path: pki/sign/example-dot-com
    auth:
      kubernetes:
        mountPath: /v1/auth/kubernetes
        role: issuer
        secretRef:
          name: $ISSUER_SECRET_REF
          key: token
EOF

# Create the issuer 
kubectl apply -f vault-issuer.yaml 

# Define a certificate named example-com cat > example-com-cert.yaml <<EOF apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: example-com namespace: default spec: secretName: example-com-tls issuerRef: name: vault-issuer commonName: www.example.com dnsNames: - www.example.com EOF 

# Create the example-com certificate 
kubectl apply -f example-com-cert.yaml 

# View the example-com certificate 
kubectl describe certificate.cert-manager example-com
Configure NGINX Ingress to use the certificate
# Define nginx ingress resource 
cat > example-ingress.yaml <<EOF 
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  tls:
  - hosts:
    - www.example.com
    secretName: example-com-tls
  rules:
  - host: www.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: example-service
            port:
              number: 80
EOF 

# Apply ingress configuration 
kubectl apply -f example-ingress.yaml
Verify automatic certificate renewal
# Check if the certificate has been created successfully and when its expiry date is
kubectl get certificate example-com -n default 
kubectl get secret example-com-tls -o jsonpath="{.data['tls\.crt']}" | base64 -d | openssl x509 -noout -dates 

# After waiting for the certificate renewal period to pass, check if a new certificate has been issued
kubectl get certificate example-com -n default 
kubectl get secret example-com-tls -o jsonpath="{.data['tls\.crt']}" | base64 -d | openssl x509 -noout -dates 

# Ensure that the expiration date has changed, indicating that a new certificate has been issued and applied
kubectl describe certificate example-com

# Check that NGINX Ingress is working with your TLS 
curl -v https://www.example.com --resolve www.example.com:443:<YOUR_INGRESS_SERVICE_EXTERNAL_IP> 

 

☕ Summary 

With HashiCorp Vault and cert-manager, certificate management is fully automated, eliminating the need for manual intervention. NGINX Ingress Controller handles TLS termination efficiently, reducing overhead on backend services while ensuring secure communication.

Beyond basic ingress capabilities, NGINX Ingress Controller offers advanced traffic splitting and content-based routing, making it a solid choice for production environments. Offloading TLS termination here not only enhances security but also optimizes resource utilization and performance. This integration provides a scalable, maintainable solution for managing certificates and ingress traffic in Kubernetes.

Published Mar 13, 2025
Version 1.0
No CommentsBe the first to comment