Repository navigation
Releases: bpmforge/kryptkeeper
Release list
v0.9.4
Kryptkeeper v0.9.4
This release packages the agent for Windows and macOS. It also fixes the
agent and ACME problems that live validation of v0.9.3 found. The Windows
agent and the macOS install (Homebrew formula and LaunchDaemon) were
validated live on the release candidate assets before release.
Packaging
- Windows agent zip and MSI. Each release now includes
kryptkeeper-agent_<version>_windows_amd64.zipand a.msi. The MSI
installs per machine and registers theKryptkeeperAgentservice. Both
files are listed in the signedchecksums.txt. The MSI is not
Authenticode-signed, so SmartScreen warns. Runbook:
docs/ops/WINDOWS_AGENT.md. - Homebrew formula. Each release includes
kryptkeeper.rb, with its own
keyless cosign signature (.sig/.pem). The formula is generated from
the signed checksums. It supports Apple Silicon only. There is no tap
repository;packaging/README.mdgives the local-tap install steps that
Homebrew 7 requires. - macOS agent as a LaunchDaemon. See
packaging/macos/com.kryptkeeper.agent.plistand the runbook
docs/ops/MACOS_AGENT.md.
Agent fixes
- Renewals run one at a time. The agent used to re-dispatch a pending
renewal on every poll, so a second ACME order competed with the first for
port 80. A failing renewal now backs off from 1 minute to 1 hour, with
jitter, and the agent reports the error to the hub. - Port 80 is checked before ordering. The agent checks IPv4 and IPv6
separately, so a listener on0.0.0.0:80is no longer missed on Windows.
If the port is in use or not permitted, the agent says so instead of
failing later with an unclear ACME error. The challenge server listens on
both IPv4 and IPv6. - The Windows agent refuses install tasks up front. It no longer tries
sudo; it reports the error to the hub. Use agentless WinRM/IIS on Windows
in v0.9.
ACME fixes
- dns-01 works where outbound port 53 is blocked. The propagation check
is now configurable per connector withdns_propagation:auto
(default),authoritative,resolver,dohornone.autokeeps the
authoritative check and falls back to DNS-over-HTTPS, then to the
resolver, when no nameserver answers. Seedocs/ops/ACME_UPSTREAM.md. POST /api/v1/renewal/executealways answers. On a slow dns-01
renewal it could return an empty reply. Its deadline now covers the
longest propagation wait, and the response is always JSON.- Failed challenges say why. The hub's ACME server now includes the
reason in a failed http-01 or dns-01 challenge'serror, for example the
URL and status. Before, clients showedauthorization error for X:with
nothing after the colon.
Upgrade notes
There are no database migrations and no breaking API changes. ACME
connector config_json gains the optional dns_* fields described above.
Existing ACME connectors use dns_propagation: auto. On networks where
port 53 works, auto behaves as before.
Install and upgrade
Replace the binary or image with 0.9.4 and restart. Docker Compose: set the
image tag to 0.9.4 (or pull 0.9), then run docker compose up -d.
Agents: install the new agent binary, zip or MSI, then restart the agent.
Verify the release
checksums.txt, kryptkeeper.rb and the image manifests are signed
keyless with cosign through the release workflow's GitHub OIDC identity.
V=0.9.4
curl -fsSLO https://github.com/bpmforge/kryptkeeper/releases/download/v$V/checksums.txt
curl -fsSLO https://github.com/bpmforge/kryptkeeper/releases/download/v$V/checksums.txt.sig
curl -fsSLO https://github.com/bpmforge/kryptkeeper/releases/download/v$V/checksums.txt.pem
cosign verify-blob \
--certificate-identity "https://github.com/bpmforge/kryptkeeper/.github/workflows/release.yml@refs/tags/v$V" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--signature checksums.txt.sig --certificate checksums.txt.pem checksums.txt
sha256sum --ignore-missing -c checksums.txt # after downloading an archive
cosign verify \
--certificate-identity "https://github.com/bpmforge/kryptkeeper/.github/workflows/release.yml@refs/tags/v$V" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/bpmforge/kryptkeeper:$VFull list of changes: CHANGELOG.
v0.9.4-rc.2
Kryptkeeper v0.9.4-rc.2
Release candidate for v0.9.4. It exists to prove the release pipeline's new
assets before the final tag: the Windows agent zip and MSI, and the signed
Homebrew formula asset. See CHANGELOG.md "[Unreleased]" for the changes.
Not for production use; v0.9.4 follows once these assets are validated.
v0.9.3
Kryptkeeper v0.9.3
Network discovery now does what the product promises: it finds every TLS
endpoint on the networks you scan, puts the certificates it finds into the
inventory, and tells you whose certificate each one is and what is wrong with
it. This release also includes the fixes prepared as v0.9.2, which was not
published separately.
Validated on a 17-host lab with targeted, full-range (/24) and scheduled
scans, on the default configuration, before release.
Discovery
- Found certificates reach the inventory. Certificates discovered on the
network appear in the certificate list without a restart, marked unmanaged
(or managed, if this hub issued them). Before, they were counted on the
dashboard but never listed. - Whose certificate is it. Every certificate a server presents is checked
against this hub's CAs and the system root store and labelledmanaged,
public,private_other(another private CA),self_signedorunknown.
The dashboard shows a "Seen on the network by issuer" breakdown, and the
Certificates and Discovery pages filter by issuer. - Chain problems.
UNTRUSTED_ROOTwhen a chain ends in a root no one
trusts;INCOMPLETE_CHAINonly when a server provably leaves out an
intermediate (for example, a hub certificate served without its hub
intermediate). Leaving out the root is normal and is not flagged. A missing
intermediate from someone else's private CA cannot be proven without
fetching it, so it is reported as unknown rather than guessed. - Per-endpoint results. Hostname mismatch and chain completeness are
stored for each certificate on each endpoint, so a certificate served under
two names keeps the right result for each, whatever order the scans run in. - Self-signed certificates made the usual way (
openssl req -x509, which
marks them as CAs) are now flagged when a server presents them. - Range scans no longer flag every certificate as a hostname mismatch
because the target was an IP address. - Servers that answer but are not usable TLS are recorded instead of
disappearing:NOT_TLS,TLS_HANDSHAKE_FAILEDwith the reason, and
POSTGRES_TLS_DISABLEDfor a PostgreSQL port that declines TLS. A scan
where some hosts failed endscompleted_with_errorsand says which. - Old protocols are detected. Servers that only offer TLS 1.0, TLS 1.1 or
SSLv3 getDEPRECATED_TLS_VERSION; their certificate is captured where
possible. - One rule for "risky". An endpoint is risky when its certificate scores
70 or more, or is expired, self-signed, weak (key or signature) or chains to
an untrusted root, or when the endpoint has a hostname mismatch, is not
TLS, fails the TLS handshake, has PostgreSQL TLS disabled, or only offers
deprecated TLS. The dashboard, the "Risky only" filter and the endpoint list
all use it, and the Findings panel counts real findings. - The Discovery list shows posture inline: issuer badge, the top findings
and a risky marker, and the protocol that was actually observed.
Other fixes (from v0.9.2)
- The audit log shows what each record is about.
GET /api/v1/auditadds
resource_name(certificate common name, CA, template, approval, rule or
scan target), resolved only for resources the caller may read. Stored
records are unchanged. The CSV export gains a trailingresource_name
column. - One rule for counting certificates. A certificate is active unless it
is revoked or superseded by a renewal. Every page that shows a certificate
total uses this rule; revoked certificates have their own dashboard figure. - Issue and renew responses include
key_algorithm_display(for example
ML-DSA-65). - UI: the certificate table scrolls instead of clipping; the PQC
readiness card shows the parts that make up its percentage; expired
certificates are no longer described as "expiring".
Behaviour changes to check before upgrading
- Discovery defaults to the
dbprovider (persisted, full-featured) when
APP_DISCOVERY_PROVIDERis unset, and in the shippedenv.example,
docker-compose.ymland Helm values.scannerremains available as a
minimal in-memory mode. Docker Compose users who kept the old default now
get persisted discovery. - More endpoints are reported as risky: hostname mismatches, failed TLS
handshakes and TLS-disabled PostgreSQL ports now count. Plain SMTP/IMAP
ports that offer no STARTTLS are reported too. - Certificate totals exclude revoked and superseded certificates, so
numbers may drop. The dashboard summary addsrevoked. Per-CA counts now
include certificates markedexpiredorreissue_required. chain_complete/chain_trustedare omitted on discovered
certificates in the certificate API: chain status is a per-endpoint fact
and is reported on the endpoint's mapped certificates.- Scan jobs can end
completed_with_errors;failednow means nothing
was reachable.GET /api/v1/discovery/scan-jobsreturnserror. - The audit CSV export has one extra trailing column.
Database migrations 000077–000079 are added (trust class, scan status,
per-endpoint facts). They run automatically on start; take a backup first as
usual.
Install and upgrade
Replace the binary or image with 0.9.3 and restart. Docker Compose: set the
image tag to 0.9.3 (or pull 0.9), then docker compose up -d.
Verify the release
checksums.txt and the image manifests are signed keyless with cosign
through the release workflow's GitHub OIDC identity.
V=0.9.3
curl -fsSLO https://github.com/bpmforge/kryptkeeper/releases/download/v$V/checksums.txt
curl -fsSLO https://github.com/bpmforge/kryptkeeper/releases/download/v$V/checksums.txt.sig
curl -fsSLO https://github.com/bpmforge/kryptkeeper/releases/download/v$V/checksums.txt.pem
cosign verify-blob \
--certificate-identity "https://github.com/bpmforge/kryptkeeper/.github/workflows/release.yml@refs/tags/v$V" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--signature checksums.txt.sig --certificate checksums.txt.pem checksums.txt
sha256sum --ignore-missing -c checksums.txt # after downloading an archive
cosign verify \
--certificate-identity "https://github.com/bpmforge/kryptkeeper/.github/workflows/release.yml@refs/tags/v$V" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/bpmforge/kryptkeeper:$VFull list of changes: CHANGELOG.
v0.9.1
Kryptkeeper v0.9.1
A fix release for v0.9.0. Upgrade if you issue post-quantum (ML-DSA)
certificates or use the PQC reports: in v0.9.0, ML-DSA certificates were
signed but could not be stored on SQLite or PostgreSQL.
Fixed
- ML-DSA certificates are stored. v0.9.0 signed the certificate and then
answered503 certificate was signed but could not be recorded, because
Go reports an ML-DSA key asunknown, which the database rejects. Issue,
renew, import, ACME and discovery now recordML-DSA. - Standard OpenSSL ML-DSA requests work. OpenSSL 3.5+ writes ML-DSA CSRs
under the usualCERTIFICATE REQUESTlabel. The hub now picks the parser
from the key's algorithm OID, so a plainopenssl reqCSR issues; the
request's self-signature is still verified. See the README section
"Issuing post-quantum (ML-DSA) certificates". - ML-DSA CAs can be created through the API and the New CA form
(ML-DSA-44,ML-DSA-65,ML-DSA-87; software key backend only). - PQC numbers agree across pages. Crypto Posture, the PQC countdown and
the PQC migration roadmap now share one definition. - Clearer errors on issue and renew. An invalid or unsupported CSR returns
400, and an inactive or keyless issuing CA returns409, instead of503. - Templates show how many certificates they issued, and
GET /api/v1/certificates?template_id=filters again. KRYPTKEEPER_ORG_NAMEsets the organization of a newly bootstrapped
root CA.- A missing
./vardirectory is created at startup instead of causing
503on file-backed templates, CAs and certificates. - UI: Approvals, Revocation Activity, certificate detail and SPIFFE show
CA and template names instead of IDs; certificate detail shows the ML-DSA
parameter set (for exampleML-DSA-65); internal ticket references are
gone from on-screen text. The Audit page has a "Hide read-only events"
toggle (off by default). Denied or failed reads are never hidden, and the
CSV export always contains every event.
Behaviour changes to check before upgrading
quantum_safe_percent(Crypto Posture,GET /api/v1/reports/crypto-posture) now
counts only certificates whose whole chain is post-quantum. An ML-DSA leaf
under a classical CA is reported separately asquantum_safe_leaf_only.
Dashboards that readquantum_safe_percentmay show a lower number. The
PQC migration roadmap response addsquantum_safe_leaf_only,
not_applicable,pqc_score_percentandquantum_safe_percent.- Deleting a template that has issued certificates now returns
409. This
guard was always intended but never fired. - Clients that retried on
503fromPOST /certificatesor.../renew
should treat400and409as final.
No database migration is added; v0.9.0 schema 76 is unchanged.
Install and upgrade
Replace the binary or image with 0.9.1 and restart. Docker Compose:
set the image tag to 0.9.1 (or pull 0.9), then docker compose up -d.
Verify the release
checksums.txt and the image manifests are signed keyless with cosign
through the release workflow's GitHub OIDC identity.
V=0.9.1
curl -fsSLO https://github.com/bpmforge/kryptkeeper/releases/download/v$V/checksums.txt
curl -fsSLO https://github.com/bpmforge/kryptkeeper/releases/download/v$V/checksums.txt.sig
curl -fsSLO https://github.com/bpmforge/kryptkeeper/releases/download/v$V/checksums.txt.pem
cosign verify-blob \
--certificate-identity "https://github.com/bpmforge/kryptkeeper/.github/workflows/release.yml@refs/tags/v$V" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--signature checksums.txt.sig --certificate checksums.txt.pem checksums.txt
sha256sum --ignore-missing -c checksums.txt # after downloading an archive
cosign verify \
--certificate-identity "https://github.com/bpmforge/kryptkeeper/.github/workflows/release.yml@refs/tags/v$V" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/bpmforge/kryptkeeper:$VFull list of changes: CHANGELOG.
v0.9.0
Kryptkeeper v0.9.0
Kryptkeeper is a self-hosted machine-identity and certificate-lifecycle
platform: a Go hub that runs your own CAs, issues certificates over ACME, EST,
SCEP, CMP and Microsoft autoenrollment (WSTEP/XCEP), publishes CRL and OCSP,
issues SSH certificates, discovers and inventories certificates across a
fleet, and renews and installs them without manual steps. A React dashboard
sits on top of the API. It runs from Docker Compose, Helm, Terraform or a
plain binary on SQLite or Postgres.
This is the first public release. Earlier versions (v1.0 to v2.2) were
internal milestones and were never published. Treat v0.9.0 as a release
candidate for 1.0: the core is validated against real clients (below), several
areas are labelled beta or experimental, and the version number says so.
Highlights
- Issuance and CAs: root and intermediate CAs, templates with name,
key-strength and SSH policy, CSR-in issuance, renew, revoke, import, fullchain
download, PQC (ML-DSA, hybrid) certificates with software keys. - Protocols: ACME (http-01, dns-01 for wildcards), EST, SCEP with one-time
challenges, CMP, WSTEP/XCEP for Windows GPO autoenrollment, CRL and OCSP. - Unattended renewal: the scheduler renews and installs on nginx, Apache or
HAProxy over SSH, on hosts running the agent, and on F5, Citrix, Kubernetes
and IIS targets. It verifies the served certificate, rolls back on failure,
retries with backoff, and tells the owner when it gives up. - Delivery plugins: sandboxed WebAssembly plugins with signed manifests and
trust tiers; F5 and Citrix ship as first-party plugins. - Discovery and inventory: network TLS scan (including ML-KEM detection),
Kubernetes and service-mesh discovery, WinRM agentless discovery (SSH agentless certificate discovery is not wired in v0.9),
read-only Cloud Keys inventory page. - Governance: append-only audit log with optional SIEM forwarding, approval
queue with proposer and approver separated, key dual control on by default,
custom roles, SSO (OIDC, SAML), SCIM, MCP server whose writes are
proposal-only. - Postgres, fully tested: every database-backed store has a round-trip test
on PostgreSQL (users and API keys, notifications, CA lifecycle and more), and
any purely additive migration is tolerated automatically on upgrade. - Key ceremonies: root-CA ceremonies work on SQLite and PostgreSQL; a root
stays pending until approved. A hub whose active CA has no key now refuses to
start (KRYPTKEEPER_ALLOW_MISSING_CA_KEYS=truefor recovery). - Notification rules: rules persist and fire under the default
delivery
provider, and are validated. - Complete audit trail: approval holds, dual-control proposals, skipped
alerts and workload attestations are all stored (migration000076), and
the Audit page andGET /api/v1/audit?outcome=filter by every outcome.
Notification providers that only record (file,db) skip alerts and audit
them asskippedinstead of reporting them sent. - Template validity:
validity_period(P30D,P1Y,72h,90d) is
honoured on issue and renew. - Security: a deep audit run on 2026-10-09 and the fixes for it (below).
What's validated
Results from 2026-10-11 on v0.9.0-rc.7 (main at 2f9e766) against real clients (full table
in docs/release/LIVE_RESULTS_v0.9.md).
Per-feature status for all 77 inventoried features is in
docs/release/FEATURE_VALIDATION_v0.9.md.
| Suite | Result | Real peer |
|---|---|---|
Smoke (tests/smoke) |
331/331 | hub binary, curl, local OIDC JWKS stub |
ACME (tests/live/acme) |
28/28 | certbot, lego |
Protocols (tests/live/protocols) |
all steps passed | openssl EST, SCEP, CMP, OCSP, CRL |
TLS (tests/live/tls) |
all steps passed | globalsign/est over the hub's own TLS |
SSH CA (tests/live/sshca) |
all steps passed | OpenSSH sshd and client |
Plugins (tests/live/plugins) |
all steps passed (27 checks) | example plugin, mock appliance, cosign |
Autopilot (tests/live/autopilot) |
all pass (29 checks) | nginx hosts and the agent, unattended |
Agent (tests/live/agent) |
18 passed, 0 failed | Linux container agent |
SSH install (tests/live/ssh-install) |
all pass (nginx, apache, rollback) | OpenSSH, nginx, apache |
go test ./..., the same with a PostgreSQL 16 test database (-p 1), and make test-race each pass
on 50 packages. The upgrade test was re-run on rc.5 (nine scenarios on SQLite and PostgreSQL) and
spot-checked on rc.7 (SQLite rc.1 and rc.2 and PostgreSQL rc.4 to rc.7, schema 76, including audit rows with
the new outcomes being stored); see
UPGRADE_TEST_v0.9.md.
Older dated runs not repeated on 2026-10-11 (Kubernetes, service mesh, notifications,
PKCS#11 on SoftHSM2, Docker Compose, WinRM/IIS, WSTEP/XCEP on a Windows lab
with Kerberos) are recorded in docs/UAT_PLAN.md.
Beta, experimental, and not in v0.9
- Beta (works; unit and integration tested only): reports, compliance
evidence, CBOM and PQC readiness; rollout groups and PQC migration waves;
SAML, OIDC and SCIM (tested against in-process IdPs, not Keycloak, Okta or
Entra); CT monitoring; cloud inventory; ADCS sync; Terraform provider;
air-gap bundle; MCP server; Helm chart (never install-tested on a cluster);
the UI has no browser end-to-end tests. - Experimental: multi-tenancy (see Security); HashiCorp Vault and GCP Cloud
KMS key stores (no tests). - Not verified on real hardware or accounts: the F5 and Citrix plugins
(mock devices only), cloud KMS (emulators only), SCEP from network devices,
the ACME upstream connector against Let's Encrypt staging (dns-01 via
Cloudflare; the live run is still pending). - Not in v0.9: finding exceptions and risk acceptance (v0.10); issuer
connectors other thaninternaland ACME (DigiCert, EST, SCEP and WSTEP
upstream connectors).
Upgrading from internal builds
v0.9.0 turns on several secure defaults. Before starting a v0.9.0 hub, read
"Upgrade notes (breaking or behaviour changes)" in the
CHANGELOG. The ones that stop a hub or
lock out an operator most often:
- Key export, backup and escrow need a second human to approve
(KRYPTKEEPER_KEY_DUAL_CONTROL=falseopts out). AUTH_MODE=jwtandoidcbearer tokens are read-only until
APP_AUTH_ROLE_CLAIMandAPP_AUTH_ROLE_MAP_JSONmap a role.- Secrets under 32 bytes, and OIDC mode without issuer and audience, are
startup errors. - Docker Compose needs
KRYPTKEEPER_KEYSTORE_PASSPHRASE, different from the
bootstrap token. - SSH certificates need a template with an
ssh_policy. - Behind a reverse proxy, set
APP_TRUSTED_PROXY_CIDRS. - API keys minted through
POST /api/v1/api-keyson earlier builds were lost at
every restart; re-mint them once (CHANGELOG upgrade notes 56 and 57). - An active CA with no key in the keystore stops the hub (note 58); existing
notification rules start alerting (note 59); templates withP1Ynow issue
1-year certificates (note 60).
Security
A deep audit of the whole repository (SAST, secrets in code and history,
dependencies, OWASP Web Top 10, OWASP LLM Top 10 for the MCP server, cloud,
IaC and CI, STRIDE threat model, 19 attack chains) ran on 2026-10-09 and
found 3 CRITICAL and 9 HIGH issues. A challenger review re-checked all 13
(none was wrong; three had their severity lowered). No live
secrets were found in code or git history, and no dependency is on the CISA KEV
list.
Everything CRITICAL and HIGH is fixed except tenant isolation. Fix groups FG-1
to FG-5 and FG-7 to FG-14 are merged (ACME issuance, key egress and separation
of duties, RBAC escalation, SSH CA policy, token hardening, login and session,
egress policy, issuance policy, plugins and MCP, cloud KMS, IaC and CI,
dependencies, documentation). Fixes ship with regression tests, and the live
smoke, ACME and SSH CA suites were re-run (results above).
Known limits you should plan around:
- Multi-tenancy is experimental (FG-6, due in v0.10). Tenant isolation is
not a security boundary: a tenant admin can act across tenants. The hub
refusesMULTI_TENANCYunlessKRYPTKEEPER_MULTI_TENANCY_EXPERIMENTAL=accept-risk
is set. Do not use it to separate mutually untrusting parties. See
docs/design/TENANCY_POSTURE.md. - Residual risks per OWASP category, such as in-memory refresh tokens and
replay caches (a restart forgets them), a denylist rather than a sandbox for
pluginremote_exec, and CMP using one shared secret per server, are listed
indocs/security/OWASP_TRACKER.mdand
docs/security/final-report.md.
Reporting a vulnerability: do not open a public issue. Use GitHub's
private "Report a vulnerability" advisory on this repository, or email
security@kryptkeeper.io. Targets: acknowledgement in 48 hours, initial
assessment in 5 business days, coordinated disclosure within 90 days. Full
policy: SECURITY.md.
Install
git clone https://github.com/bpmforge/kryptkeeper.git
cd kryptkeeper
cp env.example .env # set APP_BOOTSTRAP_TOKEN and KRYPTKEEPER_KEYSTORE_PASSPHRASE
docker compose up -dBoth values must differ; generate each with openssl rand -hex 32. The README
Quick Start conti...
v0.9.0-rc.9
Kryptkeeper v0.9.0-rc.9
Release-candidate verification run of the public release pipeline (build,
keyless signing, image push). The code is identical to v0.9.0; see
RELEASE_NOTES_v0.9.0.md for the full notes.