feat(m28+m29+m30): ACME ARI, email digest, and Helm chart

M28: ACME Renewal Information (RFC 9702) — CA-directed renewal timing
with cert ID computation, directory endpoint discovery, graceful
degradation for non-ARI CAs. 19 tests.

M29: Email notifier wiring + scheduled certificate digest — SMTP
connector bridged to service layer via NotifierAdapter, DigestService
with HTML email template, 7th scheduler loop (24h), digest preview/send
API endpoints and GUI card. 21 tests.

M30: Production-ready Helm chart — server Deployment, PostgreSQL
StatefulSet, agent DaemonSet, ConfigMaps, Secrets, Ingress, security
contexts, health probes, example values for dev/prod/ACME scenarios.

Also: OpenAPI spec updates, MCP tool additions, CI helm-lint job,
documentation updates across 5 doc files and README.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
shankar0123
2026-03-28 21:18:35 -04:00
parent cb2ef9d0e7
commit ec21c9bb29
61 changed files with 6106 additions and 27 deletions
+461
View File
@@ -0,0 +1,461 @@
# Certctl Helm Chart - Complete Summary
## Overview
A production-ready Helm chart for deploying certctl (self-hosted certificate lifecycle management platform) on Kubernetes. The chart provides:
- High availability support with multi-replica deployments
- Persistent PostgreSQL database with automatic schema migration
- DaemonSet or Deployment-based agent deployment
- Comprehensive security contexts and RBAC
- Multiple deployment scenarios (dev, prod, HA, external DB)
- Full documentation and examples
## Chart Metadata
- **Name**: certctl
- **Chart Version**: 0.1.0
- **App Version**: 2.1.0
- **Type**: application
- **License**: BSL-1.1 (converts to Apache 2.0 in 2033)
## File Structure
```
deploy/helm/
├── README.md # Main Helm chart documentation
├── DEPLOYMENT_GUIDE.md # Step-by-step deployment guide
├── CHART_SUMMARY.md # This file
├── certctl/
│ ├── Chart.yaml # Chart metadata
│ ├── values.yaml # Default configuration values
│ ├── .helmignore # Files to ignore when building chart
│ │
│ └── templates/
│ ├── _helpers.tpl # Helm template helper functions
│ ├── NOTES.txt # Post-deployment notes
│ │
│ ├── server-deployment.yaml # Certctl API server deployment
│ ├── server-service.yaml # Server Kubernetes service
│ ├── server-configmap.yaml # Server configuration
│ ├── server-secret.yaml # Server secrets (API key, DB password, etc)
│ │
│ ├── postgres-statefulset.yaml # PostgreSQL database statefulset
│ ├── postgres-service.yaml # PostgreSQL headless service
│ ├── postgres-secret.yaml # Database credentials secret
│ │
│ ├── agent-daemonset.yaml # Certctl agent daemonset/deployment
│ ├── agent-configmap.yaml # Agent configuration
│ │
│ ├── ingress.yaml # Optional ingress resource
│ └── serviceaccount.yaml # ServiceAccount and RBAC
└── examples/
├── values-dev.yaml # Development/testing configuration
├── values-prod-ha.yaml # Production HA configuration
├── values-external-db.yaml # External PostgreSQL (RDS, Cloud SQL)
└── values-acme-dns01.yaml # ACME with DNS-01 (Let's Encrypt)
```
## Key Components
### 1. Server Deployment
**File**: `templates/server-deployment.yaml`
- Manages certctl API server instances
- Configurable replicas (default: 1)
- Health checks (liveness & readiness probes)
- Security context: non-root user, read-only filesystem
- Resource limits (default: 500m CPU, 512Mi memory)
- Automatic restart on failure
**Values**:
```yaml
server:
replicas: 1
port: 8443
auth:
type: api-key
apiKey: "REQUIRED"
resources:
requests: {cpu: 100m, memory: 128Mi}
limits: {cpu: 500m, memory: 512Mi}
```
### 2. PostgreSQL StatefulSet
**File**: `templates/postgres-statefulset.yaml`
- Persistent database storage
- Automatic schema migrations on startup
- Single replica (can be extended with external HA tools)
- Health checks via pg_isready
- Configurable storage size and class
- Security context: non-root user (UID 999)
**Values**:
```yaml
postgresql:
enabled: true
storage:
size: 10Gi
storageClass: "" # Use default
auth:
database: certctl
username: certctl
password: "REQUIRED"
```
### 3. Agent DaemonSet/Deployment
**File**: `templates/agent-daemonset.yaml`
- DaemonSet mode: one agent per Kubernetes node
- Deployment mode: custom number of agent replicas
- Local key storage with secure permissions (0600)
- Health checks and automatic restart
- Optional certificate discovery from filesystem
**Values**:
```yaml
agent:
enabled: true
kind: DaemonSet # or Deployment
replicas: 1 # for Deployment only
keyDir: /var/lib/certctl/keys
discoveryDirs: "/etc/ssl/certs" # optional
```
### 4. Ingress (Optional)
**File**: `templates/ingress.yaml`
- Optional HTTPS ingress
- cert-manager integration for automatic TLS
- Multiple host support
- Path-based routing
**Values**:
```yaml
ingress:
enabled: false
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: certctl.example.com
paths:
- path: /
pathType: Prefix
```
### 5. ConfigMaps and Secrets
**Files**:
- `server-configmap.yaml` - Non-secret server configuration
- `server-secret.yaml` - API key, database URL, SMTP password
- `postgres-secret.yaml` - Database credentials
- `agent-configmap.yaml` - Agent configuration
All secrets are base64-encoded and stored in Kubernetes Secrets.
### 6. ServiceAccount and RBAC
**File**: `templates/serviceaccount.yaml`
- Optional ServiceAccount creation
- Optional RBAC (ClusterRole, ClusterRoleBinding)
- Namespace-scoped by default
## Deployment Scenarios
### Development Setup
Use `examples/values-dev.yaml`:
```bash
helm install certctl certctl/ \
--values examples/values-dev.yaml \
--set server.auth.apiKey="dev-key" \
--set postgresql.auth.password="dev-password"
```
**Features**:
- Single server replica
- Demo auth (no API key required)
- Small database (5Gi)
- LoadBalancer service for easy access
- Debug logging level
### Production HA Setup
Use `examples/values-prod-ha.yaml`:
```bash
helm install certctl certctl/ \
--values examples/values-prod-ha.yaml \
--set server.auth.apiKey="$(openssl rand -base64 32)" \
--set postgresql.auth.password="$(openssl rand -base64 32)"
```
**Features**:
- 3 server replicas with pod anti-affinity
- Large database storage (100Gi)
- Pod disruption budgets
- Prometheus monitoring enabled
- Production resource limits
### External PostgreSQL
Use `examples/values-external-db.yaml`:
```bash
helm install certctl certctl/ \
--values examples/values-external-db.yaml \
--set postgresql.enabled=false \
--set 'server.env.CERTCTL_DATABASE_URL=postgres://...'
```
**Use cases**:
- AWS RDS
- Google Cloud SQL
- Azure Database for PostgreSQL
- External self-managed PostgreSQL
### ACME with DNS-01
Use `examples/values-acme-dns01.yaml`:
```bash
helm install certctl certctl/ \
--values examples/values-acme-dns01.yaml
```
**Enables**:
- Automatic certificate issuance from Let's Encrypt
- DNS-01 challenge (wildcard support)
- Custom DNS provider scripts
## Configuration Options
### Server Configuration
| Option | Default | Description |
|--------|---------|-------------|
| `server.replicas` | 1 | Number of server replicas |
| `server.port` | 8443 | Server port |
| `server.auth.type` | api-key | Authentication type |
| `server.auth.apiKey` | "" | API key (REQUIRED) |
| `server.logging.level` | info | Log level |
| `server.logging.format` | json | Log format |
### PostgreSQL Configuration
| Option | Default | Description |
|--------|---------|-------------|
| `postgresql.enabled` | true | Enable internal PostgreSQL |
| `postgresql.storage.size` | 10Gi | Database storage size |
| `postgresql.storage.storageClass` | "" | Storage class name |
| `postgresql.auth.password` | "" | Database password (REQUIRED) |
### Agent Configuration
| Option | Default | Description |
|--------|---------|-------------|
| `agent.enabled` | true | Deploy agents |
| `agent.kind` | DaemonSet | DaemonSet or Deployment |
| `agent.replicas` | 1 | Replicas (Deployment only) |
| `agent.keyDir` | /var/lib/certctl/keys | Key storage directory |
### Issuer Configuration
| Option | Default | Description |
|--------|---------|-------------|
| `server.issuer.local.enabled` | true | Enable Local CA |
| `server.issuer.acme.enabled` | false | Enable ACME |
| `server.issuer.acme.directoryURL` | "" | ACME directory URL |
| `server.issuer.acme.email` | "" | ACME email |
| `server.issuer.acme.challengeType` | http-01 | Challenge type |
See `values.yaml` for complete configuration options.
## Helm Template Functions
Defined in `templates/_helpers.tpl`:
| Function | Purpose |
|----------|---------|
| `certctl.name` | Chart name |
| `certctl.fullname` | Full release name |
| `certctl.chart` | Chart name and version |
| `certctl.labels` | Common labels |
| `certctl.selectorLabels` | Selector labels |
| `certctl.serverSelectorLabels` | Server selector labels |
| `certctl.agentSelectorLabels` | Agent selector labels |
| `certctl.postgresSelectorLabels` | PostgreSQL selector labels |
| `certctl.serviceAccountName` | ServiceAccount name |
| `certctl.serverImage` | Server image URI |
| `certctl.agentImage` | Agent image URI |
| `certctl.postgresImage` | PostgreSQL image URI |
| `certctl.databaseURL` | Database connection string |
| `certctl.serverURL` | Server URL for agents |
## Security Features
### Pod Security
- Non-root users (UID 1000 for app, UID 999 for PostgreSQL)
- Read-only root filesystems
- No privilege escalation
- Dropped capabilities (ALL)
- Resource limits to prevent DoS
### Secrets Management
- All sensitive data in Kubernetes Secrets
- Base64 encoded at rest
- Can be integrated with:
- sealed-secrets
- external-secrets
- Vault
- AWS Secrets Manager
### RBAC
- ServiceAccount per release
- Optional ClusterRole/ClusterRoleBinding
- Extensible for custom permissions
### Network Security
- Support for Kubernetes NetworkPolicies
- Service-to-service communication via internal DNS
- Optional Ingress with TLS
## Monitoring and Observability
### Health Checks
- Liveness probes (detect dead containers)
- Readiness probes (detect not-ready services)
- HTTP endpoints: `/health`, `/readyz`
### Logging
- Structured JSON logging
- Request ID propagation
- Configurable log levels (debug, info, warn, error)
### Metrics
- Prometheus metrics endpoint: `/api/v1/metrics/prometheus`
- Optional ServiceMonitor for Prometheus Operator
- Built-in metrics:
- Certificate counts by status
- Agent counts and status
- Job completion/failure rates
- Server uptime
## Installation Quick Reference
```bash
# Development
helm install certctl certctl/ \
--set server.auth.apiKey=dev \
--set postgresql.auth.password=dev
# Production HA
helm install certctl certctl/ \
--values examples/values-prod-ha.yaml \
--set server.auth.apiKey="$(openssl rand -base64 32)" \
--set postgresql.auth.password="$(openssl rand -base64 32)"
# External database
helm install certctl certctl/ \
--values examples/values-external-db.yaml \
--set postgresql.enabled=false \
--set 'server.env.CERTCTL_DATABASE_URL=postgres://...'
# ACME with Let's Encrypt
helm install certctl certctl/ \
--set server.issuer.acme.enabled=true \
--set server.issuer.acme.directoryURL=https://acme-v02.api.letsencrypt.org/directory
# Check status
kubectl get pods -l app.kubernetes.io/instance=certctl
kubectl logs -l app.kubernetes.io/component=server -f
# Upgrade
helm upgrade certctl certctl/ -f new-values.yaml
# Uninstall
helm uninstall certctl
```
## Best Practices
### 1. Use Secrets Management
```bash
# Use sealed-secrets
kubectl create secret generic certctl-secrets \
--from-literal=api-key="$(openssl rand -base64 32)" \
--dry-run=client -o yaml | kubeseal -f - | kubectl apply -f -
```
### 2. Configure Resource Limits
Match limits to your cluster capacity:
```yaml
server:
resources:
requests: {cpu: 250m, memory: 256Mi}
limits: {cpu: 1000m, memory: 512Mi}
```
### 3. Enable HA for Production
```yaml
server:
replicas: 3
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution: [...]
```
### 4. Use Persistent Storage
```yaml
postgresql:
storage:
size: 100Gi
storageClass: fast-ssd
```
### 5. Enable Monitoring
```yaml
monitoring:
enabled: true
serviceMonitor:
enabled: true
```
## Documentation
- **README.md** - Complete Helm chart documentation
- **DEPLOYMENT_GUIDE.md** - Step-by-step deployment instructions
- **values.yaml** - Commented configuration reference
## Support
For issues, questions, or contributions:
- GitHub: https://github.com/shankar0123/certctl
- Documentation: https://github.com/shankar0123/certctl/tree/main/docs
## License
BSL-1.1 (Business Source License)
Converts to Apache 2.0 on March 28, 2033
+515
View File
@@ -0,0 +1,515 @@
# Certctl Helm Deployment Guide
Complete guide for deploying certctl on Kubernetes with Helm.
## Table of Contents
1. [Prerequisites](#prerequisites)
2. [Installation Methods](#installation-methods)
3. [Production Deployment](#production-deployment)
4. [Configuration Examples](#configuration-examples)
5. [Post-Deployment Setup](#post-deployment-setup)
6. [Monitoring and Logging](#monitoring-and-logging)
7. [Maintenance](#maintenance)
## Prerequisites
### Required Tools
```bash
# Verify Kubernetes cluster access
kubectl cluster-info
kubectl get nodes
# Install Helm (if not already installed)
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version
# Verify Helm installation
helm repo list
```
### Kubernetes Requirements
- Kubernetes 1.19 or later
- At least 2GB available memory
- At least 10GB available storage (for PostgreSQL)
- Network policies support (optional, for security)
- Ingress controller (nginx, istio, etc.) - optional
### Create Namespace
```bash
# Create isolated namespace
kubectl create namespace certctl
# Set as default namespace
kubectl config set-context --current --namespace=certctl
# Label for network policies (optional)
kubectl label namespace certctl certctl-ns=true
```
## Installation Methods
### Method 1: Minimal Development Setup
Perfect for testing and development:
```bash
# Install with minimal configuration
helm install certctl certctl/certctl \
--namespace certctl \
--set server.auth.apiKey="dev-key-change-in-production" \
--set postgresql.auth.password="dev-password-change-in-production"
# Wait for deployment
kubectl rollout status deployment/certctl-server
kubectl rollout status statefulset/certctl-postgres
```
### Method 2: Production HA Setup
For production workloads:
```bash
# Generate secure credentials
API_KEY=$(openssl rand -base64 32)
DB_PASSWORD=$(openssl rand -base64 32)
# Install with HA configuration
helm install certctl certctl/certctl \
--namespace certctl \
--values deploy/helm/examples/values-prod-ha.yaml \
--set server.auth.apiKey="$API_KEY" \
--set postgresql.auth.password="$DB_PASSWORD"
```
### Method 3: External PostgreSQL
Using managed database service:
```bash
# Install with external database
helm install certctl certctl/certctl \
--namespace certctl \
--values deploy/helm/examples/values-external-db.yaml \
--set server.auth.apiKey="$API_KEY" \
--set 'server.env.CERTCTL_DATABASE_URL=postgres://user:pass@db.example.com:5432/certctl?sslmode=require'
```
### Method 4: Using Custom values.yaml
Recommended for GitOps workflows:
```bash
# Create values file with secrets management
cat > /tmp/certctl-values.yaml <<EOF
server:
auth:
apiKey: "$API_KEY"
logging:
level: info
postgresql:
auth:
password: "$DB_PASSWORD"
storage:
size: 50Gi
agent:
enabled: true
kind: DaemonSet
ingress:
enabled: true
className: nginx
hosts:
- host: certctl.example.com
paths:
- path: /
pathType: Prefix
EOF
# Install using values file
helm install certctl certctl/certctl \
--namespace certctl \
--values /tmp/certctl-values.yaml
```
## Production Deployment
### Step 1: Prepare Environment
```bash
# Create namespace
kubectl create namespace certctl
cd deploy/helm
# Generate credentials
API_KEY=$(openssl rand -base64 32)
DB_PASSWORD=$(openssl rand -base64 32)
echo "API Key: $API_KEY"
echo "DB Password: $DB_PASSWORD"
# Save credentials in secure location (e.g., 1Password, Vault, AWS Secrets Manager)
```
### Step 2: Prepare Storage
```bash
# List available storage classes
kubectl get storageclass
# If needed, create a high-performance storage class for production
cat <<EOF | kubectl apply -f -
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: ebs.csi.aws.com # For AWS, adjust for your cloud provider
parameters:
type: gp3
iops: "3000"
throughput: "125"
EOF
```
### Step 3: Set Up TLS with cert-manager
```bash
# Install cert-manager (if not already installed)
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set installCRDs=true
# Create ClusterIssuer for Let's Encrypt
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx
EOF
```
### Step 4: Install Certctl
```bash
# Install using HA values
helm install certctl certctl/ \
--namespace certctl \
--values examples/values-prod-ha.yaml \
--set server.auth.apiKey="$API_KEY" \
--set postgresql.auth.password="$DB_PASSWORD" \
--set ingress.annotations."cert-manager\.io/cluster-issuer"=letsencrypt-prod \
--set ingress.hosts[0].host=certctl.example.com
# Verify installation
kubectl get all -l app.kubernetes.io/instance=certctl
```
### Step 5: Verify Deployment
```bash
# Check pod status
kubectl get pods -l app.kubernetes.io/instance=certctl
kubectl describe pods -l app.kubernetes.io/instance=certctl
# Check service status
kubectl get svc -l app.kubernetes.io/instance=certctl
# Check ingress status
kubectl get ingress
kubectl describe ingress certctl
# Test API connectivity
POD=$(kubectl get pods -l app.kubernetes.io/component=server -o jsonpath='{.items[0].metadata.name}')
kubectl port-forward $POD 8443:8443 &
curl -H "Authorization: Bearer $API_KEY" http://localhost:8443/health
```
### Step 6: Access the Dashboard
```bash
# Port forward to local machine
kubectl port-forward svc/certctl-server 8443:8443 &
# Or if using Ingress:
# Open browser: https://certctl.example.com
# Login with API key: $API_KEY
```
## Configuration Examples
### Example 1: ACME (Let's Encrypt)
```bash
helm install certctl certctl/ \
--set server.issuer.acme.enabled=true \
--set server.issuer.acme.directoryURL=https://acme-v02.api.letsencrypt.org/directory \
--set server.issuer.acme.email=admin@example.com \
--set server.issuer.acme.challengeType=http-01
```
### Example 2: DNS-01 (Wildcard Certs)
Requires DNS scripts ConfigMap:
```bash
# Create DNS scripts ConfigMap
kubectl create configmap dns-scripts \
--from-file=dns-present.sh=./scripts/dns-present.sh \
--from-file=dns-cleanup.sh=./scripts/dns-cleanup.sh
# Install with DNS-01
helm install certctl certctl/ \
--set server.issuer.acme.enabled=true \
--set server.issuer.acme.challengeType=dns-01 \
--values examples/values-acme-dns01.yaml
```
### Example 3: AWS RDS Database
```bash
helm install certctl certctl/ \
--set postgresql.enabled=false \
--set 'server.env.CERTCTL_DATABASE_URL=postgres://user:password@mydb.c9akciq32.us-east-1.rds.amazonaws.com:5432/certctl?sslmode=require'
```
### Example 4: Multiple Issuers
```bash
helm install certctl certctl/ \
--set server.issuer.local.enabled=true \
--set server.issuer.acme.enabled=true \
--set server.issuer.acme.directoryURL=https://acme-v02.api.letsencrypt.org/directory
```
### Example 5: Email Notifications
```bash
helm install certctl certctl/ \
--set server.smtp.enabled=true \
--set server.smtp.host=smtp.example.com \
--set server.smtp.port=587 \
--set server.smtp.username=alerts@example.com \
--set server.smtp.password="$SMTP_PASSWORD" \
--set server.smtp.fromAddress=certctl@example.com
```
## Post-Deployment Setup
### 1. Initial Database Setup
```bash
# Check database connection
POD=$(kubectl get pods -l app.kubernetes.io/component=postgres -o jsonpath='{.items[0].metadata.name}')
# Execute psql commands
kubectl exec -it $POD -- \
psql -U certctl -d certctl -c '\dt'
# View database status
kubectl logs $POD | tail -20
```
### 2. Create Default Certificates
```bash
# Port forward to API
kubectl port-forward svc/certctl-server 8443:8443 &
# Create a test certificate
API_KEY="your-api-key"
curl -X POST http://localhost:8443/api/v1/certificates \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"common_name": "test.example.com",
"sans": ["test.example.com", "*.example.com"],
"owner": "admin@example.com"
}'
```
### 3. Configure Agents
```bash
# Get agent names
kubectl get pods -l app.kubernetes.io/component=agent -o wide
# Check agent connectivity
POD=$(kubectl get pods -l app.kubernetes.io/component=agent -o jsonpath='{.items[0].metadata.name}')
kubectl logs $POD | grep -i heartbeat
```
### 4. Set Up HTTPS for Web Dashboard
The Ingress will handle TLS if configured properly:
```bash
# Verify ingress is ready
kubectl get ingress
kubectl describe ingress certctl
# Test HTTPS
curl https://certctl.example.com/health
```
## Monitoring and Logging
### 1. View Logs
```bash
# Server logs
kubectl logs -l app.kubernetes.io/component=server -f --all-containers=true
# PostgreSQL logs
kubectl logs -l app.kubernetes.io/component=postgres -f
# Agent logs
kubectl logs -l app.kubernetes.io/component=agent -f --all-containers=true
# Logs from all components
kubectl logs -l app.kubernetes.io/instance=certctl -f --all-containers=true
```
### 2. Install Prometheus Monitoring
```bash
# Install Prometheus operator (if not already installed)
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install prometheus prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--create-namespace
# Certctl will automatically expose metrics if monitoring.enabled=true
helm install certctl certctl/ \
--set monitoring.enabled=true \
--set monitoring.serviceMonitor.enabled=true
```
### 3. Set Up Alerts
```bash
# Create Prometheus alerts
cat <<EOF | kubectl apply -f -
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: certctl-alerts
spec:
groups:
- name: certctl
interval: 30s
rules:
- alert: CertctlServerDown
expr: up{job="certctl-server"} == 0
for: 5m
annotations:
summary: "Certctl server is down"
- alert: CertificateExpiringSoon
expr: certctl_certificate_expiring_soon > 0
for: 1h
annotations:
summary: "{{ \$value }} certificates expiring soon"
EOF
```
## Maintenance
### Scaling
```bash
# Scale server replicas
helm upgrade certctl certctl/ \
--set server.replicas=5
# Scale agents (Deployment kind only)
helm upgrade certctl certctl/ \
--set agent.kind=Deployment \
--set agent.replicas=10
```
### Updating
```bash
# Update chart version
helm repo update
helm upgrade certctl certctl/certctl \
--namespace certctl \
-f values.yaml
# Verify update
kubectl rollout status deployment/certctl-server
kubectl rollout status statefulset/certctl-postgres
```
### Backup and Restore
```bash
# Backup PostgreSQL data
kubectl exec -i $(kubectl get pods -l app.kubernetes.io/component=postgres -o jsonpath='{.items[0].metadata.name}') \
pg_dump -U certctl certctl | gzip > certctl-backup.sql.gz
# Restore from backup
zcat certctl-backup.sql.gz | kubectl exec -i $(kubectl get pods -l app.kubernetes.io/component=postgres -o jsonpath='{.items[0].metadata.name}') \
psql -U certctl certctl
# Backup PVC data
kubectl get pvc
kubectl exec -i $(kubectl get pods -l app.kubernetes.io/component=postgres -o jsonpath='{.items[0].metadata.name}') \
tar czf - /var/lib/postgresql/data | gzip > certctl-data-backup.tar.gz
```
### Uninstall
```bash
# Remove Helm release (keeps PVCs by default)
helm uninstall certctl --namespace certctl
# Delete PVCs if needed
kubectl delete pvc --all -n certctl
# Delete namespace
kubectl delete namespace certctl
```
## Troubleshooting
See [README.md](README.md#troubleshooting) for detailed troubleshooting steps.
Common commands:
```bash
# Get all resources
kubectl get all -n certctl
# Describe pod for events
kubectl describe pod <pod-name> -n certctl
# Stream logs
kubectl logs -f <pod-name> -n certctl
# Execute commands in pod
kubectl exec -it <pod-name> -n certctl -- /bin/sh
# Check events
kubectl get events -n certctl --sort-by='.lastTimestamp'
```
+234
View File
@@ -0,0 +1,234 @@
# Certctl Helm Chart - Complete File Index
## Navigation Guide
### Getting Started
1. **Start here**: `INSTALLATION.md` - Quick installation guide with one-liners
2. **Full reference**: `README.md` - Complete Helm chart documentation
3. **Detailed guide**: `DEPLOYMENT_GUIDE.md` - Step-by-step deployment walkthrough
4. **Architecture**: `CHART_SUMMARY.md` - Technical overview and design
### Chart Directory Structure
```
deploy/helm/
├── README.md Main documentation (15 KB)
├── DEPLOYMENT_GUIDE.md Step-by-step guide (12 KB)
├── CHART_SUMMARY.md Architecture & design (13 KB)
├── INSTALLATION.md Quick start (2.2 KB)
├── INDEX.md This file
├── certctl/ Helm chart package
│ ├── Chart.yaml Chart metadata
│ ├── values.yaml Default configuration (11 KB)
│ ├── .helmignore Build ignore patterns
│ │
│ └── templates/ 15 Kubernetes resource templates
│ ├── _helpers.tpl Helper functions
│ ├── NOTES.txt Post-install notes
│ ├── server-deployment.yaml API server
│ ├── server-service.yaml Server networking
│ ├── server-configmap.yaml Server configuration
│ ├── server-secret.yaml Server secrets
│ ├── postgres-statefulset.yaml Database
│ ├── postgres-service.yaml Database networking
│ ├── postgres-secret.yaml Database secrets
│ ├── agent-daemonset.yaml Agents (DaemonSet/Deployment)
│ ├── agent-configmap.yaml Agent configuration
│ ├── ingress.yaml Optional HTTPS ingress
│ └── serviceaccount.yaml RBAC resources
└── examples/ Example configurations
├── values-dev.yaml Development setup
├── values-prod-ha.yaml Production HA setup
├── values-external-db.yaml External PostgreSQL
└── values-acme-dns01.yaml ACME DNS-01 configuration
```
## File Descriptions
### Documentation Files
| File | Purpose | Size |
|------|---------|------|
| `README.md` | Complete Helm chart documentation, configuration reference, security considerations | 15 KB |
| `DEPLOYMENT_GUIDE.md` | Step-by-step installation instructions, production setup, troubleshooting | 12 KB |
| `CHART_SUMMARY.md` | Technical overview, architecture, features, best practices | 13 KB |
| `INSTALLATION.md` | Quick start guide, one-liner commands, verification steps | 2.2 KB |
| `INDEX.md` | This file - complete file index and navigation | - |
### Chart Files
| File | Purpose |
|------|---------|
| `Chart.yaml` | Helm chart metadata (name, version, appVersion, license) |
| `values.yaml` | Default configuration values with comprehensive comments |
| `.helmignore` | Files to ignore when building the chart |
### Template Files
| File | Components Created |
|------|-------------------|
| `_helpers.tpl` | 14 Helm template helper functions |
| `NOTES.txt` | Post-installation notes and instructions |
| `server-deployment.yaml` | Certctl API server deployment (1-N replicas) |
| `server-service.yaml` | Service exposing the server |
| `server-configmap.yaml` | Non-secret server configuration |
| `server-secret.yaml` | Secrets (API key, DB password, SMTP) |
| `postgres-statefulset.yaml` | PostgreSQL database with persistent storage |
| `postgres-service.yaml` | Headless service for PostgreSQL |
| `postgres-secret.yaml` | Database credentials |
| `agent-daemonset.yaml` | Certctl agents (DaemonSet or Deployment) |
| `agent-configmap.yaml` | Agent configuration |
| `ingress.yaml` | Optional HTTPS ingress resource |
| `serviceaccount.yaml` | ServiceAccount and RBAC resources |
### Example Configuration Files
| File | Use Case | Features |
|------|----------|----------|
| `values-dev.yaml` | Development/testing | Single replica, debug logging, LoadBalancer, no auth |
| `values-prod-ha.yaml` | Production HA | 3 replicas, pod anti-affinity, monitoring, large storage |
| `values-external-db.yaml` | External PostgreSQL | AWS RDS, Cloud SQL, Azure Database, self-managed |
| `values-acme-dns01.yaml` | Let's Encrypt | DNS-01 challenges, wildcard certs, custom DNS scripts |
## Quick Links
### Installation Commands
#### Development
```bash
helm install certctl certctl/ \
--set server.auth.type=none \
--set postgresql.auth.password=dev
```
#### Production HA
```bash
helm install certctl certctl/ \
--values examples/values-prod-ha.yaml \
--set server.auth.apiKey="$(openssl rand -base64 32)" \
--set postgresql.auth.password="$(openssl rand -base64 32)"
```
#### External Database
```bash
helm install certctl certctl/ \
--values examples/values-external-db.yaml \
--set postgresql.enabled=false \
--set 'server.env.CERTCTL_DATABASE_URL=postgres://...'
```
### Verification Commands
```bash
# Check chart syntax
helm lint certctl/
helm template certctl certctl/
# Install in cluster
helm install certctl certctl/
helm status certctl
# Check pod status
kubectl get pods -l app.kubernetes.io/instance=certctl
# View logs
kubectl logs -l app.kubernetes.io/component=server -f
```
## Documentation Organization
### By User Role
**DevOps/Platform Engineers**
- Start: `INSTALLATION.md`
- Deep dive: `DEPLOYMENT_GUIDE.md`
- Configuration reference: `README.md`
**Kubernetes Developers**
- Architecture: `CHART_SUMMARY.md`
- Configuration: `values.yaml`
- Templates: `templates/`
**Security/SREs**
- Security section: `README.md#security-considerations`
- RBAC: `templates/serviceaccount.yaml`
- Network policies: `DEPLOYMENT_GUIDE.md#network-policies`
**Database Administrators**
- PostgreSQL config: `values.yaml` (postgresql section)
- External DB setup: `examples/values-external-db.yaml`
- Backup/restore: `DEPLOYMENT_GUIDE.md#backup-and-restore`
### By Task
**Getting Started**
1. Read: `INSTALLATION.md`
2. Install: `helm install certctl certctl/`
3. Verify: Run commands in `INSTALLATION.md`
**Production Deployment**
1. Read: `DEPLOYMENT_GUIDE.md`
2. Choose: `examples/values-prod-ha.yaml`
3. Deploy: Follow step-by-step guide
4. Reference: `README.md` for detailed options
**Troubleshooting**
- Common issues: `README.md#troubleshooting`
- Detailed guide: `DEPLOYMENT_GUIDE.md#troubleshooting`
- Error messages: kubectl logs and events
**Configuration**
- All options: `values.yaml`
- Examples: `examples/values-*.yaml`
- Detailed docs: `README.md#configuration`
## Key Features
### High Availability
- Multi-replica server deployment
- Pod anti-affinity
- StatefulSet for database
- Pod disruption budgets
### Security
- Non-root containers
- Read-only filesystems
- RBAC support
- Kubernetes Secrets
- Network policies
### Flexibility
- Multiple issuers (Local CA, ACME, step-ca, OpenSSL)
- Internal or external PostgreSQL
- DaemonSet or Deployment agents
- Optional Ingress with TLS
- Email notifications
### Observability
- Health checks
- Structured logging
- Prometheus metrics
- ServiceMonitor support
## Support
- **GitHub**: https://github.com/shankar0123/certctl
- **Issues**: Report on GitHub issues
- **Documentation**: All docs are in `deploy/helm/`
## File Statistics
- **Total files**: 24
- **Documentation**: 4 files (42 KB)
- **Chart files**: 3 files
- **Templates**: 13 files
- **Examples**: 4 files
- **Total size**: 144 KB
## License
All files are covered under the BSL-1.1 license (converts to Apache 2.0 in 2033).
+95
View File
@@ -0,0 +1,95 @@
# Quick Installation Guide
## One-Liner Installation
### Development (no auth)
```bash
helm install certctl certctl/ \
--set server.auth.type=none \
--set postgresql.auth.password=dev
```
### Production (with API key)
```bash
API_KEY=$(openssl rand -base64 32)
DB_PASSWORD=$(openssl rand -base64 32)
helm install certctl certctl/ \
--values examples/values-prod-ha.yaml \
--set server.auth.apiKey="$API_KEY" \
--set postgresql.auth.password="$DB_PASSWORD"
```
## Verify Installation
```bash
# Wait for pods to be ready
kubectl rollout status deployment/certctl-server
kubectl rollout status statefulset/certctl-postgres
# Check all components
kubectl get pods -l app.kubernetes.io/instance=certctl
# View server logs
kubectl logs -l app.kubernetes.io/component=server -f
# Access the API
kubectl port-forward svc/certctl-server 8443:8443 &
curl http://localhost:8443/health
```
## Next Steps
1. **Read Documentation**
- `README.md` - Complete reference
- `DEPLOYMENT_GUIDE.md` - Step-by-step guide
- `CHART_SUMMARY.md` - Architecture overview
2. **Configure for Your Environment**
- Review `examples/` for your deployment scenario
- Customize `values.yaml` as needed
- Use `helm upgrade` to apply changes
3. **Set Up Monitoring**
- Install Prometheus (optional)
- Enable Ingress with HTTPS
- Configure email notifications
4. **Deploy Agents**
- Agents deploy automatically as DaemonSet
- Verify with: `kubectl get pods -l app.kubernetes.io/component=agent`
5. **Create Certificates**
- Configure issuer connectors (Local CA, ACME, etc.)
- Access web dashboard at ingress or port-forward
## Common Commands
```bash
# List installations
helm list
# View chart values
helm values certctl
# Upgrade chart
helm upgrade certctl certctl/ -f new-values.yaml
# Rollback to previous version
helm rollback certctl 1
# Uninstall chart
helm uninstall certctl
# View deployment history
helm history certctl
# Dry-run installation to see generated YAML
helm install certctl certctl/ --dry-run --debug
```
## Support
- Full documentation in `README.md`
- Troubleshooting in `DEPLOYMENT_GUIDE.md`
- Issues: https://github.com/shankar0123/certctl
+516
View File
@@ -0,0 +1,516 @@
# Certctl Helm Chart
Production-ready Helm chart for deploying certctl (self-hosted certificate lifecycle management platform) on Kubernetes.
## Table of Contents
1. [Quick Start](#quick-start)
2. [Chart Features](#chart-features)
3. [Prerequisites](#prerequisites)
4. [Installation](#installation)
5. [Configuration](#configuration)
6. [Usage Examples](#usage-examples)
7. [Upgrading](#upgrading)
8. [Uninstalling](#uninstalling)
9. [Architecture](#architecture)
10. [Security Considerations](#security-considerations)
11. [Troubleshooting](#troubleshooting)
## Quick Start
```bash
# 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
```bash
helm repo add certctl https://charts.example.com
helm repo update
helm install certctl certctl/certctl -f my-values.yaml
```
### 2. Using Local Chart
```bash
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
```bash
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
```yaml
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
```yaml
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
```yaml
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
```yaml
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
```yaml
# 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:
```bash
helm install certctl certctl/certctl -f ha-values.yaml
```
### Example 2: External PostgreSQL Database
```yaml
# external-db-values.yaml
postgresql:
enabled: false
server:
env:
CERTCTL_DATABASE_URL: "postgres://user:password@rds.example.com:5432/certctl?sslmode=require"
```
Deploy with:
```bash
helm install certctl certctl/certctl -f external-db-values.yaml
```
### Example 3: ACME + Let's Encrypt
```yaml
# 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
```yaml
# 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
```bash
# 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
```bash
# 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
```bash
# 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:**
```yaml
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:
```yaml
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:
```bash
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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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:
```yaml
# Temporarily allow root for debugging
server:
securityContext:
runAsUser: 0 # NOT FOR PRODUCTION
```
### 6. Out of Memory
Increase resource limits:
```bash
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:
```bash
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:
- GitHub: https://github.com/shankar0123/certctl
- Documentation: https://github.com/shankar0123/certctl/tree/main/docs
## License
BSL-1.1 (converts to Apache 2.0 in 2033)
+31
View File
@@ -0,0 +1,31 @@
# Patterns to ignore when building packages.
# This supports shell glob patterns, relative path patterns, and negated
# patterns. Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.swo
*~
*.pyo
*.pyc
.pytest_cache/
*.egg-info/
dist/
build/
# IDE
.vscode/
.idea/
*.sublime-project
*.sublime-workspace
# OS
Thumbs.db
# Helm
Chart.lock
+20
View File
@@ -0,0 +1,20 @@
apiVersion: v2
name: certctl
description: Self-hosted certificate lifecycle management platform
type: application
version: 0.1.0
appVersion: "2.1.0"
keywords:
- certificate
- tls
- ssl
- pki
- acme
- lifecycle
- kubernetes
maintainers:
- name: certctl
home: https://github.com/shankar0123/certctl
sources:
- https://github.com/shankar0123/certctl
license: BSL-1.1
+68
View File
@@ -0,0 +1,68 @@
1. Get the certctl Server URL by running:
{{- if .Values.ingress.enabled }}
https://{{ index .Values.ingress.hosts 0 "host" }}
{{- else if contains "NodePort" .Values.server.service.type }}
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "certctl.fullname" . }}-server)
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.server.service.type }}
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "certctl.fullname" . }}-server --template "{.status.loadBalancer.ingress[0].ip}")
echo http://$SERVICE_IP:{{ .Values.server.service.port }}
{{- else }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "certctl.name" . }},app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=server" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}
2. Get the default API key:
kubectl get secret --namespace {{ .Release.Namespace }} {{ include "certctl.fullname" . }}-server -o jsonpath="{.data.api-key}" | base64 --decode; echo
3. Get PostgreSQL connection details:
Host: {{ include "certctl.fullname" . }}-postgres.{{ .Release.Namespace }}.svc.cluster.local
Port: 5432
Database: {{ .Values.postgresql.auth.database }}
Username: {{ .Values.postgresql.auth.username }}
Password: $(kubectl get secret --namespace {{ .Release.Namespace }} {{ include "certctl.fullname" . }}-postgres -o jsonpath="{.data.password}" | base64 --decode)
4. Check deployment status:
kubectl get pods -n {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }}
5. View server logs:
kubectl logs -n {{ .Release.Namespace }} -l app.kubernetes.io/name={{ include "certctl.name" . }},app.kubernetes.io/component=server -f
{{- if .Values.agent.enabled }}
6. View agent logs:
kubectl logs -n {{ .Release.Namespace }} -l app.kubernetes.io/name={{ include "certctl.name" . }},app.kubernetes.io/component=agent -f
{{- end }}
IMPORTANT NOTES FOR PRODUCTION:
1. Update the API key for security:
kubectl patch secret {{ include "certctl.fullname" . }}-server -n {{ .Release.Namespace }} \
-p '{"data":{"api-key":"'$(echo -n "YOUR_NEW_API_KEY" | base64)'"}}'
2. Update PostgreSQL password:
kubectl patch secret {{ include "certctl.fullname" . }}-postgres -n {{ .Release.Namespace }} \
-p '{"data":{"password":"'$(echo -n "YOUR_NEW_PASSWORD" | base64)'"}}'
3. Configure certificate issuers (ACME, step-ca, etc.) via values.yaml:
helm upgrade {{ .Release.Name }} certctl/certctl \
--set server.issuer.acme.enabled=true \
--set server.issuer.acme.directoryURL=https://acme-v02.api.letsencrypt.org/directory \
--set server.issuer.acme.email=admin@example.com
4. For production with persistent databases and backups:
- Use an external PostgreSQL managed service (AWS RDS, Cloud SQL, etc.)
- Set postgresql.enabled=false and configure CERTCTL_DATABASE_URL in values
5. Enable HTTPS/TLS using an Ingress with certificate management:
- Configure cert-manager for automatic TLS certificate renewal
- Update ingress values with your domain and certificate issuer
6. Review security contexts and network policies:
- All containers run as non-root
- Implement network policies to restrict traffic between components
- Consider pod security policies or security standards for your cluster
+125
View File
@@ -0,0 +1,125 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "certctl.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "certctl.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "certctl.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "certctl.labels" -}}
helm.sh/chart: {{ include "certctl.chart" . }}
{{ include "certctl.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- with .Values.commonLabels }}
{{ toYaml . }}
{{- end }}
{{- end }}
{{/*
Selector labels for the main service (server, agent, postgres)
*/}}
{{- define "certctl.selectorLabels" -}}
app.kubernetes.io/name: {{ include "certctl.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Server selector labels
*/}}
{{- define "certctl.serverSelectorLabels" -}}
{{ include "certctl.selectorLabels" . }}
app.kubernetes.io/component: server
{{- end }}
{{/*
Agent selector labels
*/}}
{{- define "certctl.agentSelectorLabels" -}}
{{ include "certctl.selectorLabels" . }}
app.kubernetes.io/component: agent
{{- end }}
{{/*
PostgreSQL selector labels
*/}}
{{- define "certctl.postgresSelectorLabels" -}}
{{ include "certctl.selectorLabels" . }}
app.kubernetes.io/component: postgres
{{- end }}
{{/*
Service account name
*/}}
{{- define "certctl.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "certctl.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
{{/*
Server image
*/}}
{{- define "certctl.serverImage" -}}
{{- $image := .Values.server.image }}
{{- printf "%s:%s" $image.repository (coalesce $image.tag .Chart.AppVersion) }}
{{- end }}
{{/*
Agent image
*/}}
{{- define "certctl.agentImage" -}}
{{- $image := .Values.agent.image }}
{{- printf "%s:%s" $image.repository (coalesce $image.tag .Chart.AppVersion) }}
{{- end }}
{{/*
PostgreSQL image
*/}}
{{- define "certctl.postgresImage" -}}
{{- $image := .Values.postgresql.image }}
{{- printf "%s:%s" $image.repository $image.tag }}
{{- end }}
{{/*
Database connection string
*/}}
{{- define "certctl.databaseURL" -}}
postgres://{{ .Values.postgresql.auth.username }}:$(POSTGRES_PASSWORD)@{{ include "certctl.fullname" . }}-postgres:5432/{{ .Values.postgresql.auth.database }}?sslmode=disable
{{- end }}
{{/*
Server URL (for agents)
*/}}
{{- define "certctl.serverURL" -}}
http://{{ include "certctl.fullname" . }}-server:{{ .Values.server.service.port }}
{{- end }}
@@ -0,0 +1,13 @@
{{- if .Values.agent.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "certctl.fullname" . }}-agent
labels:
{{- include "certctl.labels" . | nindent 4 }}
app.kubernetes.io/component: agent
data:
{{- if .Values.agent.discoveryDirs }}
discovery-dirs: {{ .Values.agent.discoveryDirs | quote }}
{{- end }}
{{- end }}
@@ -0,0 +1,162 @@
{{- if .Values.agent.enabled }}
{{- if eq .Values.agent.kind "DaemonSet" }}
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: {{ include "certctl.fullname" . }}-agent
labels:
{{- include "certctl.labels" . | nindent 4 }}
app.kubernetes.io/component: agent
spec:
selector:
matchLabels:
{{- include "certctl.agentSelectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "certctl.agentSelectorLabels" . | nindent 8 }}
spec:
serviceAccountName: {{ include "certctl.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.agent.securityContext | nindent 8 }}
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.agent.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.agent.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.agent.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: agent
image: {{ include "certctl.agentImage" . }}
imagePullPolicy: {{ .Values.agent.image.pullPolicy }}
env:
- name: CERTCTL_SERVER_URL
value: {{ include "certctl.serverURL" . }}
- name: CERTCTL_API_KEY
valueFrom:
secretKeyRef:
name: {{ include "certctl.fullname" . }}-server
key: api-key
- name: CERTCTL_AGENT_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: CERTCTL_KEY_DIR
value: {{ .Values.agent.keyDir }}
{{- if .Values.agent.discoveryDirs }}
- name: CERTCTL_DISCOVERY_DIRS
valueFrom:
configMapKeyRef:
name: {{ include "certctl.fullname" . }}-agent
key: discovery-dirs
{{- end }}
{{- with .Values.agent.env }}
{{- toYaml . | nindent 12 }}
{{- end }}
resources:
{{- toYaml .Values.agent.resources | nindent 12 }}
volumeMounts:
- name: agent-keys
mountPath: {{ .Values.agent.keyDir }}
- name: tmp
mountPath: /tmp
volumes:
- name: agent-keys
emptyDir:
sizeLimit: 1Gi
- name: tmp
emptyDir: {}
{{- else if eq .Values.agent.kind "Deployment" }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "certctl.fullname" . }}-agent
labels:
{{- include "certctl.labels" . | nindent 4 }}
app.kubernetes.io/component: agent
spec:
replicas: {{ .Values.agent.replicas }}
selector:
matchLabels:
{{- include "certctl.agentSelectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "certctl.agentSelectorLabels" . | nindent 8 }}
spec:
serviceAccountName: {{ include "certctl.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.agent.securityContext | nindent 8 }}
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.agent.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.agent.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.agent.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: agent
image: {{ include "certctl.agentImage" . }}
imagePullPolicy: {{ .Values.agent.image.pullPolicy }}
env:
- name: CERTCTL_SERVER_URL
value: {{ include "certctl.serverURL" . }}
- name: CERTCTL_API_KEY
valueFrom:
secretKeyRef:
name: {{ include "certctl.fullname" . }}-server
key: api-key
- name: CERTCTL_AGENT_NAME
{{- if .Values.agent.name }}
value: {{ .Values.agent.name | quote }}
{{- else }}
valueFrom:
fieldRef:
fieldPath: metadata.name
{{- end }}
- name: CERTCTL_KEY_DIR
value: {{ .Values.agent.keyDir }}
{{- if .Values.agent.discoveryDirs }}
- name: CERTCTL_DISCOVERY_DIRS
valueFrom:
configMapKeyRef:
name: {{ include "certctl.fullname" . }}-agent
key: discovery-dirs
{{- end }}
{{- with .Values.agent.env }}
{{- toYaml . | nindent 12 }}
{{- end }}
resources:
{{- toYaml .Values.agent.resources | nindent 12 }}
volumeMounts:
- name: agent-keys
mountPath: {{ .Values.agent.keyDir }}
- name: tmp
mountPath: /tmp
volumes:
- name: agent-keys
emptyDir:
sizeLimit: 1Gi
- name: tmp
emptyDir: {}
{{- end }}
{{- end }}
@@ -0,0 +1,41 @@
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "certctl.fullname" . }}
labels:
{{- include "certctl.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.className }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "certctl.fullname" . }}-server
port:
number: {{ $.Values.server.service.port }}
{{- end }}
{{- end }}
{{- end }}
@@ -0,0 +1,15 @@
apiVersion: v1
kind: Secret
metadata:
name: {{ include "certctl.fullname" . }}-postgres
labels:
{{- include "certctl.labels" . | nindent 4 }}
app.kubernetes.io/component: postgres
type: Opaque
stringData:
{{- if not .Values.postgresql.auth.password }}
{{- fail "postgresql.auth.password is required" }}
{{- end }}
password: {{ .Values.postgresql.auth.password | quote }}
username: {{ .Values.postgresql.auth.username | quote }}
database: {{ .Values.postgresql.auth.database | quote }}
@@ -0,0 +1,18 @@
{{- if .Values.postgresql.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "certctl.fullname" . }}-postgres
labels:
{{- include "certctl.labels" . | nindent 4 }}
app.kubernetes.io/component: postgres
spec:
clusterIP: None
ports:
- port: {{ .Values.postgresql.service.port }}
targetPort: postgres
protocol: TCP
name: postgres
selector:
{{- include "certctl.postgresSelectorLabels" . | nindent 4 }}
{{- end }}
@@ -0,0 +1,79 @@
{{- if .Values.postgresql.enabled }}
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ include "certctl.fullname" . }}-postgres
labels:
{{- include "certctl.labels" . | nindent 4 }}
app.kubernetes.io/component: postgres
spec:
serviceName: {{ include "certctl.fullname" . }}-postgres
replicas: 1
selector:
matchLabels:
{{- include "certctl.postgresSelectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "certctl.postgresSelectorLabels" . | nindent 8 }}
spec:
securityContext:
{{- toYaml .Values.postgresql.securityContext | nindent 8 }}
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: postgres
image: {{ include "certctl.postgresImage" . }}
imagePullPolicy: {{ .Values.postgresql.image.pullPolicy }}
ports:
- name: postgres
containerPort: 5432
protocol: TCP
env:
- name: POSTGRES_DB
valueFrom:
secretKeyRef:
name: {{ include "certctl.fullname" . }}-postgres
key: database
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: {{ include "certctl.fullname" . }}-postgres
key: username
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "certctl.fullname" . }}-postgres
key: password
- name: POSTGRES_INITDB_ARGS
value: "--encoding=UTF8"
livenessProbe:
{{- toYaml .Values.postgresql.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.postgresql.readinessProbe | nindent 12 }}
resources:
{{- toYaml .Values.postgresql.resources | nindent 12 }}
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
subPath: postgres
- name: postgres-init
mountPath: /docker-entrypoint-initdb.d
volumes:
- name: postgres-init
emptyDir: {}
volumeClaimTemplates:
- metadata:
name: postgres-data
spec:
accessModes:
- ReadWriteOnce
{{- if .Values.postgresql.storage.storageClass }}
storageClassName: {{ .Values.postgresql.storage.storageClass }}
{{- end }}
resources:
requests:
storage: {{ .Values.postgresql.storage.size }}
{{- end }}
@@ -0,0 +1,36 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "certctl.fullname" . }}-server
labels:
{{- include "certctl.labels" . | nindent 4 }}
app.kubernetes.io/component: server
data:
log-level: {{ .Values.server.logging.level | quote }}
auth-type: {{ .Values.server.auth.type | quote }}
keygen-mode: {{ .Values.server.keygen.mode | quote }}
rate-limit-rps: {{ .Values.server.rateLimiting.rps | quote }}
rate-limit-burst: {{ .Values.server.rateLimiting.burst | quote }}
{{- if .Values.server.cors.origins }}
cors-origins: {{ .Values.server.cors.origins | quote }}
{{- end }}
{{- if .Values.server.networkScan.enabled }}
network-scan-interval: {{ .Values.server.networkScan.interval | quote }}
{{- end }}
{{- if .Values.server.est.enabled }}
est-issuer-id: {{ .Values.server.est.issuerID | quote }}
{{- if .Values.server.est.profileID }}
est-profile-id: {{ .Values.server.est.profileID | quote }}
{{- end }}
{{- end }}
{{- if .Values.server.smtp.enabled }}
smtp-host: {{ .Values.server.smtp.host | quote }}
smtp-port: {{ .Values.server.smtp.port | quote }}
smtp-username: {{ .Values.server.smtp.username | quote }}
smtp-from-address: {{ .Values.server.smtp.fromAddress | quote }}
{{- end }}
{{- if .Values.server.issuer.acme.enabled }}
acme-directory-url: {{ .Values.server.issuer.acme.directoryURL | quote }}
acme-email: {{ .Values.server.issuer.acme.email | quote }}
acme-challenge-type: {{ .Values.server.issuer.acme.challengeType | quote }}
{{- end }}
@@ -0,0 +1,196 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "certctl.fullname" . }}-server
labels:
{{- include "certctl.labels" . | nindent 4 }}
app.kubernetes.io/component: server
spec:
{{- if ne .Values.server.replicas 1 }}
replicas: {{ .Values.server.replicas }}
{{- end }}
selector:
matchLabels:
{{- include "certctl.serverSelectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "certctl.serverSelectorLabels" . | nindent 8 }}
annotations:
checksum/config: {{ include (print $.Template.BasePath "/server-configmap.yaml") . | sha256sum }}
checksum/secret: {{ include (print $.Template.BasePath "/server-secret.yaml") . | sha256sum }}
spec:
serviceAccountName: {{ include "certctl.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.server.securityContext | nindent 8 }}
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: server
image: {{ include "certctl.serverImage" . }}
imagePullPolicy: {{ .Values.server.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.server.port }}
protocol: TCP
env:
- name: CERTCTL_SERVER_HOST
value: "0.0.0.0"
- name: CERTCTL_SERVER_PORT
value: "{{ .Values.server.port }}"
- name: CERTCTL_DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ include "certctl.fullname" . }}-server
key: database-url
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "certctl.fullname" . }}-postgres
key: password
- name: CERTCTL_LOG_LEVEL
valueFrom:
configMapKeyRef:
name: {{ include "certctl.fullname" . }}-server
key: log-level
- name: CERTCTL_LOG_FORMAT
value: "json"
- name: CERTCTL_AUTH_TYPE
valueFrom:
configMapKeyRef:
name: {{ include "certctl.fullname" . }}-server
key: auth-type
{{- if eq .Values.server.auth.type "api-key" }}
- name: CERTCTL_AUTH_SECRET
valueFrom:
secretKeyRef:
name: {{ include "certctl.fullname" . }}-server
key: api-key
{{- end }}
- name: CERTCTL_KEYGEN_MODE
valueFrom:
configMapKeyRef:
name: {{ include "certctl.fullname" . }}-server
key: keygen-mode
- name: CERTCTL_RATE_LIMIT_RPS
valueFrom:
configMapKeyRef:
name: {{ include "certctl.fullname" . }}-server
key: rate-limit-rps
- name: CERTCTL_RATE_LIMIT_BURST
valueFrom:
configMapKeyRef:
name: {{ include "certctl.fullname" . }}-server
key: rate-limit-burst
{{- if .Values.server.cors.origins }}
- name: CERTCTL_CORS_ORIGINS
valueFrom:
configMapKeyRef:
name: {{ include "certctl.fullname" . }}-server
key: cors-origins
{{- end }}
{{- if .Values.server.networkScan.enabled }}
- name: CERTCTL_NETWORK_SCAN_ENABLED
value: "true"
- name: CERTCTL_NETWORK_SCAN_INTERVAL
valueFrom:
configMapKeyRef:
name: {{ include "certctl.fullname" . }}-server
key: network-scan-interval
{{- end }}
{{- if .Values.server.est.enabled }}
- name: CERTCTL_EST_ENABLED
value: "true"
- name: CERTCTL_EST_ISSUER_ID
valueFrom:
configMapKeyRef:
name: {{ include "certctl.fullname" . }}-server
key: est-issuer-id
{{- if .Values.server.est.profileID }}
- name: CERTCTL_EST_PROFILE_ID
valueFrom:
configMapKeyRef:
name: {{ include "certctl.fullname" . }}-server
key: est-profile-id
{{- end }}
{{- end }}
{{- if .Values.server.smtp.enabled }}
- name: CERTCTL_SMTP_HOST
valueFrom:
configMapKeyRef:
name: {{ include "certctl.fullname" . }}-server
key: smtp-host
- name: CERTCTL_SMTP_PORT
valueFrom:
configMapKeyRef:
name: {{ include "certctl.fullname" . }}-server
key: smtp-port
- name: CERTCTL_SMTP_USERNAME
valueFrom:
configMapKeyRef:
name: {{ include "certctl.fullname" . }}-server
key: smtp-username
- name: CERTCTL_SMTP_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "certctl.fullname" . }}-server
key: smtp-password
- name: CERTCTL_SMTP_FROM_ADDRESS
valueFrom:
configMapKeyRef:
name: {{ include "certctl.fullname" . }}-server
key: smtp-from-address
{{- end }}
{{- if .Values.server.issuer.acme.enabled }}
- name: CERTCTL_ACME_DIRECTORY_URL
valueFrom:
configMapKeyRef:
name: {{ include "certctl.fullname" . }}-server
key: acme-directory-url
- name: CERTCTL_ACME_EMAIL
valueFrom:
configMapKeyRef:
name: {{ include "certctl.fullname" . }}-server
key: acme-email
- name: CERTCTL_ACME_CHALLENGE_TYPE
valueFrom:
configMapKeyRef:
name: {{ include "certctl.fullname" . }}-server
key: acme-challenge-type
{{- end }}
{{- with .Values.server.env }}
{{- toYaml . | nindent 12 }}
{{- end }}
livenessProbe:
{{- toYaml .Values.server.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.server.readinessProbe | nindent 12 }}
resources:
{{- toYaml .Values.server.resources | nindent 12 }}
volumeMounts:
- name: tmp
mountPath: /tmp
{{- if .Values.server.volumeMounts }}
{{- toYaml .Values.server.volumeMounts | nindent 12 }}
{{- end }}
volumes:
- name: tmp
emptyDir: {}
{{- if .Values.server.volumes }}
{{- toYaml .Values.server.volumes | nindent 8 }}
{{- end }}
{{- with .Values.nodeAffinity }}
affinity:
nodeAffinity:
{{- toYaml . | nindent 10 }}
{{- else if .Values.podAntiAffinity }}
affinity:
podAntiAffinity:
{{- toYaml . | nindent 10 }}
{{- else if .Values.podAffinity }}
affinity:
podAffinity:
{{- toYaml . | nindent 10 }}
{{- end }}
@@ -0,0 +1,19 @@
apiVersion: v1
kind: Secret
metadata:
name: {{ include "certctl.fullname" . }}-server
labels:
{{- include "certctl.labels" . | nindent 4 }}
app.kubernetes.io/component: server
type: Opaque
stringData:
database-url: postgres://{{ .Values.postgresql.auth.username }}:$(POSTGRES_PASSWORD)@{{ include "certctl.fullname" . }}-postgres:5432/{{ .Values.postgresql.auth.database }}?sslmode=disable
{{- if eq .Values.server.auth.type "api-key" }}
{{- if not .Values.server.auth.apiKey }}
{{- fail "server.auth.apiKey is required when server.auth.type is 'api-key'" }}
{{- end }}
api-key: {{ .Values.server.auth.apiKey | quote }}
{{- end }}
{{- if .Values.server.smtp.enabled }}
smtp-password: {{ .Values.server.smtp.password | quote }}
{{- end }}
@@ -0,0 +1,20 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "certctl.fullname" . }}-server
labels:
{{- include "certctl.labels" . | nindent 4 }}
app.kubernetes.io/component: server
{{- with .Values.server.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.server.service.type }}
ports:
- port: {{ .Values.server.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "certctl.serverSelectorLabels" . | nindent 4 }}
@@ -0,0 +1,37 @@
{{- if .Values.serviceAccount.create }}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "certctl.serviceAccountName" . }}
labels:
{{- include "certctl.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
{{- if .Values.rbac.create }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "certctl.fullname" . }}
labels:
{{- include "certctl.labels" . | nindent 4 }}
rules: []
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "certctl.fullname" . }}
labels:
{{- include "certctl.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "certctl.fullname" . }}
subjects:
- kind: ServiceAccount
name: {{ include "certctl.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- end }}
+434
View File
@@ -0,0 +1,434 @@
# Default values for certctl Helm chart
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
# Namespace override (optional)
namespace: ""
# Global configuration
commonLabels: {}
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
# ==============================================================================
# Certctl Server Configuration
# ==============================================================================
server:
# Number of replicas (for HA deployments)
replicas: 1
# Image configuration
image:
repository: ghcr.io/shankar0123/certctl
tag: "" # defaults to Chart.appVersion
pullPolicy: IfNotPresent
# Server port
port: 8443
# Resource requests and limits
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
# Pod security context
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
# Liveness and readiness probes
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /readyz
port: http
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 2
# Service type (ClusterIP, LoadBalancer, NodePort)
service:
type: ClusterIP
port: 8443
annotations: {}
# Authentication configuration
auth:
type: api-key # Options: api-key, none (for demo only)
apiKey: "" # REQUIRED in production - set via --set or values override
# Logging configuration
logging:
level: info # debug, info, warn, error
format: json # json or text
# SMTP configuration for email notifications (optional)
smtp:
enabled: false
host: ""
port: 587
username: ""
password: ""
fromAddress: ""
useTLS: true
# Certificate digest digest (periodic email summary)
digest:
enabled: false
interval: "24h"
recipients: []
# Example:
# - admin@example.com
# - ops@example.com
# Enrollment over Secure Transport (EST) configuration
est:
enabled: false
issuerID: "iss-local"
profileID: ""
# Rate limiting configuration
rateLimiting:
rps: 100 # Requests per second
burst: 200 # Burst capacity
# Network scanning configuration
networkScan:
enabled: false
interval: "6h"
# Certificate key generation mode
keygen:
mode: agent # Options: agent (production), server (demo with warning)
# CORS configuration
cors:
origins: "" # Comma-separated list, empty means deny all cross-origin requests
# Issuer connectors configuration
issuer:
local:
enabled: true
# For sub-CA mode, provide these paths:
# caCertPath: /path/to/ca.crt
# caKeyPath: /path/to/ca.key
acme:
enabled: false
directoryURL: ""
email: ""
challengeType: "http-01" # Options: http-01, dns-01, dns-persist-01
# DNS configuration (for dns-01 or dns-persist-01)
# dnsPresentScript: /path/to/dns-present.sh
# dnsCleanupScript: /path/to/dns-cleanup.sh
# dnsPropagationWait: "30s"
# dnsPersistIssuerDomain: "validation.example.com"
# EAB configuration (for ZeroSSL, Google Trust Services, etc.)
# eabKid: ""
# eabHmac: ""
stepca:
enabled: false
# rootCAPath: /path/to/root_ca.crt
# intermediateCAPath: /path/to/intermediate_ca.crt
# provisionerName: ""
# provisionerPassword: ""
openssl:
enabled: false
# signScript: /path/to/sign.sh
# revokeScript: /path/to/revoke.sh
# crlScript: /path/to/crl.sh
# timeoutSeconds: 30
# Notifier connectors configuration
notifiers:
slack:
enabled: false
# webhookUrl: ""
# channel: ""
# username: ""
# iconEmoji: ""
teams:
enabled: false
# webhookUrl: ""
pagerduty:
enabled: false
# routingKey: ""
# severity: warning
opsgenie:
enabled: false
# apiKey: ""
# priority: P3
# Additional environment variables
# Will be passed as-is to the server container
env: {}
# Example:
# CERTCTL_SCHEDULER_RENEWAL_CHECK_INTERVAL: "1h"
# CERTCTL_DATABASE_MAX_CONNS: "25"
# Additional volume mounts for custom configurations
# volumeMounts: []
# - name: ca-cert
# mountPath: /etc/ssl/certs/ca.crt
# subPath: ca.crt
# Additional volumes
# volumes: []
# - name: ca-cert
# secret:
# secretName: ca-cert
# ==============================================================================
# PostgreSQL Configuration
# ==============================================================================
postgresql:
# Enable/disable PostgreSQL (set to false if using external database)
enabled: true
# Image configuration
image:
repository: postgres
tag: "16-alpine"
pullPolicy: IfNotPresent
# Authentication
auth:
database: certctl
username: certctl
password: "" # REQUIRED - set via --set or values override
# Storage configuration
storage:
size: 10Gi
storageClass: "" # Uses default StorageClass if empty
# deleteOnTermination: false # Keep data on Helm uninstall
# Resource requests and limits
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
# Pod security context
securityContext:
runAsNonRoot: true
runAsUser: 999
runAsGroup: 999
fsGroup: 999
# Liveness and readiness probes
livenessProbe:
exec:
command:
- /bin/sh
- -c
- pg_isready -U certctl -d certctl
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
exec:
command:
- /bin/sh
- -c
- pg_isready -U certctl -d certctl
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 2
# Service configuration
service:
type: ClusterIP
port: 5432
# PostgreSQL-specific settings
postgresqlConfig: {}
# Example:
# max_connections: "200"
# shared_buffers: "256MB"
# ==============================================================================
# Certctl Agent Configuration
# ==============================================================================
agent:
# Enable/disable agent deployment
enabled: true
# Deployment strategy: DaemonSet (recommended) or Deployment
kind: DaemonSet # Options: DaemonSet, Deployment
# Image configuration
image:
repository: ghcr.io/shankar0123/certctl-agent
tag: "" # defaults to Chart.appVersion
pullPolicy: IfNotPresent
# Number of replicas (for Deployment kind; ignored for DaemonSet)
replicas: 1
# Resource requests and limits
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
# Pod security context
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
# Agent name (can be overridden per pod via StatefulSet ordinals)
name: "" # If empty, uses release name
# Key storage directory
keyDir: /var/lib/certctl/keys
# Certificate discovery directories (comma-separated)
discoveryDirs: ""
# Example: "/etc/ssl/certs,/etc/pki/tls"
# Node selector for agent pods (for DaemonSet)
nodeSelector: {}
# Example:
# node-role.kubernetes.io/worker: "true"
# Tolerations for agent pods
tolerations: []
# Example:
# - key: node-role
# operator: Equal
# value: worker
# effect: NoSchedule
# Affinity rules
affinity: {}
# Additional environment variables
env: {}
# ==============================================================================
# Ingress Configuration
# ==============================================================================
ingress:
enabled: false
className: ""
annotations: {}
# kubernetes.io/ingress.class: nginx
# cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: certctl.local
paths:
- path: /
pathType: Prefix
tls: []
# - secretName: certctl-tls
# hosts:
# - certctl.local
# ==============================================================================
# Service Account Configuration
# ==============================================================================
serviceAccount:
create: true
annotations: {}
name: "" # defaults to release name if empty
# ==============================================================================
# RBAC Configuration
# ==============================================================================
rbac:
create: true
# ==============================================================================
# Pod Disruption Budget (for HA deployments)
# ==============================================================================
podDisruptionBudget:
enabled: false
minAvailable: 1
# maxUnavailable: 1
# ==============================================================================
# Monitoring Configuration
# ==============================================================================
monitoring:
enabled: false
# Prometheus ServiceMonitor
serviceMonitor:
enabled: false
interval: 30s
scrapeTimeout: 10s
# labels: {}
# selector: {}
# ==============================================================================
# Advanced Configuration
# ==============================================================================
# Node affinity for server pods
nodeAffinity: {}
# Pod affinity for server pods
podAffinity: {}
# Pod anti-affinity for server pods (for HA)
podAntiAffinity: {}
# Example:
# podAntiAffinity:
# preferredDuringSchedulingIgnoredDuringExecution:
# - weight: 100
# podAffinityTerm:
# labelSelector:
# matchExpressions:
# - key: app.kubernetes.io/name
# operator: In
# values:
# - certctl
# topologyKey: kubernetes.io/hostname
# Custom labels for all resources
customLabels: {}
# Custom annotations for all resources
customAnnotations: {}
@@ -0,0 +1,77 @@
# Certctl with ACME DNS-01 Challenge (Let's Encrypt)
# Enables automatic certificate issuance from Let's Encrypt
# using DNS-01 verification (wildcard-capable)
server:
auth:
type: api-key
apiKey: "CHANGE_ME"
issuer:
local:
enabled: true
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
# For DNS-PERSIST-01 (standing validation record, no per-renewal updates):
# challengeType: dns-persist-01
# dnsPersistIssuerDomain: validation.example.com
# Mount DNS scripts as ConfigMap
volumes:
- name: dns-scripts
configMap:
name: dns-scripts
defaultMode: 0755
volumeMounts:
- name: dns-scripts
mountPath: /scripts
readOnly: true
postgresql:
enabled: true
storage:
size: 20Gi
agent:
enabled: true
kind: DaemonSet
ingress:
enabled: true
className: nginx
hosts:
- host: certctl.example.com
paths:
- path: /
pathType: Prefix
---
# You'll need to create the DNS scripts ConfigMap separately:
#
# kubectl create configmap dns-scripts \
# --from-file=dns-present.sh=./scripts/dns-present.sh \
# --from-file=dns-cleanup.sh=./scripts/dns-cleanup.sh
#
# Example dns-present.sh (Cloudflare):
# #!/bin/bash
# DOMAIN=$1
# TOKEN=$2
#
# curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records" \
# -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
# -d "{\"type\":\"TXT\",\"name\":\"_acme-challenge.${DOMAIN}\",\"content\":\"${TOKEN}\"}"
#
# Example dns-cleanup.sh (Cloudflare):
# #!/bin/bash
# DOMAIN=$1
#
# curl -X DELETE "https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records/{record_id}" \
# -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}"
+99
View File
@@ -0,0 +1,99 @@
# Certctl Development Configuration
# Lightweight setup for development and testing
# - Single server replica
# - Small PostgreSQL storage
# - Minimal resource limits
# - No ingress or monitoring
# - Demo auth mode (no API key required)
server:
replicas: 1
image:
repository: ghcr.io/shankar0123/certctl
pullPolicy: IfNotPresent # Use latest tag
port: 8443
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
auth:
type: none # Demo mode - no authentication
logging:
level: debug
format: json
service:
type: LoadBalancer # Easy external access for dev
issuer:
local:
enabled: true
rateLimiting:
rps: 100
burst: 200
postgresql:
enabled: true
image:
repository: postgres
tag: "16-alpine"
pullPolicy: IfNotPresent
auth:
database: certctl
username: certctl
password: "dev-password-change-me"
storage:
size: 5Gi
storageClass: "" # Use default storage class
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
agent:
enabled: true
kind: Deployment
replicas: 1
image:
repository: ghcr.io/shankar0123/certctl-agent
pullPolicy: IfNotPresent
resources:
requests:
cpu: 25m
memory: 32Mi
limits:
cpu: 100m
memory: 128Mi
ingress:
enabled: false
serviceAccount:
create: true
rbac:
create: true
monitoring:
enabled: false
customLabels:
environment: development
@@ -0,0 +1,50 @@
# Certctl with External PostgreSQL Database
# Use this when PostgreSQL is managed externally:
# - AWS RDS
# - Cloud SQL (Google Cloud)
# - Azure Database for PostgreSQL
# - Self-managed PostgreSQL server
server:
replicas: 2
auth:
type: api-key
apiKey: "CHANGE_ME"
issuer:
local:
enabled: true
# Pass external database URL via environment variable
env:
CERTCTL_DATABASE_URL: "postgres://certctl:CHANGE_ME@postgres.example.com:5432/certctl?sslmode=require"
# Disable internal PostgreSQL
postgresql:
enabled: false
agent:
enabled: true
kind: DaemonSet
ingress:
enabled: true
className: nginx
hosts:
- host: certctl.example.com
paths:
- path: /
pathType: Prefix
# For AWS RDS with IAM authentication:
# env:
# CERTCTL_DATABASE_URL: "postgres://certctl:CHANGE_ME@mydb.123456789.us-east-1.rds.amazonaws.com:5432/certctl?sslmode=require"
# For Google Cloud SQL:
# env:
# CERTCTL_DATABASE_URL: "postgres://certctl:CHANGE_ME@/certctl?host=/cloudsql/PROJECT:REGION:INSTANCE&sslmode=require"
# For Azure Database:
# env:
# CERTCTL_DATABASE_URL: "postgres://certctl@servername:CHANGE_ME@servername.postgres.database.azure.com:5432/certctl?sslmode=require"
+159
View File
@@ -0,0 +1,159 @@
# Certctl Production HA Configuration
# High availability deployment with:
# - 3 server replicas with pod anti-affinity
# - Large PostgreSQL storage
# - Resource limits for production
# - Prometheus monitoring
# - Network policies enforcement
namespace: certctl
server:
replicas: 3
image:
repository: ghcr.io/shankar0123/certctl
tag: "2.1.0"
pullPolicy: IfNotPresent
port: 8443
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 1000m
memory: 512Mi
auth:
type: api-key
apiKey: "CHANGE_ME_IN_PRODUCTION" # Use --set or sealed-secrets
logging:
level: info
format: json
service:
type: ClusterIP
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8443"
prometheus.io/path: "/api/v1/metrics/prometheus"
issuer:
local:
enabled: true
acme:
enabled: true
directoryURL: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com
challengeType: dns-01
rateLimiting:
rps: 500
burst: 1000
postgresql:
enabled: true
image:
repository: postgres
tag: "16-alpine"
pullPolicy: IfNotPresent
auth:
database: certctl
username: certctl
password: "CHANGE_ME_IN_PRODUCTION" # Use --set or sealed-secrets
storage:
size: 100Gi
storageClass: "fast-ssd" # Use your high-performance storage class
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 2000m
memory: 2Gi
agent:
enabled: true
kind: DaemonSet
image:
repository: ghcr.io/shankar0123/certctl-agent
tag: "2.1.0"
pullPolicy: IfNotPresent
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
discoveryDirs: "/etc/ssl/certs,/etc/pki/tls,/etc/ssl"
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
hosts:
- host: certctl.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: certctl-tls
hosts:
- certctl.example.com
serviceAccount:
create: true
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT:role/certctl-role # For IRSA on AWS
rbac:
create: true
podDisruptionBudget:
enabled: true
minAvailable: 2
monitoring:
enabled: true
serviceMonitor:
enabled: true
interval: 30s
scrapeTimeout: 10s
# Pod anti-affinity for HA
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- certctl
- key: app.kubernetes.io/component
operator: In
values:
- server
topologyKey: kubernetes.io/hostname
customLabels:
environment: production
team: platform
cost-center: ops
customAnnotations:
slack-alerts: "#ops"
backup-policy: daily