{"id":48291844,"url":"https://github.com/thpham/serles-acme","last_synced_at":"2026-04-04T23:10:02.243Z","repository":{"id":323942771,"uuid":"1095316950","full_name":"thpham/serles-acme","owner":"thpham","description":"[FORK] Pluggable ACME: a tiny ACME-CA implementation to enhance existing CA infrastructure","archived":false,"fork":false,"pushed_at":"2025-11-12T23:23:11.000Z","size":149,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-11-13T01:15:24.523Z","etag":null,"topics":["acme","docker","docker-compose","ejbca"],"latest_commit_sha":null,"homepage":"https://serles-acme.readthedocs.io/en/latest/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thpham.png","metadata":{"files":{"readme":"README.docker.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-12T22:18:59.000Z","updated_at":"2025-11-12T23:28:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/thpham/serles-acme","commit_stats":null,"previous_names":["thpham/serles-acme"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/thpham/serles-acme","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thpham%2Fserles-acme","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thpham%2Fserles-acme/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thpham%2Fserles-acme/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thpham%2Fserles-acme/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thpham","download_url":"https://codeload.github.com/thpham/serles-acme/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thpham%2Fserles-acme/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31418289,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T20:09:54.854Z","status":"ssl_error","status_checked_at":"2026-04-04T20:09:44.350Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["acme","docker","docker-compose","ejbca"],"created_at":"2026-04-04T23:10:01.736Z","updated_at":"2026-04-04T23:10:02.232Z","avatar_url":"https://github.com/thpham.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Serles ACME Server - Docker Deployment Guide\n\nComplete guide for running Serles ACME Server with Docker and Docker Compose, including EJBCA CE PKI integration.\n\n---\n\n## Table of Contents\n\n- [Quick Start](#quick-start)\n- [Architecture Overview](#architecture-overview)\n- [Configuration](#configuration)\n- [Development Setup](#development-setup)\n- [Production Deployment](#production-deployment)\n- [Kubernetes Deployment](#kubernetes-deployment)\n- [Troubleshooting](#troubleshooting)\n- [Advanced Topics](#advanced-topics)\n\n---\n\n## Quick Start\n\n### Prerequisites\n\n- Docker 24.0+ with BuildKit support\n- Docker Compose 2.0+\n- 4GB RAM minimum (for full stack with EJBCA)\n- Multi-architecture support: amd64, arm64\n\n### 1. Clone and Start\n\n```bash\n# Clone repository\ngit clone https://github.com/thpham/serles-acme.git\ncd serles-acme\n\n# Start full stack (Serles + PostgreSQL + EJBCA)\ndocker-compose up -d\n\n# Check status\ndocker-compose ps\n```\n\n**Note for Apple Silicon (Mx chip) Users:**\n\nEJBCA CE only provides AMD64 images, but Docker Desktop will automatically use Rosetta 2 emulation. The `platform: linux/amd64` is already configured in [docker-compose.yaml](docker-compose.yaml:37). Expect slightly slower startup times (~3-4 minutes for EJBCA instead of ~2 minutes).\n\n### 2. Configure EJBCA (First Time Only - Automated)\n\n```bash\n# Wait for EJBCA to be healthy (takes ~2-3 minutes)\ndocker-compose logs -f ejbca\n# Press Ctrl+C when you see \"INFO: Server startup completed\"\n\n# Run fully automated bootstrap script\n./docker/run-ejbca-bootstrap.sh\n\n# This script will automatically:\n#   1. Create ACME Certificate Authority (ACMECA)\n#   2. Generate client certificate for Serles SOAP API access (P12 format)\n#   3. Configure administrator role and permissions\n#   4. Export certificates to /mnt/persistent inside EJBCA container\n#   5. Convert P12 certificate to PEM format using host's openssl\n#   6. Copy certificates to ./docker/certs/ directory\n#   7. Set proper file permissions (600)\n#   8. Restart Serles container to pick up the certificate\n#   9. Display verification logs\n\n# IMPORTANT: Restart EJBCA to apply configuration changes\ndocker-compose restart ejbca\n\n# Wait for EJBCA to be healthy again (~2 minutes)\ndocker-compose logs -f ejbca\n# Press Ctrl+C when you see \"INFO: Server startup completed\"\n```\n\n**Alternative: Manual Configuration**\n\nIf you prefer manual configuration or need custom profiles:\n\n```bash\n# Access EJBCA Web UI\n# URL: https://localhost:9443/ejbca/\n# Follow instructions in docker/ejbca-bootstrap.sh comments\n```\n\n### 3. Verify Serles\n\n```bash\n# Check health\ncurl http://localhost:8080/\n\n# Check ACME directory\ncurl http://localhost:8080/directory\n```\n\n### 4. Try the Demo (Optional)\n\nSee Serles in action with automatic HTTPS via Caddy:\n\n```bash\n# One command - Caddy automatically gets certificate!\ndocker-compose -f docker-compose.demo.yml up -d\n\n# Access demo site\nopen https://localhost:8043\n```\n\nFor details, see [README.demo.md](README.demo.md).\n\n---\n\n## Architecture Overview\n\n### Components\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│                     Docker Compose Stack                     │\n├─────────────────┬────────────────────┬─────────────────────┤\n│  Serles ACME    │  PostgreSQL 17     │  EJBCA CE (latest)  │\n│  Port: 8080     │  Port: 5432        │  Port: 9443         │\n│  Python 3.12    │  Database          │  PKI Backend        │\n│  Gunicorn+gevent│  Persistent Volume │  TLS Enabled        │\n└─────────────────┴────────────────────┴─────────────────────┘\n          ↓                ↓                      ↓\n    serles-network (Bridge Network)\n```\n\n### Image Details\n\n**Serles ACME:**\n\n- **Base**: `python:3.12-slim` (multi-stage build)\n- **Size**: ~468MB\n- **Platforms**: `linux/amd64`, `linux/arm64`\n- **Registry**: `ghcr.io/thpham/serles-acme`\n- **Tags**:\n  - `latest` - Latest stable release\n  - `v{version}` - Semantic version (e.g., v1.2.0)\n  - `version-{sha}` - Development builds from master\n\n**EJBCA CE:**\n\n- **Platform**: `linux/amd64` only (uses Rosetta 2 emulation on Apple Silicon)\n- **Note**: Expect 50% slower startup on ARM64 hosts (~3-4 minutes vs ~2 minutes)\n\n---\n\n## Configuration\n\n### Environment Variables\n\n| Variable                   | Description                     | Default                                             |\n| -------------------------- | ------------------------------- | --------------------------------------------------- |\n| `DATABASE_URL`             | PostgreSQL connection string    | `sqlite:////var/lib/serles/db.sqlite`               |\n| `BACKEND`                  | Backend module                  | `serles.backends.ejbca:EjbcaBackend`                |\n| `EJBCA_API_URL`            | EJBCA SOAP endpoint             | `https://localhost:9443/ejbca/ejbcaws/ejbcaws?wsdl` |\n| `EJBCA_CA_BUNDLE`          | CA certificate verification     | `none`                                              |\n| `EJBCA_CLIENT_CERT`        | Client certificate path         | `/etc/serles/client01-privpub.pem`                  |\n| `EJBCA_CA_NAME`            | CA name in EJBCA                | `ACMECA`                                            |\n| `EJBCA_END_ENTITY_PROFILE` | End entity profile              | `ACMEEndEntityProfile`                              |\n| `EJBCA_CERT_PROFILE`       | Certificate profile             | `ACMEServerProfile`                                 |\n| `SUBJECT_NAME_TEMPLATE`    | Subject DN template             | `CN={SAN[0]}`                                       |\n| `FORCE_TEMPLATE_DN`        | Override CSR DN                 | `true`                                              |\n| `ALLOW_WILDCARDS`          | Allow `*.example.com` certs     | `false`                                             |\n| `VERIFY_PTR`               | Verify reverse DNS              | `false`                                             |\n| `ALLOWED_IP_RANGES`        | CIDR ranges (newline-separated) | ` ` (all allowed)                                   |\n| `EXCLUDED_IP_RANGES`       | Excluded CIDR ranges            | ` `                                                 |\n| `GUNICORN_WORKERS`         | Number of workers               | `4`                                                 |\n| `LOG_LEVEL`                | Log level                       | `info`                                              |\n\n### Configuration Modes\n\n#### 1. Environment Variables (Recommended for Docker/K8s)\n\n```yaml\nservices:\n  serles:\n    environment:\n      DATABASE_URL: \"postgresql://user:pass@host:5432/db\"\n      EJBCA_API_URL: \"https://ejbca:8443/ejbca/ejbcaws/ejbcaws?wsdl\" # EJBCA internal port\n      ALLOW_WILDCARDS: \"true\"\n```\n\n#### 2. Configuration File Override\n\n```yaml\nservices:\n  serles:\n    volumes:\n      - ./my-config.ini:/etc/serles/config.ini:ro\n```\n\n#### 3. Hybrid Approach (Both)\n\nEnvironment variables take precedence over file values via template substitution.\n\n---\n\n## Development Setup\n\n### Full Stack with Docker Compose\n\n```bash\n# Start all services\ndocker-compose up -d\n\n# View logs\ndocker-compose logs -f serles\n\n# Stop all services\ndocker-compose down\n\n# Clean up (including volumes)\ndocker-compose down -v\n```\n\n### EJBCA Configuration\n\nAfter starting EJBCA for the first time, configure it for Serles:\n\n```bash\n# 1. Run bootstrap script\ndocker exec serles-ejbca /opt/ejbca-bootstrap.sh\n\n# 2. Access EJBCA Web UI\nopen https://localhost:9443/ejbca/\n\n# 3. Complete configuration steps (see bootstrap output)\n#    - Create CA (ACMECA)\n#    - Create Certificate Profiles\n#    - Create End Entity Profiles\n#    - Create API user and role\n#    - Issue client certificate\n\n# 4. Copy CSR content from bootstrap output\ndocker exec serles-ejbca cat /tmp/serles-certs/client01.csr\n\n# 5. Issue certificate via EJBCA Web UI\n\n# 6. Combine certificate and key\ncat client01.key client01.pem \u003e client01-privpub.pem\n\n# 7. Mount certificate for Serles\nmkdir -p docker/certs\ncp client01-privpub.pem docker/certs/\n\n# 8. Update docker-compose.yaml volume mount\n# volumes:\n#   - ./docker/certs/client01-privpub.pem:/etc/serles/client01-privpub.pem:ro\n\n# 9. Restart Serles\ndocker-compose restart serles\n```\n\n### Testing with OpenSSL Backend\n\nFor quick testing without EJBCA:\n\n```bash\n# Override backend in docker-compose.yaml\nenvironment:\n  BACKEND: \"serles.backends.openssl:OpenSSLBackend\"\n  # Remove EJBCA-specific variables\n\n# Start only Serles and PostgreSQL\ndocker-compose up -d serles postgresql\n```\n\n---\n\n## Production Deployment\n\n### Standalone Docker\n\n```bash\n# Pull image\ndocker pull ghcr.io/thpham/serles-acme:latest\n\n# Run with external PostgreSQL and EJBCA\ndocker run -d \\\n  --name serles-acme \\\n  -p 8080:8080 \\\n  -e DATABASE_URL=\"postgresql://user:pass@postgres.example.com:5432/serles\" \\\n  -e EJBCA_API_URL=\"https://ejbca.example.com/ejbca/ejbcaws/ejbcaws?wsdl\" \\\n  -e EJBCA_CA_BUNDLE=\"/etc/serles/ejbca-ca.pem\" \\\n  -e EJBCA_CLIENT_CERT=\"/etc/serles/client01-privpub.pem\" \\\n  -v /path/to/certs:/etc/serles/certs:ro \\\n  -v serles-data:/var/lib/serles \\\n  ghcr.io/thpham/serles-acme:latest\n```\n\n### TLS Termination\n\nSerles runs HTTP internally. Use a reverse proxy for TLS:\n\n#### Nginx Example\n\n```nginx\nupstream serles {\n    server localhost:8080;\n}\n\nserver {\n    listen 443 ssl http2;\n    server_name acme.example.com;\n\n    ssl_certificate /etc/nginx/certs/server.crt;\n    ssl_certificate_key /etc/nginx/certs/server.key;\n\n    location / {\n        proxy_pass http://serles;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n}\n```\n\n#### Traefik Example\n\n```yaml\nlabels:\n  - \"traefik.enable=true\"\n  - \"traefik.http.routers.serles.rule=Host(`acme.example.com`)\"\n  - \"traefik.http.routers.serles.entrypoints=websecure\"\n  - \"traefik.http.routers.serles.tls.certresolver=letsencrypt\"\n  - \"traefik.http.services.serles.loadbalancer.server.port=8080\"\n```\n\n---\n\n## Kubernetes Deployment\n\n### Example Manifests\n\n#### ConfigMap\n\n```yaml\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: serles-config\ndata:\n  config.ini: |\n    [serles]\n    database = postgresql://serles:password@postgresql:5432/serles\n    backend = serles.backends.ejbca:EjbcaBackend\n    # ... (rest of config)\n```\n\n#### Secret (Client Certificate)\n\n```yaml\napiVersion: v1\nkind: Secret\nmetadata:\n  name: serles-ejbca-cert\ntype: Opaque\ndata:\n  client01-privpub.pem: \u003cbase64-encoded-cert\u003e\n```\n\n#### Deployment\n\n```yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: serles-acme\nspec:\n  replicas: 3\n  selector:\n    matchLabels:\n      app: serles-acme\n  template:\n    metadata:\n      labels:\n        app: serles-acme\n    spec:\n      containers:\n        - name: serles\n          image: ghcr.io/thpham/serles-acme:latest\n          ports:\n            - containerPort: 8080\n              name: http\n          env:\n            - name: DATABASE_URL\n              valueFrom:\n                secretKeyRef:\n                  name: serles-db-secret\n                  key: connection-string\n            - name: EJBCA_API_URL\n              value: \"https://ejbca.example.com/ejbca/ejbcaws/ejbcaws?wsdl\"\n          volumeMounts:\n            - name: config\n              mountPath: /etc/serles/config.ini\n              subPath: config.ini\n            - name: certs\n              mountPath: /etc/serles/certs\n              readOnly: true\n          livenessProbe:\n            httpGet:\n              path: /\n              port: 8080\n            initialDelaySeconds: 30\n            periodSeconds: 10\n          readinessProbe:\n            httpGet:\n              path: /directory\n              port: 8080\n            initialDelaySeconds: 10\n            periodSeconds: 5\n      volumes:\n        - name: config\n          configMap:\n            name: serles-config\n        - name: certs\n          secret:\n            secretName: serles-ejbca-cert\n```\n\n#### Service\n\n```yaml\napiVersion: v1\nkind: Service\nmetadata:\n  name: serles-acme\nspec:\n  selector:\n    app: serles-acme\n  ports:\n    - port: 80\n      targetPort: 8080\n      name: http\n  type: ClusterIP\n```\n\n#### Ingress\n\n```yaml\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: serles-ingress\n  annotations:\n    cert-manager.io/cluster-issuer: letsencrypt-prod\nspec:\n  tls:\n    - hosts:\n        - acme.example.com\n      secretName: serles-tls\n  rules:\n    - host: acme.example.com\n      http:\n        paths:\n          - path: /\n            pathType: Prefix\n            backend:\n              service:\n                name: serles-acme\n                port:\n                  number: 80\n```\n\n---\n\n## Troubleshooting\n\n### Common Issues\n\n#### 1. EJBCA Connection Failed\n\n**Symptoms**: `zeep.exceptions.TransportError` or `SSL: CERTIFICATE_VERIFY_FAILED`\n\n**Solutions**:\n\n```bash\n# Check EJBCA is accessible\ncurl -k https://ejbca:8443/ejbca/publicweb/healthcheck/ejbcahealth\n\n# Verify client certificate\nopenssl x509 -in client01-privpub.pem -text -noout\n\n# Disable certificate verification for testing\nenvironment:\n  EJBCA_CA_BUNDLE: \"none\"\n```\n\n#### 2. Database Connection Timeout\n\n**Symptoms**: `psycopg2.OperationalError: could not connect to server`\n\n**Solutions**:\n\n```bash\n# Check PostgreSQL is ready\ndocker-compose logs postgresql\n\n# Verify connection string\ndocker-compose exec serles env | grep DATABASE_URL\n\n# Test connection manually\ndocker-compose exec serles pg_isready -h postgresql -p 5432\n```\n\n#### 3. Gunicorn Worker Timeout\n\n**Symptoms**: `[CRITICAL] WORKER TIMEOUT`\n\n**Solutions**:\n\n```bash\n# Increase timeout in docker/gunicorn_config.py\ntimeout = 180  # seconds\n\n# Reduce workers if resource-constrained\nenvironment:\n  GUNICORN_WORKERS: \"2\"\n```\n\n#### 4. Permission Denied\n\n**Symptoms**: `PermissionError: [Errno 13] Permission denied`\n\n**Solutions**:\n\n```bash\n# Check file ownership\ndocker-compose exec serles ls -la /etc/serles/\n\n# Ensure volumes are writable\ndocker-compose exec serles touch /var/lib/serles/test.txt\n```\n\n### Debug Mode\n\n```bash\n# Enable debug logging\nenvironment:\n  LOG_LEVEL: \"debug\"\n\n# View real-time logs\ndocker-compose logs -f serles\n\n# Exec into container\ndocker-compose exec serles bash\n\n# Check config\ndocker-compose exec serles cat /etc/serles/config.ini\n\n# Test connectivity\ncurl http://localhost:8080/directory\n```\n\n---\n\n## Advanced Topics\n\n### Custom Backends\n\nCreate a custom backend by extending `serles.backends.base.Backend`:\n\n```python\n# custom_backend.py\nfrom serles.backends.base import Backend\n\nclass CustomBackend(Backend):\n    def issue_certificate(self, csr, subject_alt_names):\n        # Your implementation\n        pass\n```\n\nMount in container:\n\n```yaml\nvolumes:\n  - ./custom_backend.py:/app/custom_backend.py:ro\nenvironment:\n  BACKEND: \"custom_backend:CustomBackend\"\n```\n\n### Performance Tuning\n\n```bash\n# Increase workers for high load\nGUNICORN_WORKERS: \"8\"  # 2 * CPU_cores + 1\n\n# Use gevent for I/O-bound workloads (default)\n# worker_class = \"gevent\" in gunicorn_config.py\n\n# Connection pooling for PostgreSQL\nDATABASE_URL: \"postgresql://user:pass@host:5432/db?pool_size=20\u0026max_overflow=10\"\n```\n\n### Monitoring\n\n```yaml\n# Prometheus metrics (if integrated)\n- name: metrics\n  containerPort: 9090\n\n# Health check endpoint\ncurl http://localhost:8080/\n```\n\n### Security Hardening\n\n```bash\n# 1. Use secrets management\ndocker secret create ejbca_cert client01-privpub.pem\n\n# 2. Network isolation\nnetworks:\n  frontend:\n    external: true\n  backend:\n    internal: true\n\n# 3. Read-only root filesystem\nsecurity_opt:\n  - no-new-privileges:true\nread_only: true\ntmpfs:\n  - /tmp\n```\n\n---\n\n## CI/CD Integration\n\n### GitHub Actions (Included)\n\nThe repository includes `.github/workflows/docker-build.yml`:\n\n- **Triggers**: Push to master, Git tags, Manual dispatch\n- **Builds**: Multi-arch (amd64, arm64)\n- **Registry**: GitHub Container Registry (GHCR)\n- **Tagging**:\n  - `v{version}` → `{version}` + `latest`\n  - Push to master → `version-{short_sha}`\n  - Cleanup: Keep last 4 `version-*` tags\n\n### Building Locally\n\n```bash\n# Build for current architecture\ndocker build -t serles-acme:local .\n\n# Multi-arch build\ndocker buildx create --use\ndocker buildx build --platform linux/amd64,linux/arm64 -t serles-acme:multi .\n```\n\n---\n\n## Support\n\n- **Issues**: https://github.com/thpham/serles-acme/issues\n- **Original Project**: https://github.com/dvtirol/serles-acme\n- **EJBCA Docs**: https://www.primekey.com/products/ejbca-enterprise/\n\n---\n\n## License\n\nGNU General Public License v3 (GPLv3)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthpham%2Fserles-acme","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthpham%2Fserles-acme","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthpham%2Fserles-acme/lists"}