mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-09 21:38:52 +00:00
Complete M1, M1.1, M2: end-to-end lifecycle, agent deployment, ACME v2
- Wire issuer connector end-to-end with IssuerConnectorAdapter (dependency inversion)
- Renewal/issuance job processor: RSA key + CSR generation, Local CA signing, cert version storage
- Agent work API (GET /agents/{id}/work) and job status API (POST /agents/{id}/jobs/{job_id}/status)
- Agent-side deployment: WorkItem enrichment with target type/config, NGINX/F5/IIS connector invocation
- Full ACME v2 implementation: HTTP-01 challenge solving, account registration, order lifecycle
- Update all docs (README, architecture, connectors, demo-advanced, quickstart) for M1-M2
- Fix go vet warning in deployment.go (non-constant format string)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+62
-12
@@ -8,7 +8,7 @@ New to certificates? Read the [Concepts Guide](concepts.md) first.
|
||||
|
||||
### Design Principles
|
||||
|
||||
1. **Zero Private Key Exposure** — Private keys are generated and managed only on agents, never sent to the control plane
|
||||
1. **Private Key Isolation (V2+ goal)** — In V1, the Local CA generates server-side keys for simplicity. V2+ moves key generation to agents so private keys never touch the control plane
|
||||
2. **Decoupled Operations** — Agents operate autonomously; the control plane coordinates but doesn't block agent function
|
||||
3. **Audit-First** — Complete traceability of all issuance, deployment, and rotation events
|
||||
4. **Connector Architecture** — Pluggable issuers, targets, and notifiers for extensibility
|
||||
@@ -73,9 +73,9 @@ The server exposes a REST API under `/api/v1/` and optionally serves the web das
|
||||
|
||||
### Agents
|
||||
|
||||
Lightweight Go processes that run on or near your infrastructure. An agent generates private keys locally, creates CSRs, receives signed certificates from the control plane, deploys them to target systems, and reports status back. Agents communicate with the control plane via HTTP and authenticate with API keys.
|
||||
Lightweight Go processes that run on or near your infrastructure. Agents poll the control plane for pending deployment jobs, fetch signed certificates, deploy them to target systems, and report job status back. In V2+, agents will also generate private keys locally and create CSRs. Agents communicate with the control plane via HTTP and authenticate with API keys.
|
||||
|
||||
The agent runs two background loops: a heartbeat (every 60 seconds) to signal it's alive, and a work poll (every 30 seconds) to check for pending jobs.
|
||||
The agent runs two background loops: a heartbeat (every 60 seconds) to signal it's alive, and a work poll (every 30 seconds) to check for pending deployment jobs via `GET /api/v1/agents/{id}/work`. When a job is found, the agent fetches the certificate, executes the deployment, and reports status via `POST /api/v1/agents/{id}/jobs/{job_id}/status`.
|
||||
|
||||
### Web Dashboard
|
||||
|
||||
@@ -223,7 +223,38 @@ sequenceDiagram
|
||||
API-->>U: 201 Created + JSON body
|
||||
```
|
||||
|
||||
### 2. Agent Requests Certificate (CSR → Issuance)
|
||||
### 2. Certificate Issuance
|
||||
|
||||
#### V1: Server-Side Key Generation (Local CA)
|
||||
|
||||
In V1, the control plane generates keys and CSRs server-side for the Local CA. This simplifies the initial implementation — the full agent-side key generation flow is planned for V2+.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as User / Scheduler
|
||||
participant API as Control Plane API
|
||||
participant SVC as RenewalService
|
||||
participant ISS as IssuerConnector
|
||||
participant DB as PostgreSQL
|
||||
|
||||
U->>API: POST /api/v1/certificates/{id}/renew
|
||||
API->>SVC: ProcessRenewalJob(job)
|
||||
|
||||
SVC->>SVC: Generate RSA-2048 key pair (server-side)
|
||||
SVC->>SVC: Create CSR with CN + SANs
|
||||
SVC->>ISS: IssueCertificate(commonName, sans, csrPEM)
|
||||
ISS-->>SVC: IssuanceResult{cert_pem, chain_pem, serial, not_after}
|
||||
|
||||
SVC->>SVC: Compute SHA-256 fingerprint
|
||||
SVC->>DB: INSERT INTO certificate_versions (PEM chain + CSR)
|
||||
SVC->>DB: UPDATE managed_certificates SET status='Active', expires_at
|
||||
SVC->>DB: INSERT INTO audit_events
|
||||
SVC->>DB: CREATE deployment jobs for all mapped targets
|
||||
|
||||
Note over SVC: Deployment jobs picked up by agents<br/>via GET /api/v1/agents/{id}/work
|
||||
```
|
||||
|
||||
#### V2+ (Planned): Agent-Side Key Generation
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
@@ -232,22 +263,19 @@ sequenceDiagram
|
||||
participant ISS as Issuer Connector
|
||||
participant DB as PostgreSQL
|
||||
|
||||
A->>A: Generate RSA-2048 key pair
|
||||
A->>A: Generate RSA-2048 key pair locally
|
||||
A->>A: Create CSR (CN + SANs, public key only)
|
||||
A->>API: POST /api/v1/agents/{id}/csr<br/>{csr_pem: "-----BEGIN..."}
|
||||
A->>API: POST /api/v1/agents/{id}/csr<br/>{csr_pem, certificate_id}
|
||||
|
||||
API->>API: Validate CSR format
|
||||
API->>ISS: IssueCertificate(IssuanceRequest{CSR})
|
||||
ISS-->>API: IssuanceResult{cert_pem, chain_pem, serial, not_after}
|
||||
|
||||
API->>DB: INSERT INTO certificate_versions
|
||||
API->>DB: UPDATE managed_certificates SET status='Active'
|
||||
API->>DB: INSERT INTO audit_events
|
||||
|
||||
API-->>A: {certificate_pem, chain_pem}<br/>(NO private key in response)
|
||||
|
||||
A->>A: Store cert.pem + chain.pem locally
|
||||
Note over A: key.pem stays on agent<br/>Never transmitted anywhere
|
||||
A->>A: Store cert + chain locally (key never leaves agent)
|
||||
A->>A: Deploy to target system
|
||||
```
|
||||
|
||||
@@ -320,6 +348,26 @@ flowchart TB
|
||||
end
|
||||
```
|
||||
|
||||
### IssuerConnectorAdapter (Dependency Inversion)
|
||||
|
||||
The service layer defines its own `IssuerConnector` interface (`internal/service/renewal.go`) while the connector layer has its own `issuer.Connector` interface (`internal/connector/issuer/interface.go`). The `IssuerConnectorAdapter` (`internal/service/issuer_adapter.go`) bridges the two, translating between their request/response types. This maintains clean dependency inversion — the service package never imports the connector package directly.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
SVC["Service Layer<br/>service.IssuerConnector"] --> ADAPT["IssuerConnectorAdapter<br/>(bridges interfaces)"]
|
||||
ADAPT --> CONN["Connector Layer<br/>issuer.Connector"]
|
||||
CONN --> LC["Local CA"]
|
||||
CONN --> ACME["ACME v2"]
|
||||
```
|
||||
|
||||
Registration happens in `cmd/server/main.go`:
|
||||
```go
|
||||
localCA := local.New(nil, logger)
|
||||
issuerRegistry := map[string]service.IssuerConnector{
|
||||
"iss-local": service.NewIssuerConnectorAdapter(localCA),
|
||||
}
|
||||
```
|
||||
|
||||
### Issuer Connector
|
||||
|
||||
Handles certificate issuance from CAs.
|
||||
@@ -394,14 +442,16 @@ flowchart LR
|
||||
style ROT fill:#efe,stroke:#3c3
|
||||
```
|
||||
|
||||
Private keys follow a strict lifecycle:
|
||||
**V1 (Current):** The Local CA issuer generates RSA-2048 keys and CSRs server-side within `RenewalService.ProcessRenewalJob`. Private key material is stored alongside the CSR in the `certificate_versions` table. This is a pragmatic V1 trade-off to get the end-to-end lifecycle working.
|
||||
|
||||
**V2+ (Target Architecture):** Private keys follow a strict lifecycle on agents:
|
||||
|
||||
1. **Generated on the agent** — never sent to the control plane
|
||||
2. **Stored on the agent** — file permissions 0600, owned by the agent process user
|
||||
3. **Used by the agent** — for deployment to targets and CSR generation
|
||||
4. **Rotated by the agent** — old keys deleted after successful renewal
|
||||
|
||||
The control plane only ever handles public material: certificates, chains, and CSRs. This is a deliberate architectural decision — even if the control plane database is compromised, no private keys are exposed.
|
||||
The V2+ architecture ensures the control plane only handles public material: certificates, chains, and CSRs.
|
||||
|
||||
### Authentication
|
||||
|
||||
|
||||
Reference in New Issue
Block a user