ACME Certificate Automation¶
Automated Certificate Management with cert-manager and Kubernetes
Document Version: 1.0.0
Last Updated: 2026-03-14
Overview¶
MazeVault implements an ACME server (RFC 8555) that allows Kubernetes clusters to automatically request, issue, and renew TLS certificates through cert-manager. This eliminates manual certificate management and integrates your existing PKI — including Microsoft ADCS — with cloud-native infrastructure.
sequenceDiagram
participant CM as ⚙️ cert-manager
participant MV as 🏦 MazeVault ACME Server
participant CA as 📜 Backend CA (ADCS/Internal)
rect rgb(235, 245, 251)
Note over CM,MV: 🔑 Account Registration
CM->>MV: POST /new-account (EAB)
MV-->>CM: Account registered
end
rect rgb(255, 248, 225)
Note over CM,CA: 📝 Certificate Order
CM->>MV: POST /new-order
MV-->>CM: Order + Authorizations
CM->>MV: POST /finalize (CSR)
MV->>CA: Sign CSR
CA-->>MV: Signed certificate
end
rect rgb(232, 245, 233)
Note over CM,MV: ✅ Certificate Delivery
MV-->>CM: Certificate ready
CM->>MV: GET /cert/:id
MV-->>CM: PEM certificate chain
end
Key Benefits¶
| Feature | Description |
|---|---|
| Zero-touch renewal | cert-manager automatically renews certificates before expiry |
| ADCS bridge | Issue certificates from Microsoft ADCS via standard ACME protocol |
| Template routing | Map ACME profiles or domains to specific certificate templates |
| EAB security | External Account Binding ensures only authorized clusters can register |
| Auto-approve | Internal domains (.local, .internal, .lan, .corp) are approved instantly |
| Audit trail | All ACME operations are logged in the MazeVault audit system |
Prerequisites¶
- MazeVault v1.0.17+ with ACME Server enabled
- Kubernetes cluster with cert-manager v1.12+
- At least one CA Account configured in MazeVault (ADCS, Internal CA, or external)
- A Certificate Template configured for your use case
Step 1: Generate EAB Credentials¶
External Account Binding (EAB) credentials tie an ACME client to your MazeVault organization. Each set of credentials can only be used once.
Via Web Interface¶
- Navigate to Organization Settings → Certificate Authorities
- Scroll to the ACME Access section
- Click Generate EAB Credentials
- Fill in:
- Cluster Label — Descriptive name (e.g.,
prod-aks-westeurope) - Default Project ID — Project where certificates will be stored (optional)
- Default Template ID — Certificate template to use (optional)
- Click Generate
- Copy and securely store the EAB Key ID and HMAC Key — the HMAC key is shown only once
Via API¶
curl -X POST https://vault.example.com/api/v1/organizations/{org_id}/acme-credentials \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"project_id": "proj_abc123",
"default_template_id": "tmpl_def456",
"cluster_label": "prod-aks-westeurope"
}'
Response:
{
"id": "cred_xyz789",
"eab_key_id": "***REMOVED***",
"eab_hmac_key": "***REMOVED***",
"cluster_label": "prod-aks-westeurope",
"created_at": "2026-03-14T10:00:00Z"
}
Save Credentials Immediately
The eab_hmac_key is returned only once at creation time. It cannot be retrieved later. Store it in a secure location (e.g., Azure Key Vault, HashiCorp Vault).
Step 2: Create the EAB Secret in Kubernetes¶
Store the HMAC key as a Kubernetes Secret for cert-manager to use:
apiVersion: v1
kind: Secret
metadata:
name: mazevault-eab-secret
namespace: cert-manager
type: Opaque
stringData:
secret: "***REMOVED***" # Your EAB HMAC Key
Alternative: Create from CLI
Step 3: Configure ClusterIssuer¶
Create a ClusterIssuer (cluster-wide) or Issuer (namespace-scoped) resource:
ClusterIssuer (Recommended)¶
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: mazevault-issuer
spec:
acme:
# MazeVault ACME directory URL
server: https://vault.example.com/api/acme/directory
# External Account Binding
externalAccountBinding:
keyID: "***REMOVED***" # Your EAB Key ID
keySecretRef:
name: mazevault-eab-secret
key: secret
keyAlgorithm: HS256
# Private key for the ACME account (auto-generated by cert-manager)
privateKeySecretRef:
name: mazevault-acme-account-key
# Challenge solver for non-auto-approved domains
solvers:
- http01:
ingress:
ingressClassName: nginx
Namespace-Scoped Issuer¶
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: mazevault-issuer
namespace: my-app
spec:
acme:
server: https://vault.example.com/api/acme/directory
externalAccountBinding:
keyID: "***REMOVED***"
keySecretRef:
name: mazevault-eab-secret
key: secret
keyAlgorithm: HS256
privateKeySecretRef:
name: mazevault-acme-account-key
solvers:
- http01:
ingress:
ingressClassName: nginx
Verify Issuer Status¶
Expected output:
NAME READY STATUS AGE
mazevault-issuer True The ACME account was registered with the ACME server 30s
Step 4: Request Certificates¶
Option A: Certificate Resource¶
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: api-tls
namespace: my-app
spec:
secretName: api-tls-secret
duration: 8760h # 1 year
renewBefore: 720h # Renew 30 days before expiry
issuerRef:
name: mazevault-issuer
kind: ClusterIssuer
commonName: api.example.com
dnsNames:
- api.example.com
- api-internal.example.com
Option B: Ingress Annotation (Auto-TLS)¶
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
namespace: my-app
annotations:
cert-manager.io/cluster-issuer: mazevault-issuer
spec:
ingressClassName: nginx
tls:
- hosts:
- api.example.com
secretName: api-tls-secret
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
Option C: ACME Profiles (cert-manager v1.18+)¶
ACME profiles allow cert-manager to select a specific certificate template by name:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: web-tls
namespace: my-app
spec:
secretName: web-tls-secret
issuerRef:
name: mazevault-issuer
kind: ClusterIssuer
commonName: web.example.com
dnsNames:
- web.example.com
# Profile maps to a MazeVault Certificate Template with matching ACMEProfileName
profileName: web-server
Profile Configuration
To use ACME profiles, set the ACME Profile Name field on your Certificate Template in MazeVault. The profile name in the Certificate resource must match exactly.
Step 5: Verify Certificate Issuance¶
# Inspect the issued certificate
kubectl describe certificate api-tls -n my-app
# View the actual TLS certificate
kubectl get secret api-tls-secret -n my-app -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -text
Domain Auto-Approval¶
For internal domains, MazeVault skips the HTTP-01 challenge and approves certificates immediately:
| Domain Suffix | Example | Challenge Required |
|---|---|---|
.local |
app.mycompany.local |
No (auto-approved) |
.internal |
api.cluster.internal |
No (auto-approved) |
.lan |
server.office.lan |
No (auto-approved) |
.corp |
vault.mycompany.corp |
No (auto-approved) |
| All others | api.example.com |
Yes (HTTP-01) |
Internal Domains Are Fastest
For Kubernetes services that use internal DNS names, certificates are issued instantly without challenge validation. This is ideal for service mesh, internal APIs, and microservice-to-microservice TLS.
Template Routing¶
MazeVault routes ACME certificate requests to the correct CA backend using these resolution rules (in priority order):
- ACME Profile — If the request includes a
profileName, the template with matchingACMEProfileNameis used - Domain Rules — If domain rules are configured on the EAB credential, the first matching rule determines the template
- Default Template — Falls back to the default template set on the EAB credential or ACME account
graph TD
A["📨 ACME Order"] --> B{"Profile<br/>specified?"}
B -->|Yes| C["✅ Use matching template"]
B -->|No| D{"Domain rules<br/>match?"}
D -->|Yes| E["✅ Use rule's template"]
D -->|No| F{"Default template<br/>set?"}
F -->|Yes| G["✅ Use default template"]
F -->|No| H["❌ Error: No template"]
classDef start fill:#EBF5FB,stroke:#2196F3,stroke-width:2px,color:#1565C0
classDef decision fill:#FFF8E1,stroke:#FF9800,stroke-width:2px,color:#E65100
classDef success fill:#E8F5E9,stroke:#4CAF50,stroke-width:2px,color:#2E7D32
classDef error fill:#FFEBEE,stroke:#F44336,stroke-width:2px,color:#C62828
class A start
class B,D,F decision
class C,E,G success
class H error
Managing EAB Credentials¶
List Credentials¶
Navigate to Organization Settings → Certificate Authorities → ACME Access to view all EAB credentials with their status:
| Status | Meaning |
|---|---|
| Available | Not yet used, ready for registration |
| Used | Already consumed by an ACME client |
| Revoked | Disabled by an administrator |
Revoking a Credential¶
To prevent an ACME client from registering (or to decommission a cluster):
- Navigate to the ACME Access section
- Find the credential in the table
- Click the Revoke button
Existing Accounts Unaffected
Revoking an EAB credential does not affect accounts already registered with it. To deactivate an existing account, use the ACME account deactivation flow.
ACME Endpoints Reference¶
| Endpoint | Method | Description |
|---|---|---|
/api/acme/directory |
GET | ACME directory (discovery) |
/api/acme/new-nonce |
HEAD, POST | Get replay-protection nonce |
/api/acme/new-account |
POST | Register new account (EAB required) |
/api/acme/new-order |
POST | Create certificate order |
/api/acme/order/:id |
POST | Get order status |
/api/acme/authz/:id |
POST | Get authorization details |
/api/acme/challenge/:id |
POST | Trigger challenge validation |
/api/acme/finalize/:id |
POST | Submit CSR for signing |
/api/acme/cert/:id |
POST | Download issued certificate |
Troubleshooting¶
ClusterIssuer shows False READY status¶
Common causes:
| Error | Solution |
|---|---|
401 externalAccountRequired |
EAB credentials are missing or incorrect |
403 unauthorized: EAB credential already used |
Generate new EAB credentials — each can only be used once |
403 unauthorized: EAB credential revoked |
The credential was revoked, generate a new one |
| Connection refused | Verify the MazeVault URL is reachable from the cluster |
| TLS error | Ensure the cluster trusts MazeVault's TLS certificate |
Certificate stays in Pending state¶
kubectl describe certificate api-tls -n my-app
kubectl describe certificaterequest -n my-app
kubectl describe order -n my-app
kubectl describe challenge -n my-app
Common causes:
| Issue | Solution |
|---|---|
| Challenge failed | Ensure the Ingress controller can serve /.well-known/acme-challenge/ |
| No template found | Configure a default template on the EAB credential or use ACME profiles |
| Order expired | Orders expire after 24 hours; check for stuck challenges |
Useful Commands¶
# View cert-manager logs
kubectl logs -n cert-manager deploy/cert-manager -f
# Check ACME account registration
kubectl get secret mazevault-acme-account-key -n cert-manager -o yaml
# Force certificate renewal
kubectl delete secret api-tls-secret -n my-app
# cert-manager will automatically re-issue
Complete Example: End-to-End Setup¶
Here is a complete working example that provisions a TLS certificate for a web application:
---
# 1. EAB Secret
apiVersion: v1
kind: Secret
metadata:
name: mazevault-eab-secret
namespace: cert-manager
type: Opaque
stringData:
secret: "YOUR_EAB_HMAC_KEY"
---
# 2. ClusterIssuer
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: mazevault-issuer
spec:
acme:
server: https://vault.example.com/api/acme/directory
externalAccountBinding:
keyID: "YOUR_EAB_KEY_ID"
keySecretRef:
name: mazevault-eab-secret
key: secret
keyAlgorithm: HS256
privateKeySecretRef:
name: mazevault-acme-account-key
solvers:
- http01:
ingress:
ingressClassName: nginx
---
# 3. Certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: myapp-tls
namespace: default
spec:
secretName: myapp-tls-secret
duration: 8760h
renewBefore: 720h
issuerRef:
name: mazevault-issuer
kind: ClusterIssuer
commonName: myapp.example.com
dnsNames:
- myapp.example.com
- www.myapp.example.com
---
# 4. Ingress using the certificate
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
namespace: default
spec:
ingressClassName: nginx
tls:
- hosts:
- myapp.example.com
- www.myapp.example.com
secretName: myapp-tls-secret
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp
port:
number: 80
Related Documentation¶
- Certificate Management Guide — PKI operations overview
- Certificates API Reference — Full API documentation
- Helm Charts — Kubernetes deployment configuration
- TLS Configuration — TLS setup for MazeVault itself