Files
certctl/deploy/helm
shankar0123 2d9110b0c4 auth-bundle-2 Phase 0: dependency-add + oidc auth-type literal + runtime guard
Bundle 2 Phase 0 stages the dependencies + auth-type discriminator
literal that later phases consume. No handler chain wired yet; an
operator who sets CERTCTL_AUTH_TYPE=oidc on this commit gets a clear
refuse-to-start error rather than a silent fallback to api-key (the
G-1 failure mode that drove "jwt" out of the allowed set).

Deliverables:

* go.mod: github.com/coreos/go-oidc/v3 v3.18.0 added as a direct
  require. Per the pre-bundle dependency audit (Apache-2.0, zero CVEs
  ever per OSV.dev, 2,400+ stars, used by Hashicorp Vault + Dex +
  Hydra + Authentik + every Kubernetes OIDC integration), this is the
  ecosystem-standard Go OIDC client. Pinned to a specific minor
  (v3.18.0) per the prompt's "no bare latest" rule.
* go.mod: golang.org/x/oauth2 promoted from // indirect to direct,
  bumped from v0.34.0 to v0.36.0 by go mod tidy. Both versions are
  OSV-clean. Maintained by the Go team.
* No JSON-path library added (forbidden by the dependency audit; the
  group-claim resolver is hand-rolled in Phase 3).
* internal/config/config.go: AuthTypeOIDC constant added with a
  load-bearing comment explaining (a) this is the AUTH-TYPE literal,
  not a JWT alg literal, so the G-1 closure invariant is preserved
  ("jwt" stays out of ValidAuthTypes forever); (b) the runtime guard
  in cmd/server/main.go intentionally refuses-to-start when oidc is
  set pre-Phase-6 to avoid the silent-downgrade failure mode.
  ValidAuthTypes() now returns {api-key, none, oidc}.
* internal/config/config_test.go: TestValidAuthTypesIsExactly_APIKey_None
  renamed to TestValidAuthTypesIsExactly_APIKey_None_OIDC and now pins
  the 3-entry set. TestValidAuthTypesDoesNotContainJWT (G-1 closure
  test) still passes because "jwt" is never added back.
  TestValidate_GenericInvalidAuthType's bad-types list updated:
  "oidc" removed (now valid), "saml" added (correctly rejected per
  Decision 5's SAML deferral).
* cmd/server/main.go: defense-in-depth runtime auth-type guard now
  has an explicit AuthTypeOIDC case that exit(1)s with an actionable
  message: "the OIDC auth chain is not yet wired in this build (Auth
  Bundle 2 Phase 6 ships the session middleware that consumes this
  auth-type literal)." This closes the lying-field gap the literal
  would otherwise create. Phase 6 of Bundle 2 relaxes this case to
  fall through alongside api-key + none.
* api/openapi.yaml: /v1/auth/info auth_type enum extended from
  [api-key, none] to [api-key, none, oidc] with an in-line comment
  explaining the Phase-0-vs-Phase-6 timing so an OpenAPI consumer
  isn't surprised by "oidc" appearing here pre-Bundle-2-merge.
* deploy/helm/certctl/templates/_helpers.tpl::certctl.validateAuthType:
  valid set extended to include "oidc". Chart-time validation now
  passes for type=oidc; the binary's runtime guard takes over to
  refuse the start. Once Bundle 2 ships, the runtime guard relaxes
  and OIDC works end-to-end with no further chart edits.
* .env.example: CERTCTL_AUTH_TYPE comment block updated to document
  the three valid values + the Phase-0-vs-Phase-6 timing.
* internal/auth/oidc/doc.go: new package directory with package doc
  + transitional blank imports for coreos/go-oidc/v3 + x/oauth2 so
  go mod tidy keeps both deps as direct requires until Phase 3's
  service.go replaces the blanks with real symbol use. Doc explains
  the package layout (oidc/ + oidc/domain/ + oidc/groupclaim/ +
  oidc/testfixtures/) so the post-Bundle-2 reader can navigate.

Verifications:
* gofmt clean on every changed file.
* go vet clean on internal/config + cmd/server + internal/auth/oidc.
* go test -short -count=1 green on internal/config (including the
  G-1 closure + new validation tests), cmd/server, internal/auth (all
  Bundle 1 packages), internal/service/auth.
* govulncheck ./... clean (M-024 hard CI gate).
* All 24 ci-guards pass locally.

Phase 0 exit criteria from cowork/auth-bundle-2-prompt.md:
* go.mod shows coreos/go-oidc/v3 as direct: yes.
* golang.org/x/oauth2 is direct (not indirect): yes.
* govulncheck ./... clean: yes.
* No JSON-path library in go.mod / go.sum deltas: confirmed (only
  v3 of go-oidc + the x/oauth2 bump landed).
* make verify green: gofmt + vet + go test pass; full make verify
  (which would invoke golangci-lint) deferred to CI since the
  sandbox doesn't have golangci-lint installed; the operator runs
  make verify locally before pushing per CLAUDE.md operating rule.
2026-05-10 03:31:51 +00:00
..

Certctl Helm Chart

Production-ready Helm chart for deploying certctl (self-hosted certificate lifecycle management platform) on Kubernetes.

Table of Contents

  1. Quick Start
  2. Chart Features
  3. Prerequisites
  4. Installation
  5. Configuration
  6. Usage Examples
  7. Upgrading
  8. Uninstalling
  9. Architecture
  10. Security Considerations
  11. Troubleshooting

Quick Start

# Add the chart repository (when available)
helm repo add certctl https://charts.example.com
helm repo update

# Install with default values
helm install certctl certctl/certctl \
  --set server.auth.apiKey="your-secure-api-key" \
  --set postgresql.auth.password="your-secure-password"

# Check installation status
kubectl get pods -l app.kubernetes.io/instance=certctl

Chart Features

  • Server Deployment — certctl control plane with configurable replicas
  • PostgreSQL StatefulSet — Persistent database with automatic schema migration
  • Agent DaemonSet or Deployment — Flexible agent deployment (per-node or custom replicas)
  • Ingress Support — Optional HTTPS ingress with cert-manager integration
  • Security Contexts — Non-root containers, read-only filesystems, minimal capabilities
  • Resource Limits — Configurable CPU and memory requests/limits
  • Health Checks — Liveness and readiness probes on all containers
  • ConfigMaps and Secrets — Centralized configuration management
  • Service Account and RBAC — Optional cluster role bindings
  • Pod Disruption Budgets — HA-ready with configurable disruption budgets
  • Monitoring — Optional Prometheus ServiceMonitor support

Prerequisites

  • Kubernetes 1.19 or later
  • Helm 3.0 or later
  • Optional: cert-manager (for automatic TLS certificate provisioning)
  • Optional: Prometheus (for metrics scraping)

Installation

1. Using Chart from Repository

helm repo add certctl https://charts.example.com
helm repo update
helm install certctl certctl/certctl -f my-values.yaml

2. Using Local Chart

cd deploy/helm
helm install certctl certctl/ \
  --set server.auth.apiKey="$(openssl rand -base64 32)" \
  --set postgresql.auth.password="$(openssl rand -base64 32)"

3. Minimal Production Installation

helm install certctl certctl/certctl \
  --namespace certctl \
  --create-namespace \
  --set server.auth.apiKey="change-me" \
  --set postgresql.auth.password="change-me" \
  --set server.replicas=2 \
  --set server.resources.requests.cpu=200m \
  --set server.resources.requests.memory=256Mi \
  --set ingress.enabled=true \
  --set ingress.className=nginx \
  --set ingress.hosts[0].host=certctl.example.com

Configuration

Server Configuration

server:
  replicas: 1                    # Number of server replicas
  port: 8443                     # Service port
  auth:
    type: api-key               # Authentication type
    apiKey: "your-api-key"      # REQUIRED for production
  logging:
    level: info                 # Log level (debug, info, warn, error)
    format: json                # Output format
  issuer:
    local:
      enabled: true             # Enable local CA issuer
    acme:
      enabled: false            # Enable ACME issuer
      directoryURL: ""          # ACME directory URL
      email: ""                 # ACME registration email
      challengeType: "http-01"  # Challenge type (http-01, dns-01, dns-persist-01)

PostgreSQL Configuration

postgresql:
  enabled: true                 # Use managed PostgreSQL
  auth:
    database: certctl
    username: certctl
    password: "your-password"   # REQUIRED
  storage:
    size: 10Gi                  # PVC size
    storageClass: ""            # Use default StorageClass

Agent Configuration

agent:
  enabled: true                 # Deploy agents
  kind: DaemonSet              # DaemonSet (one per node) or Deployment
  replicas: 1                  # For Deployment kind only
  discoveryDirs: ""            # Comma-separated cert discovery paths
  nodeSelector: {}             # Node affinity for DaemonSet

Ingress Configuration

ingress:
  enabled: false
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
  hosts:
    - host: certctl.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: certctl-tls
      hosts:
        - certctl.example.com

See values.yaml for all available configuration options.

Usage Examples

Example 1: High Availability Setup

# ha-values.yaml
server:
  replicas: 3
  resources:
    requests:
      cpu: 250m
      memory: 256Mi
    limits:
      cpu: 1000m
      memory: 512Mi

postgresql:
  storage:
    size: 50Gi

podAntiAffinity:
  requiredDuringSchedulingIgnoredDuringExecution:
    - labelSelector:
        matchExpressions:
          - key: app.kubernetes.io/component
            operator: In
            values: [server]
      topologyKey: kubernetes.io/hostname

Deploy with:

helm install certctl certctl/certctl -f ha-values.yaml

Example 2: External PostgreSQL Database

# external-db-values.yaml
postgresql:
  enabled: false

server:
  env:
    CERTCTL_DATABASE_URL: "postgres://user:password@rds.example.com:5432/certctl?sslmode=require"

Deploy with:

helm install certctl certctl/certctl -f external-db-values.yaml

Example 3: ACME + Let's Encrypt

# acme-values.yaml
server:
  issuer:
    acme:
      enabled: true
      directoryURL: https://acme-v02.api.letsencrypt.org/directory
      email: admin@example.com
      challengeType: dns-01
      dnsPresentScript: /scripts/dns-present.sh
      dnsCleanupScript: /scripts/dns-cleanup.sh
      dnsPropagationWait: 30s

Example 4: Email Notifications via Slack + SMTP

# notifications-values.yaml
server:
  smtp:
    enabled: true
    host: smtp.example.com
    port: 587
    username: certctl@example.com
    password: "smtp-password"
    fromAddress: certctl@example.com
    useTLS: true

  notifiers:
    slack:
      enabled: true
      webhookUrl: https://hooks.slack.com/services/YOUR/WEBHOOK/URL
      channel: "#certificates"

Upgrading

# Update chart repository
helm repo update

# Upgrade release
helm upgrade certctl certctl/certctl -f values.yaml

# View upgrade history
helm history certctl

# Rollback to previous version
helm rollback certctl 1

Uninstalling

# Delete the release (keeps data by default)
helm uninstall certctl

# Also delete persistent data
kubectl delete pvc --all -l app.kubernetes.io/instance=certctl

# Delete namespace
kubectl delete namespace certctl

Architecture

Components

┌──────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster                                           │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌─────────────────┐                 ┌──────────────────┐  │
│  │ Ingress/LB      │                 │  Agent Pod 1     │  │
│  │ (optional)      │                 │  (DaemonSet)     │  │
│  └────────┬────────┘                 └──────────────────┘  │
│           │                                                  │
│           ▼                           ┌──────────────────┐  │
│  ┌─────────────────────────┐          │  Agent Pod 2     │  │
│  │ Server Deployment       │          │  (DaemonSet)     │  │
│  │ (1 to N replicas)       │          └──────────────────┘  │
│  │ - REST API              │                                 │
│  │ - Scheduler             │          ┌──────────────────┐  │
│  │ - UI Dashboard          │          │  Agent Pod N     │  │
│  └────────┬────────────────┘          │  (DaemonSet)     │  │
│           │                           └──────────────────┘  │
│           │                                                  │
│           ▼                                                  │
│  ┌──────────────────────────┐                               │
│  │ PostgreSQL StatefulSet   │                               │
│  │ - Database               │                               │
│  │ - PVC (persistent)       │                               │
│  └──────────────────────────┘                               │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Network Communication

  • Server → PostgreSQL: Internal cluster DNS (certctl-postgres:5432)
  • Agent → Server: Internal cluster DNS (certctl-server:8443)
  • External → Server: Via Ingress or Service (ClusterIP/LoadBalancer/NodePort)

Security Considerations

1. Secrets Management

All sensitive data is stored in Kubernetes Secrets:

  • PostgreSQL credentials
  • API keys
  • SMTP passwords
  • ACME account secrets

Best Practices:

  • Use sealed-secrets or external-secrets operator
  • Enable encryption at rest in etcd
  • Rotate secrets regularly
# Example: Using sealed-secrets
kubectl create secret generic certctl-api-key --from-literal=api-key="$(openssl rand -base64 32)" --dry-run=client -o yaml | kubeseal -f - | kubectl apply -f -

2. RBAC

The chart creates minimal RBAC by default:

  • ServiceAccount per release
  • ClusterRole (empty, extensible)
  • ClusterRoleBinding

To restrict further:

rbac:
  create: true
  # Add specific rules here

3. Pod Security

All containers run with:

  • Non-root user (UID 1000)
  • Read-only root filesystem
  • No privilege escalation
  • Dropped capabilities (ALL)

4. Network Policies

Restrict pod-to-pod communication:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: certctl-default-deny
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/instance: certctl
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              name: certctl
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              name: certctl
    - to:
        - podSelector: {}
      ports:
        - protocol: TCP
          port: 53  # DNS
        - protocol: UDP
          port: 53

5. TLS/HTTPS

Enable HTTPS with cert-manager:

helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --set installCRDs=true

Then configure Ingress with TLS.

6. API Key Security

For production:

  1. Generate a strong API key: openssl rand -base64 32
  2. Store securely (Vault, sealed-secrets, etc.)
  3. Never commit to Git
  4. Rotate periodically
# Generate and deploy API key
NEW_KEY=$(openssl rand -base64 32)
kubectl patch secret certctl-server -p "{\"data\":{\"api-key\":\"$(echo -n $NEW_KEY | base64)\"}}"

Troubleshooting

1. Pods Not Starting

# Check pod status
kubectl get pods -l app.kubernetes.io/instance=certctl
kubectl describe pod <pod-name>
kubectl logs <pod-name>

2. Database Connection Issues

# Verify PostgreSQL is running
kubectl get pods -l app.kubernetes.io/component=postgres
kubectl logs -l app.kubernetes.io/component=postgres

# Test connection from server pod
kubectl exec -it <server-pod> -- \
  psql postgres://certctl:password@certctl-postgres:5432/certctl

3. Agent Not Connecting

# Check agent logs
kubectl logs -l app.kubernetes.io/component=agent

# Verify server is reachable
kubectl exec -it <agent-pod> -- \
  wget -q -O - http://certctl-server:8443/health

4. Persistent Data Loss

# Check PVC status
kubectl get pvc

# Verify data is being stored
kubectl exec -it <postgres-pod> -- \
  ls -lah /var/lib/postgresql/data/postgres

5. Permission Denied Errors

The chart runs containers as non-root (UID 1000). If you see permission errors:

# Temporarily allow root for debugging
server:
  securityContext:
    runAsUser: 0  # NOT FOR PRODUCTION

6. Out of Memory

Increase resource limits:

helm upgrade certctl certctl/certctl \
  --set server.resources.limits.memory=1Gi \
  --set postgresql.resources.limits.memory=2Gi

7. Certificate Validation Issues

For self-signed certificates:

kubectl exec -it <pod> -- \
  CERTCTL_TLS_INSECURE_SKIP_VERIFY=true <command>

Common Issues and Solutions

Issue Solution
ImagePullBackOff Update server.image.repository to your registry
CrashLoopBackOff Check logs with kubectl logs <pod>
Pending PVC Check storage class availability
Connection timeout Verify network policies and service DNS
High memory usage Adjust postgresql.resources.limits and server.resources.limits

Support and Contributing

For issues, questions, or contributions, visit:

License

BSL-1.1