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
Component | Resource Name |
Vault helm release | vault |
Vault pod | vault-0 |
Vault service endpoint | http://vault.default:8200 |
Domain | example.com |
PKI role | example-dot-com |
Kubernetes service account | issuer |
Kubernetes secret | issuer-token |
Cert manager issuer | vault-issuer |
Certificate | example-com |
TLS secret | example-com-tls |
NGINX ingress | example-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.