Skip to content

Labbs/cert-manager-webhook-etcd

Repository files navigation

cert-manager-webhook-etcd

DNS webhook solver for cert-manager using etcd as DNS backend. This webhook allows cert-manager to create DNS TXT records in etcd for ACME DNS-01 validation.

🎯 Features

  • ✅ DNS-01 validation support for Let's Encrypt (and other ACME CAs)
  • ✅ DNS records storage in etcd using SkyDNS/CoreDNS format
  • ✅ Compatible with CoreDNS using the etcd plugin
  • ✅ Deployment via Helm or Kubernetes manifests
  • ✅ etcd authentication support
  • ✅ TLS/mTLS support for secure etcd connections
  • ✅ Wildcard certificates supported

📋 Prerequisites

  • Kubernetes 1.20+
  • cert-manager 1.0+
  • etcd cluster accessible from Kubernetes
  • CoreDNS (or other DNS server) configured with etcd backend

🏗️ Architecture

┌─────────────────┐     ┌──────────────────────┐     ┌─────────┐
│   cert-manager  │────▶│  webhook-etcd        │────▶│  etcd   │
│                 │     │  (this project)      │     │         │
└─────────────────┘     └──────────────────────┘     └────┬────┘
                                                          │
                        ┌──────────────────────┐          │
                        │  CoreDNS             │◀─────────┘
                        │  (etcd plugin)       │
                        └──────────────────────┘

🚀 Installation

Option 1: Helm (Recommended)

# Install the Helm chart
helm install cert-manager-webhook-etcd \
  ./charts/cert-manager-webhook-etcd \
  --namespace cert-manager \
  --set groupName=acme.example.com

Option 2: Kubernetes Manifests

# Apply the manifests
kubectl apply -f deploy/rbac.yaml
kubectl apply -f deploy/deployment.yaml
kubectl apply -f deploy/apiservice.yaml

⚙️ Configuration

⚠️ Important: Make sure the groupName in your Helm values/deployment matches exactly the groupName in your ClusterIssuer. This is a common source of configuration errors.

1. Create a ClusterIssuer

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-prod-account-key
    solvers:
      - dns01:
          webhook:
            groupName: acme.example.com  # Must match GROUP_NAME
            solverName: etcd
            config:
              endpoints:
                - "http://etcd-0.etcd:2379"
                - "http://etcd-1.etcd:2379"
                - "http://etcd-2.etcd:2379"
              prefix: "/skydns"
              dialTimeout: 10
              # Optional: authentication
              # username: "root"
              # password: "password"

2. Create a Certificate

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: my-certificate
  namespace: default
spec:
  secretName: my-tls-secret
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
    - example.com
    - "*.example.com"  # Wildcard supported

🔧 Configuration Options

Parameter Description Needed Default
endpoints List of etcd endpoints yes
prefix Prefix for DNS records yes /skydns
username etcd username (inline, use credentialsSecretRef for production) no -
password etcd password (inline, use credentialsSecretRef for production) no -
credentialsSecretRef Name of Kubernetes secret containing etcd credentials no -
credentialsSecretNamespace Namespace of the credentials secret no challenge namespace
dialTimeout Connection timeout (seconds) no 10
tlsSecretRef Name of Kubernetes secret containing TLS certs no -
tlsSecretNamespace Namespace of the TLS secret no challenge namespace
tlsCAKey Key name for CA certificate in the secret no ca.crt
tlsCertKey Key name for client certificate in the secret no tls.crt
tlsKeyKey Key name for client private key in the secret no tls.key
tlsServerName Server name for TLS verification (when connecting via IP) no -
tlsInsecureSkipVerify Skip TLS verification (not recommended) no false

🔐 TLS Configuration

Credentials from Secret (Recommended)

For production environments, it's recommended to store etcd credentials in a Kubernetes secret instead of inline in the Issuer configuration:

apiVersion: v1
kind: Secret
metadata:
  name: etcd-credentials
  namespace: cert-manager
type: Opaque
data:
  # echo -n 'root' | base64
  username: cm9vdA==
  # echo -n 'your-password' | base64
  password: eW91ci1wYXNzd29yZA==

Then reference it in your Issuer:

config:
  endpoints:
    - "http://etcd:2379"
  credentialsSecretRef: "etcd-credentials"
  credentialsSecretNamespace: "cert-manager"  # Optional, defaults to challenge namespace

TLS Certificates

To connect to a TLS-secured etcd cluster, create a Kubernetes secret containing the certificates:

apiVersion: v1
kind: Secret
metadata:
  name: etcd-tls-certs
  namespace: cert-manager
type: Opaque
data:
  # CA certificate to verify the etcd server
  ca.crt: <base64-encoded-ca-cert>
  # Client certificate (optional, for mTLS)
  tls.crt: <base64-encoded-client-cert>
  # Client private key (optional, for mTLS)
  tls.key: <base64-encoded-client-key>

Then reference it in your Issuer:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-prod-account-key
    solvers:
      - dns01:
          webhook:
            groupName: acme.example.com
            solverName: etcd
            config:
              endpoints:
                - "https://etcd-0.etcd:2379"
                - "https://etcd-1.etcd:2379"
                - "https://etcd-2.etcd:2379"
              prefix: "/skydns"
              tlsSecretRef: "etcd-tls-certs"
              tlsSecretNamespace: "cert-manager"

TLS Options

  • CA only: Provide only ca.crt to verify the etcd server
  • mTLS (mutual TLS): Provide ca.crt, tls.crt and tls.key for client authentication
  • Insecure: Use tlsInsecureSkipVerify: true (not recommended for production)

📁 DNS Records Format

DNS records are stored in etcd using the SkyDNS format, compatible with CoreDNS:

/skydns/com/example/_acme-challenge

The content is JSON:

{
  "text": "challenge-token-value",
  "ttl": 60
}

🔍 Verification

To verify that the webhook is working:

# Check the pod
kubectl get pods -n cert-manager -l app.kubernetes.io/name=cert-manager-webhook-etcd

# Check the logs
kubectl logs -n cert-manager -l app.kubernetes.io/name=cert-manager-webhook-etcd

# Check the APIService
kubectl get apiservice v1alpha1.acme.example.com

🔨 Development

Local Build

# Download dependencies
make deps

# Build
make build

# Tests
make test

Docker Build

# Build the image
make docker-build

# Push to a registry
REGISTRY=ghcr.io/your-org make docker-push

🐛 Troubleshooting

⚠️ Important: The groupName must match exactly in:

  1. Helm values (or deployment ENV)
  2. ClusterIssuer webhook.groupName field

Common error: "failed to load config: config is required" - See TROUBLESHOOTING.md for detailed debugging steps.

Quick Checks

# Verify groupName matches everywhere
kubectl get deployment cert-manager-webhook-etcd -n cert-manager -o jsonpath='{.spec.template.spec.containers[0].env[?(@.name=="GROUP_NAME")].value}'
kubectl get clusterissuer letsencrypt-prod -o yaml | grep groupName

# Check APIService status
kubectl get apiservice | grep acme

# Check webhook logs
kubectl logs -n cert-manager -l app.kubernetes.io/name=cert-manager-webhook-etcd

For detailed troubleshooting, see TROUBLESHOOTING.md.

📄 License

Apache License 2.0

🤝 Contributing

Contributions are welcome! Feel free to open an issue or a pull request.

About

Cert-manager webhook for etcd

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors 3

  •  
  •  
  •