MyOwnPKI

module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 25, 2026 License: Apache-2.0

README

MyOwnPKI

Your own PKI – self-hosted, automated, from homelab to enterprise.

Status: v0.3. A working CA with web UI, REST API, legacy import and backup – it replaces the pki.sh Bash suite – an ACME server for your own CA (behind a reverse proxy, or with its own TLS since v0.2.2) and, new in v0.3.0, the Linux agent that gets and deploys certificates by ACME, with "Renew now" in the web UI. Monitoring comes with v0.3.x. See docs/PLAN.md for the architecture, decisions and roadmap.

MyOwnPKI is a self-hosted certificate authority platform. v0.1 brought the CA, the web UI and the REST API, the import of a pki.sh PKI and backups; v0.2 adds the ACME server; v0.3.0 the Linux agent; the rest of this list follows up to v1.0:

  • Secure web UI with dashboards, upload/download and role-based access
  • ACME server for your internal CA (v0.2) – use certbot, acme.sh, lego, Traefik, Caddy or win-acme; behind a reverse proxy, or since v0.2.2 with its own TLS (docs/OPERATIONS.md chapter 10)
  • ACME client for public certificates from Let's Encrypt / ZeroSSL (http-01, dns-01)
  • Optional agent that deploys certificates to Linux, Windows and FreeBSD hosts, reloads services safely and monitors what is actually being served (v0.3.0: Linux, nginx and files, with rollback)
  • Multiple domains and tenants, policies per domain, audit log
  • Runs anywhere as a single binary or container: Docker, Podman, LXC, Linux, macOS, Windows, FreeBSD

Screenshots (the ACME pages: v0.2) – a demo instance (myownpki init --demo) with sample certificates under homelab.example.

Certificate list with filters
Certificates Certificate detail page
Certificate detail
Issue wizard Certificate authorities
Certificate authorities Dashboard in the dark theme
Dark theme Unsealing the CA keys after the start
Unseal Signing an intermediate with the offline root
Offline root signing Report of a legacy pki.sh import
Legacy import
Sign in Users and roles
Users and roles Audit log with filters
Audit log Certificate detail in the dark theme
Detail, dark theme
ACME accounts New names waiting for approval
ACME new names ACME orders with their result
ACME orders

Install

Read docs/OPERATIONS.md first – above all the warning about the unseal passphrase. Releases: https://github.com/Der-Felix/MyOwnPKI/releases.

One command installs MyOwnPKI as a hardened systemd service (Debian, Ubuntu and other systemd distributions; amd64 or arm64). Download the script with the checksum file, check it, run it – never pipe it into a shell:

v=0.3.0
curl -fsSLO "https://github.com/Der-Felix/MyOwnPKI/releases/download/v$v/install.sh"
curl -fsSLO "https://github.com/Der-Felix/MyOwnPKI/releases/download/v$v/checksums.txt"
curl -fsSLO "https://github.com/Der-Felix/MyOwnPKI/releases/download/v$v/checksums.txt.sigstore.json"
sha256sum --check --ignore-missing checksums.txt
sudo bash install.sh --tls-names "pki.homelab.lan, 192.0.2.5"

It checks the archive it downloads (SHA-256, and the Sigstore signature when cosign is installed – see "Verify a download"), creates the user, the config and the unit, enables the service without starting it and prints the next commands: init at the terminal, start, unseal in the browser. Running it again for a newer release updates – with a backup first. Everything it does and every option: docs/OPERATIONS.md §2.

From a release archive

Archives for linux/amd64, linux/arm64, darwin/arm64, windows/amd64 and freebsd/amd64 hold the single myownpki binary, the operations guide, an example configuration, the systemd unit and install.sh.

v=0.3.0
curl -fsSLO "https://github.com/Der-Felix/MyOwnPKI/releases/download/v$v/myownpki_${v}_linux_amd64.tar.gz"
curl -fsSLO "https://github.com/Der-Felix/MyOwnPKI/releases/download/v$v/checksums.txt"
curl -fsSLO "https://github.com/Der-Felix/MyOwnPKI/releases/download/v$v/checksums.txt.sigstore.json"

Then verify the download (next section), unpack it, copy deploy/config.example.yaml to /etc/myownpki/config.yaml and continue with myownpki init (docs/OPERATIONS.md §3). On a machine without internet, install.sh --archive installs from this archive.

With Docker (the alternative)

The image ghcr.io/der-felix/myownpki (linux/amd64 and linux/arm64) is distroless and runs as a non-root user; the data directory is a volume, and the image's healthcheck runs myownpki healthcheck. For a single server, use deploy/docker-compose.yml with deploy/docker/config.yaml:

docker compose run --rm -it myownpki init
docker compose up -d
Verify a download

Every release is built by .github/workflows/release.yml and signed keyless with cosign (Sigstore): the signature proves that the files come from that workflow at that tag – there is no key to steal. First the checksum file, then the archive against it:

cosign verify-blob \
  --bundle checksums.txt.sigstore.json \
  --certificate-identity "https://github.com/Der-Felix/MyOwnPKI/.github/workflows/release.yml@refs/tags/v$v" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  checksums.txt
sha256sum --check --ignore-missing checksums.txt

The image:

cosign verify ghcr.io/der-felix/myownpki:0.3.0 \
  --certificate-identity "https://github.com/Der-Felix/MyOwnPKI/.github/workflows/release.yml@refs/tags/v0.3.0" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

Each archive has an SPDX SBOM next to it (*.sbom.json, listed in checksums.txt). The builds are reproducible: the same tag built again gives byte-identical archives.

Repository layout

Path Content
docs/PLAN.md Project plan, architecture decisions (ADRs), roadmap
docs/OPERATIONS.md Operations guide: install, init, backup and restore, emergencies, the ACME server (behind a reverse proxy or with its own TLS)
docs/RELEASING.md How a release is made (owner checklist)
deploy/ Example configuration, install.sh and the systemd unit, Dockerfile and Docker Compose file
CHANGELOG.md Changes per release
cmd/ The three binaries: myownpki (server), myownpki-agent, myownpki-ctl
internal/ca/preset Loader and validation for certificate presets
internal/web templ components, layouts and pages of the admin UI
presets/ Built-in certificate presets (YAML, ported from legacy/lib/presets.sh)
api/openapi.yaml REST API specification (OpenAPI 3.1)
web/styles/ Design tokens and the global CSS classes
legacy/ The original Bash PKI suite (pki.sh) – still usable as an offline/air-gapped root CA tool

Development

Requirements: Go (the exact toolchain is pinned in go.mod and downloaded automatically), Task, golangci-lint and sqlc – versions in Taskfile.yml. templ and govulncheck are pinned in go.mod and run via go tool. Works in PowerShell and Git Bash. Rules for changes and commits: CONTRIBUTING.md.

task              # list all tasks
task check        # everything that must pass before a commit
task dev          # UI prototype at http://127.0.0.1:9443/ (no TLS, no login, loopback only)
task screenshots  # regenerate docs/screenshots – at milestones only
task vuln         # known vulnerabilities in dependencies and the Go standard library
task lint:ci      # actionlint on the GitHub workflows (needs actionlint, see Taskfile.yml)
task lint:api     # vacuum on api/openapi.yaml (needs vacuum, see Taskfile.yml)
task cross        # build for linux/amd64, linux/arm64, darwin/arm64, windows/amd64, freebsd/amd64
task release:check     # check .goreleaser.yaml (needs goreleaser, see Taskfile.yml)
task release:snapshot  # local release build into dist/ – no signing, no image, nothing published

Legacy Bash suite

The predecessor of this project lives in legacy/. It remains supported as an offline tool for root CA ceremonies. Its full history is in the original pki_script repository.

Security

Please report vulnerabilities privately – see SECURITY.md.

License

Apache License 2.0

Directories

Path Synopsis
Package api holds the OpenAPI specification of the admin REST API (openapi.yaml).
Package api holds the OpenAPI specification of the admin REST API (openapi.yaml).
Package assets embeds the brand files the admin UI serves: logo variants and favicons.
Package assets embeds the brand files the admin UI serves: logo variants and favicons.
cmd
myownpki command
Command myownpki is the MyOwnPKI server: web UI, REST API, ACME server and CA engine.
Command myownpki is the MyOwnPKI server: web UI, REST API, ACME server and CA engine.
myownpki-agent command
Command myownpki-agent keeps certificates in place on a host (docs/PHASE3.md): it enrolls with the MyOwnPKI server (ADR-18), keeps its agent certificate, and gets and deploys certificates by ACME.
Command myownpki-agent keeps certificates in place on a host (docs/PHASE3.md): it enrolls with the MyOwnPKI server (ADR-18), keeps its agent certificate, and gets and deploys certificates by ACME.
myownpki-ctl command
Command myownpki-ctl is the command-line client for the MyOwnPKI REST API, with an offline mode for root CA ceremonies.
Command myownpki-ctl is the command-line client for the MyOwnPKI REST API, with an offline mode for root CA ceremonies.
internal
acme
Package acme is the ACME server (RFC 8555) for the internal CAs (PHASE2).
Package acme is the ACME server (RFC 8555) for the internal CAs (PHASE2).
acme/jose
Package jose reads the JSON Web Signatures of ACME requests (RFC 7515, RFC 8555 §6.2) – own code for the parsing and the rules, cryptography only from the standard library (owner Q3, 2026-10-22).
Package jose reads the JSON Web Signatures of ACME requests (RFC 7515, RFC 8555 §6.2) – own code for the parsing and the rules, cryptography only from the standard library (owner Q3, 2026-10-22).
agent
Package agent is myownpki-agent on a host (docs/PHASE3.md): it enrolls with proof of control (ADR-18), keeps its 7-day agent certificate, and – from block C on – gets certificates by ACME and deploys them.
Package agent is myownpki-agent on a host (docs/PHASE3.md): it enrolls with proof of control (ADR-18), keeps its 7-day agent certificate, and – from block C on – gets certificates by ACME and deploys them.
agents
Package agents is the server side of myownpki-agent (docs/PHASE3.md): join tokens, enrollment with proof of control (ADR-18), the agents' 7-day certificates from the agent CA, their names, disable and revoke.
Package agents is the server side of myownpki-agent (docs/PHASE3.md): join tokens, enrollment with proof of control (ADR-18), the agents' 7-day certificates from the agent CA, their names, disable and revoke.
audit
Package audit writes and verifies the hash-chained audit logs (PLAN §5, security.md "Audit log").
Package audit writes and verifies the hash-chained audit logs (PLAN §5, security.md "Audit log").
auth
Package auth signs users in to the admin listener (PHASE1 step 10, security.md "Authentication"): Argon2id passwords, TOTP (RFC 6238), recovery codes, server-side sessions, CSRF protection, throttling and lockout.
Package auth signs users in to the admin listener (PHASE1 step 10, security.md "Authentication"): Argon2id passwords, TOTP (RFC 6238), recovery codes, server-side sessions, CSRF protection, throttling and lockout.
auth/passkeytest
Package passkeytest is a virtual WebAuthn authenticator for tests (PHASE1 step 17): it answers the options of navigator.credentials.create() and .get() the way a browser with a platform authenticator does – ECDSA P-256, attestation "none", user verification done.
Package passkeytest is a virtual WebAuthn authenticator for tests (PHASE1 step 17): it answers the options of navigator.credentials.create() and .get() the way a browser with a platform authenticator does – ECDSA P-256, attestation "none", user verification done.
authz
Package authz decides who may do what (security.md "Authorization", PHASE1 step 10).
Package authz decides who may do what (security.md "Authorization", PHASE1 step 10).
backup
Package backup makes, checks and restores backups of an instance (PHASE1 step 16): every database as one consistent copy (VACUUM INTO, also while the server runs), the audit databases last, a manifest with SHA-256 per file and the head of every audit chain.
Package backup makes, checks and restores backups of an instance (PHASE1 step 16): every database as one consistent copy (VACUUM INTO, also while the server runs), the audit databases last, a manifest with SHA-256 per file and the head of every audit chain.
ca
Package ca is the CA engine: it issues end-entity certificates from a preset, after checking everything a request contains (PHASE1 step 7).
Package ca is the CA engine: it issues end-entity certificates from a preset, after checking everything a request contains (PHASE1 step 7).
ca/legacy
Package legacy reads a PKI directory of legacy/pki.sh (PHASE1 step 15): pki.conf, the root and intermediate CAs (ca.meta, certs/ca.crt, private/ca.key, index.txt, newcerts/, crlnumber, crl/ca.crl, archive/) and the issued certificates (issued/<name>/cert.meta, cert.pem, key.pem, archive/).
Package legacy reads a PKI directory of legacy/pki.sh (PHASE1 step 15): pki.conf, the root and intermediate CAs (ca.meta, certs/ca.crt, private/ca.key, index.txt, newcerts/, crlnumber, crl/ca.crl, archive/) and the issued certificates (issued/<name>/cert.meta, cert.pem, key.pem, archive/).
ca/legacy/legacytest
Package legacytest writes a small directory in the format of legacy/pki.sh with Go only – for tests that must run without bash and OpenSSL (Windows, macOS).
Package legacytest writes a small directory in the format of legacy/pki.sh with Go only – for tests that must run without bash and OpenSSL (Windows, macOS).
ca/preset
Package preset loads and validates certificate presets – profiles such as "server" or "smime" that decide which key usages, extended key usages and subject alternative names an end-entity certificate gets.
Package preset loads and validates certificate presets – profiles such as "server" or "smime" that decide which key usages, extended key usages and subject alternative names an end-entity certificate gets.
clock
Package clock provides the injectable clock (go-conventions.md "Time").
Package clock provides the injectable clock (go-conventions.md "Time").
config
Package config loads the server configuration: one YAML file, decoded strictly, plus MYOWNPKI_* environment overrides (go-conventions.md "Configuration", PLAN §14).
Package config loads the server configuration: one YAML file, decoded strictly, plus MYOWNPKI_* environment overrides (go-conventions.md "Configuration", PLAN §14).
gen
Package gen holds generated code: the agent protocol (proto/, ADR-10), generated by buf with protoc-gen-go and protoc-gen-connect-go.
Package gen holds generated code: the agent protocol (proto/, ADR-10), generated by buf with protoc-gen-go and protoc-gen-connect-go.
httpapi
Package httpapi is the REST API of the admin listener (api/openapi.yaml, PHASE1 step 13): hand-written on net/http and encoding/json.
Package httpapi is the REST API of the admin listener (api/openapi.yaml, PHASE1 step 13): hand-written on net/http and encoding/json.
ids
Package ids creates the opaque IDs of database rows (PHASE1 §1: "ULID-like").
Package ids creates the opaque IDs of database rows (PHASE1 §1: "ULID-like").
keys
Package keys holds the key hierarchy of the online CA keys (PLAN §5 "Keys", security.md "Key storage"):
Package keys holds the key hierarchy of the online CA keys (PLAN §5 "Keys", security.md "Key storage"):
pkcs12
Package pkcs12 writes PKCS#12 files (RFC 7292) for the key export (PHASE1 step 18): the private key in a PBES2-encrypted bag (PBKDF2 with HMAC-SHA256, AES-256-CBC), the certificates in a plain bag, and an HMAC-SHA256 integrity MAC – what OpenSSL 3 writes by default and what Windows 10 (1709+), macOS, Java and OpenSSL import.
Package pkcs12 writes PKCS#12 files (RFC 7292) for the key export (PHASE1 step 18): the private key in a PBES2-encrypted bag (PBKDF2 with HMAC-SHA256, AES-256-CBC), the certificates in a plain bag, and an HMAC-SHA256 integrity MAC – what OpenSSL 3 writes by default and what Windows 10 (1709+), macOS, Java and OpenSSL import.
publish
Package publish serves what certificates point to: CA certificates (AIA caIssuers) and CRLs (CRL distribution point) – plain HTTP on the public listener, no login (PHASE1 step 9; wired into the listener in step 12).
Package publish serves what certificates point to: CA certificates (AIA caIssuers) and CRLs (CRL distribution point) – plain HTTP on the public listener, no login (PHASE1 step 9; wired into the listener in step 12).
server
Package server runs MyOwnPKI (PHASE1 step 12): the admin listener with TLS, the public listener for CRLs and CA certificates, and the background jobs (audit delivery and checkpoints, CRL renewal, clean-ups).
Package server runs MyOwnPKI (PHASE1 step 12): the admin listener with TLS, the public listener for CRLs and CA certificates, and the background jobs (audit delivery and checkpoints, CRL renewal, clean-ups).
setup
Package setup is myownpki init (PHASE1 step 11): unseal passphrase, recovery key, auth key, the first user with TOTP and – optionally – an online test CA.
Package setup is myownpki init (PHASE1 step 11): unseal passphrase, recovery key, auth key, the first user with TOTP and – optionally – an online test CA.
store
Package store opens the SQLite databases (ADR-06), keeps their schemas migrated and knows where each file lives in the data directory (PLAN §4).
Package store opens the SQLite databases (ADR-06), keeps their schemas migrated and knows where each file lives in the data directory (PLAN §4).
store/migrate
Package migrate applies SQL schema migrations to one SQLite database (PLAN §4, decided instead of goose).
Package migrate applies SQL schema migrations to one SQLite database (PLAN §4, decided instead of goose).
tenant
Package tenant holds the settings of a tenant (PHASE1 step 18): whether the server may generate keys, and the subject defaults that fill empty fields of a request – what pki.conf of legacy/pki.sh did.
Package tenant holds the settings of a tenant (PHASE1 step 18): whether the server may generate keys, and the subject defaults that fill empty fields of a request – what pki.conf of legacy/pki.sh did.
testinstance
Package testinstance builds an initialized instance for tests of the HTTP layers (API, web UI): init --demo with the test CA, the services wired like the server does, a fake clock and helpers for users and TOTP codes.
Package testinstance builds an initialized instance for tests of the HTTP layers (API, web UI): init --demo with the test CA, the services wired like the server does, a fake clock and helpers for users and TOTP codes.
testutil
Package testutil holds helpers that tests of several packages share.
Package testutil holds helpers that tests of several packages share.
tools/agentui command
Command agentui starts a loopback-only UI fixture with one reported agent.
Command agentui starts a loopback-only UI fixture with one reported agent.
tools/buf command
Command buf runs the pinned buf CLI (docs/PHASE3.md §4) for `task generate`: it downloads the release binary for this system once into .tools/, checks its SHA-256 against the pins below, and runs it in the repository root.
Command buf runs the pinned buf CLI (docs/PHASE3.md §4) for `task generate`: it downloads the release binary for this system once into .tools/, checks its SHA-256 against the pins below, and runs it in the repository root.
tools/commonpasswords command
Command commonpasswords builds internal/auth/common-passwords.txt, the list of common passwords that new passwords are checked against (NIST SP 800-63B, PHASE1 step 10).
Command commonpasswords builds internal/auth/common-passwords.txt, the list of common passwords that new passwords are checked against (NIST SP 800-63B, PHASE1 step 10).
tools/screenshots command
Command screenshots renders the real UI of a throwaway demo instance with a headless Chromium-based browser (Edge, Chrome, Chromium) and writes losslessly optimized PNGs for the README to docs/screenshots.
Command screenshots renders the real UI of a throwaway demo instance with a headless Chromium-based browser (Edge, Chrome, Chromium) and writes losslessly optimized PNGs for the README to docs/screenshots.
version
Package version reports how a MyOwnPKI binary was built.
Package version reports how a MyOwnPKI binary was built.
web
Package web serves the admin UI: templ pages, the embedded static assets and the security headers every response gets.
Package web serves the admin UI: templ pages, the embedded static assets and the security headers every response gets.
web/components
templ: version: v0.3.1070
templ: version: v0.3.1070
web/fixture
Package fixture provides fixed sample data for the UI prototype in dev mode.
Package fixture provides fixed sample data for the UI prototype in dev mode.
web/layout
templ: version: v0.3.1070
templ: version: v0.3.1070
web/pages/admin
templ: version: v0.3.1070
templ: version: v0.3.1070
web/pages/authorities
templ: version: v0.3.1070
templ: version: v0.3.1070
web/pages/certificates
templ: version: v0.3.1070
templ: version: v0.3.1070
web/pages/design
Package design renders the component gallery at /design: every CSS class of web/styles is shown with realistic sample data (frontend.md).
Package design renders the component gallery at /design: every CSS class of web/styles is shown with realistic sample data (frontend.md).
web/pages/overview
templ: version: v0.3.1070
templ: version: v0.3.1070
web/view
Package view holds the types shared by the shell, components and pages: page metadata, the user's appearance preferences, status values and date formatting.
Package view holds the types shared by the shell, components and pages: page metadata, the user's appearance preferences, status values and date formatting.
Package presets embeds the built-in certificate presets (*.yaml in this directory).
Package presets embeds the built-in certificate presets (*.yaml in this directory).
Package web embeds the static UI assets: the global stylesheets (styles/*.css).
Package web embeds the static UI assets: the global stylesheets (styles/*.css).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL