Deterministic TLS bundle swap CLI: ingest any cert format, deploy to k8s/ssh/local/proxmox with validation, evidence, and ArgoCD-safe swaps.
Project description
certswap
Deterministic TLS bundle swap CLI.
Take a TLS bundle in any common format (PFX, PEM bundle, separate files, PKCS#7, zip/tar archive), validate it, and deploy it to a target — a Kubernetes secret, an SSH-reachable VM, a local directory, or a Proxmox host — with a structured evidence trail per swap.
Built for the operational reality no other tool covers: a CA or customer hands you a bundle out-of-band and it has to land safely on GitOps-managed or mixed infrastructure. ACME clients automate issuance-driven renewal; cert-manager owns the in-cluster path; certswap handles everything that arrives by hand — with an ArgoCD-safe swap that existing tooling only documents as a manual procedure.
Not related to the archived pivotal-cf/certswap (a Go tool for
swapping system trust stores).
Install
Requires Python 3.12+ and uv.
uv tool install certswap # from PyPI (first release pending)
uv tool install git+https://github.com/MWest2020/certswap # latest main
For local development:
git clone git@github.com:MWest2020/certswap.git
cd certswap
uv sync --dev
uv run ruff check . && uv run mypy src && uv run pytest
Commands
certswap inspect <bundle> # show bundle contents (key optional)
certswap plan local <bundle> --dest <dir>
certswap apply local <bundle> --dest <dir>
certswap verify local --dest <dir>
certswap plan ssh <bundle> --host <h> --cert-dest /etc/nginx/tls/x.pem --key-dest /etc/nginx/tls/x.key
certswap apply ssh <bundle> --host <h> --cert-dest ... --key-dest ... --reload "nginx -s reload"
certswap plan k8s <bundle> --namespace ns --secret tls --context homelab --ingress app
certswap apply k8s <bundle> --namespace ns --secret tls --argocd-app my-app
certswap apply proxmox <bundle> --host pve-node
certswap upcoming --within-days 60
Drivers: local, ssh, k8s (with optional ArgoCD coordination), proxmox.
All commands accept --json for machine-readable output; apply writes an
evidence dir under ~/.certswap/evidence/; deployments are tracked in
~/.certswap/state.json for upcoming.
What it does
Concretely, this is one command per target for the parts of TLS-renewal that normally cost an afternoon and a postmortem:
- Customers / CAs deliver bundles in random formats —
inspectandingestnormalise PFX (incl. Sectigo's RC2-40-CBC legacy MAC, viaopenssl pkcs12 -legacyshell-out), PEM bundles, PKCS#7, archives, and separate-file layouts to one canonicalCertBundle.inspectalso accepts cert-only CA deliveries (no private key yet) — deployment commands keep requiring the key. - Leaf-only bundles get AIA-walked into a complete chain
(
--fetch-intermediates). - Key↔cert mismatches and SAN/host mismatches are caught at
plan, not at deployment. - On Kubernetes,
apply k8sdeletes the cert-manager Certificate that produced the secret and strips thecert-manager.io/cluster-issuerannotation, then replaces thekubernetes.io/tlssecret in place (a single PUT — no window in which the secret is absent). With--argocd-app, the Application's automated-sync policy is saved to an annotation and disabled,RespectIgnoreDifferences=trueandignoreDifferencesentries are patched in (idempotently), and after the swap the saved policy is restored withselfHealforced off — becauseselfHealignoresignoreDifferences.pruneand the rest of the policy come back exactly as they were; an app that wasn't auto-syncing stays that way. - Applications owned by an ApplicationSet or a parent app (app-of-apps)
are detected and block the plan: the owning controller would revert
certswap's patches, so the
ignoreDifferencesbelongs in git there.--argocd-force-managedoverrides with a warning. - On a VM,
apply sshuses your~/.ssh/config, scp-uploads to a random/tmp/file, atomic-mv's into place, chmod/chown's, runs your reload command, post-checks, and restores backups on failure. - On Proxmox,
apply proxmoxisapply sshwith the pveproxy defaults baked in. - Every
applywrites bothevidence.json(pydantic-modeled, for machines) andevidence.md(for the next human who'll renew this cert a year from now).
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 10 | Validation failed (plan blocked, host/key mismatch) |
| 20 | Target drift |
| 30 | Ingest failed (bad format, wrong password) |
| 40 | Remote unreachable |
| 50 | Apply failed mid-flight |
| 60 | Verify failed post-apply |
License
EUPL-1.2.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file certswap-0.2.0.tar.gz.
File metadata
- Download URL: certswap-0.2.0.tar.gz
- Upload date:
- Size: 47.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
966524c1e6acc3008ee2c87190f16a1e8315d57c305f84658391c7e9b8fd7a32
|
|
| MD5 |
171ac98dbfdbdd30e6c2e169576a5431
|
|
| BLAKE2b-256 |
b2c7bb5e0232315ff5c5b2015f1534ce4ae406966900ec567b800994d7c61cbb
|
Provenance
The following attestation bundles were made for certswap-0.2.0.tar.gz:
Publisher:
release.yml on MWest2020/certswap
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
certswap-0.2.0.tar.gz -
Subject digest:
966524c1e6acc3008ee2c87190f16a1e8315d57c305f84658391c7e9b8fd7a32 - Sigstore transparency entry: 1803139708
- Sigstore integration time:
-
Permalink:
MWest2020/certswap@4b3b0936e28dcef50d21953d38cda88b17f4aa3d -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/MWest2020
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4b3b0936e28dcef50d21953d38cda88b17f4aa3d -
Trigger Event:
push
-
Statement type:
File details
Details for the file certswap-0.2.0-py3-none-any.whl.
File metadata
- Download URL: certswap-0.2.0-py3-none-any.whl
- Upload date:
- Size: 72.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
36fe12c7f1a5ff5e6b28f3693692d31dfc5ca17824e19c02439208ff1a3a2f48
|
|
| MD5 |
a818beb84483c90d3ce26b6252ca8910
|
|
| BLAKE2b-256 |
9803acd97d8f7c7e984e8af45f87edbb486c64213b543bbfe4a9a85da2f67e09
|
Provenance
The following attestation bundles were made for certswap-0.2.0-py3-none-any.whl:
Publisher:
release.yml on MWest2020/certswap
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
certswap-0.2.0-py3-none-any.whl -
Subject digest:
36fe12c7f1a5ff5e6b28f3693692d31dfc5ca17824e19c02439208ff1a3a2f48 - Sigstore transparency entry: 1803139727
- Sigstore integration time:
-
Permalink:
MWest2020/certswap@4b3b0936e28dcef50d21953d38cda88b17f4aa3d -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/MWest2020
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4b3b0936e28dcef50d21953d38cda88b17f4aa3d -
Trigger Event:
push
-
Statement type: