- Go 93.6%
- Shell 6.4%
commit |
||
|---|---|---|
| completions | ||
| testdata | ||
| .gitignore | ||
| .gogogo.conf | ||
| .golangci.yaml | ||
| check.go | ||
| check_test.go | ||
| CLAUDE.md | ||
| completion.go | ||
| completion_test.go | ||
| domain.go | ||
| domain_test.go | ||
| expiry.go | ||
| explain.go | ||
| explain_test.go | ||
| glob.go | ||
| glob_test.go | ||
| go.mod | ||
| go.sum | ||
| gpg-publish-concept.svg | ||
| gpg.go | ||
| label.go | ||
| label_test.go | ||
| LICENSE | ||
| main.go | ||
| main_test.go | ||
| nsupdate.go | ||
| nsupdate_test.go | ||
| openpgpkey.go | ||
| openpgpkey_test.go | ||
| rdata.go | ||
| rdata_test.go | ||
| README.md | ||
| strip.go | ||
| strip_test.go | ||
| ttl.go | ||
| ttl_test.go | ||
| warn.go | ||
gpg-publish
The Missing Link for OPENPGPKEY DNS Records
Publishing GPG keys to DNS shouldn't require manual hex-editing or breaking your zone files with oversized TXT blobs. gpg-publish is a dedicated CLI that cleanly distills your local keyring into RFC 7929 compliant DNS records, instantly making your encryption keys globally discoverable.
Why you need it:
- Intelligent Key Minimization: DNS hates large records.
gpg-publishautomatically strips third-party signatures, photo IDs, and expired subkeys, guaranteeing a minimal, DNS-friendly footprint. - Zero-Friction Discovery: Match your workflow. Pull keys by exact email, use glob patterns (
*@yourdomain.com), or pipe directly from standard input. - Zone & Automation Ready: Outputs exactly what you need. Choose compact terminal output, wrapped zone-file strings, or fully automated
nsupdatetransactions with built-innxrrsetsafeguards. - Close the Loop with
--check: Don't just fire and forget. Instantly compare your local keyring against live DNS records to spot missing, mismatched, or outdated keys in seconds.
A small tool to generate OPENPGPKEY DNS records from a GPG public key.
The tool retrieves a GPG key, extracts its User IDs (email addresses), and generates a corresponding DNS record for each valid identity. It can get the key from your local GPG keyring, a file, or standard input.
The output conforms to RFC 7929:
- Each record contains a minimal key — only the matching User ID, its
self-signature, and non-expired subkeys. Third-party signatures, unrelated
User IDs, User Attributes (photos), and expired subkeys are stripped.
Stripping details are reported as warnings to stderr when
--warningsis enabled. - When fetching from the local keyring,
gpg --export-options export-minimalis used for an initial size reduction before programmatic stripping. - Multiple User IDs with the same email on one entity are deduplicated into a single record (the DNS label depends only on the email address). A warning is emitted if different entities (different keys) claim the same email, as this creates conflicting records.
The tool also checks key validity:
- Expired keys are silently skipped.
- Keys expiring within 30 days will be used, but a warning will be printed to standard error.
Warnings about stripped key elements are printed to stderr only when --warnings
is enabled. When stderr is a terminal, they appear dimmed (ANSI faint) to reduce
visual noise.
Usage
gpg-publish [flags] <target>
Target
The target argument can be one of the following:
- An email address: The tool will call
gpg --export --export-options export-minimal <email>to retrieve the key from your local keyring. - A glob pattern: If the target contains
*or?characters, it is treated as a glob pattern matching email addresses in your keyring. The tool finds all matching keys (with at least 30 days of remaining validity) and exports them together. For example,'*@example.com'matches all identities under theexample.comdomain. - A file path: The tool will read the key from the specified file.
-: The tool will read the key from standard input.
Flags
-
--domain/-d string: Filter output to identities in a specific domain. Can be repeated.example.com— exact domain match only.example.com— matchesexample.comand all subdomains*.example.com— matches subdomains only (not the bare domain)
When omitted, records are emitted for all valid identities in the key.
-
--check/-c: Compare generated records against live DNS instead of printing them. ReportsOK,DIFF(record exists but differs), orMISS(no record in DNS) per identity. The summary distinguishes counts: e.g. "DNS check failed: 1 not found, 1 mismatched". Exits non-zero if any record differs or is missing. -
--resolver string: DNS resolver to use with--check(e.g.8.8.8.8,8.8.8.8:53, orns1.example.com). Hostnames are resolved using the system resolver. Defaults to the first nameserver from/etc/resolv.conf. -
--verbose/-v: With--check, show key metadata (fingerprint, creation date, expiry, identities) for both local and DNS keys when a DIFF is detected. -
--output-format format: Output format (defaultshortin a terminal,wrappedwhen piped). Choose one of:short: Compact one-line format with TTL, record type, an abbreviated key (first 4 chars +…+ last 4 chars), and comment (human-readable, useful for terminal output)wrapped: Zone-file records with( ... )wrapping for large keysplain: Zone-file records on a single line (no wrapping)nsupdate: nsupdate commands. Each record is its own transaction, guarded withprereq nxrrset <label> OPENPGPKEYso it fails cleanly if a record already exists.
-
--force: With--output-format=nsupdate, replace existing records instead of bailing out. Emitsupdate delete <label> OPENPGPKEYbeforeupdate addso any previous record is removed first. -
--completion shell: Print shell completion script and exit. Supported shells:bash,zsh,fish. Pipe to your shell's completion directory to enable auto-completion forgpg-publish.
--check and --output-format are mutually exclusive.
-
--ttl value: TTL for DNS records (default1h). Accepts seconds (3600) or duration strings (1h,30m,1h30m). Applies to all output formats. -
--version: Print version information and exit. -
--warnings/-w: Show warnings about stripped key content (User Attributes, third-party signatures, expired subkeys) on stderr. Off by default.
Examples
1. From GPG Keyring
Generate records for all identities in the key for hs@schlittermann.de.
gpg-publish hs@schlittermann.de
2. Exact domain filter
Emit records only for identities whose domain is exactly schlittermann.de.
gpg-publish --domain schlittermann.de mykey.asc
3. Domain plus all subdomains
Emit records for schlittermann.de and any subdomain (e.g. ml.schlittermann.de).
gpg-publish --domain .schlittermann.de mykey.asc
4. Subdomains only Emit records for subdomain identities, not the bare domain.
gpg-publish --domain '*.schlittermann.de' mykey.asc
5. Multiple domain filters Combine filters to cover the domain and a specific subdomain set.
gpg-publish --domain schlittermann.de --domain '*.schlittermann.de' mykey.asc
6. From Standard Input
Pipe a key directly from gpg and generate records for all identities.
gpg --export hs@schlittermann.de | gpg-publish -
7. Glob pattern — all keys under a domain Expand a glob pattern to match all keys in your keyring whose email addresses match. Only keys with at least 30 days of remaining validity are included.
gpg-publish '*@schlittermann.de'
gpg-publish 'user*@example.com'
gpg-publish --output-format=nsupdate '*@example.com' | nsupdate -v
8. Check records against DNS Verify that published DNS records match the current key.
gpg-publish --check hs@schlittermann.de
gpg-publish --check --domain schlittermann.de hs@schlittermann.de
gpg-publish --check --resolver 8.8.8.8 --domain schlittermann.de hs@schlittermann.de
9. Generate nsupdate commands
Produce output for piping into nsupdate(1). By default each transaction is
guarded with prereq nxrrset so it fails cleanly if a record already exists.
Add --force to replace any existing records.
gpg-publish --output-format=nsupdate hs@schlittermann.de | nsupdate -v
gpg-publish --output-format=nsupdate --ttl 7200 --domain schlittermann.de mykey.asc
gpg-publish --output-format=nsupdate --force hs@schlittermann.de | nsupdate -v
10. Install shell completion Generate and install completion for your shell.
gpg-publish --completion bash | sudo tee /etc/bash_completion.d/gpg-publish
gpg-publish --completion zsh | sudo tee /usr/local/share/zsh/site-functions/_gpg-publish
gpg-publish --completion fish | sudo tee /usr/local/share/fish/vendor_completions.d/gpg-publish.fish