WKD (advanced method): nginx-image met gpg-wks-client-gebouwde hu/-boom (infra#88)
All checks were successful
build-and-push / build (push) Successful in 8s
All checks were successful
build-and-push / build (push) Successful in 8s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
commit
21b237f0da
6 changed files with 154 additions and 0 deletions
30
.forgejo/workflows/build.yml
Normal file
30
.forgejo/workflows/build.yml
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
name: build-and-push
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
env:
|
||||||
|
IMAGE: git.doc-gen.eu/wsfcqyxbu1/wkd # lowercase namespace!
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: docker-builder
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Login bij Forgejo registry
|
||||||
|
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.doc-gen.eu -u wSFcQyxBu1 --password-stdin
|
||||||
|
- name: Build image (bouwt de hu/-boom met gpg-wks-client; faalt bij een foute/privé-sleutel)
|
||||||
|
run: docker build --pull -t "${IMAGE}:latest" -t "${IMAGE}:${{ github.sha }}" .
|
||||||
|
- name: Controle in de gebouwde image (policy per domein aanwezig)
|
||||||
|
run: |
|
||||||
|
docker run --rm "${IMAGE}:latest" sh -c 'set -e; cd /usr/share/nginx/html/.well-known/openpgpkey; for d in */; do test -f "$d/policy"; done; find . -type f | sort'
|
||||||
|
- name: Push image
|
||||||
|
run: |
|
||||||
|
docker push "${IMAGE}:latest"
|
||||||
|
docker push "${IMAGE}:${{ github.sha }}"
|
||||||
|
- name: ntfy bij falen
|
||||||
|
if: failure()
|
||||||
|
run: |
|
||||||
|
curl -s -o /dev/null -H "Authorization: Bearer ${{ secrets.NTFY_TOKEN }}" \
|
||||||
|
-H "Title: WKD CI-build FAALDE" -H "Priority: high" -H "Tags: rotating_light" \
|
||||||
|
-d "build.yml van wSFcQyxBu1/wkd faalde (run ${{ github.run_number }}, sha ${{ github.sha }})." \
|
||||||
|
https://ntfy.3838.eu/docgen-backup || true
|
||||||
22
Dockerfile
Normal file
22
Dockerfile
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
# WKD (OpenPGP Web Key Directory) — publieke PGP-sleutels van de eigenaar, advanced method
|
||||||
|
# (https://openpgpkey.<domein>/.well-known/openpgpkey/<domein>/hu/<hash>?l=<local-part>).
|
||||||
|
# Route-B image-app op de derden-box. Alles in git (alleen PUBLIEKE sleutels — nooit een privésleutel
|
||||||
|
# of passphrase in deze repo); sleutel toevoegen/vernieuwen = commit + push → CI → watcher deployt.
|
||||||
|
#
|
||||||
|
# Twee stappen:
|
||||||
|
# 1. build-stage: GnuPG's eigen gpg-wks-client bouwt uit keys/<domein>/<adres>.asc de hu/-boom
|
||||||
|
# (z-base-32(SHA-1(lowercased local-part))) + het verplichte policy-bestand. Het GnuPG-tool
|
||||||
|
# garandeert de juiste hash én exporteert alléén de UID van dat adres (spec: andere UIDs MOETEN weg).
|
||||||
|
# 2. runtime: nginx serveert de boom met de juiste headers (application/octet-stream, CORS *).
|
||||||
|
# Bumpen = de twee FROM-tags (Docker Hub: library/alpine, library/nginx).
|
||||||
|
|
||||||
|
FROM alpine:3.22.5 AS build
|
||||||
|
RUN apk add --no-cache gnupg gnupg-wks-client
|
||||||
|
COPY keys/ /keys/
|
||||||
|
COPY build-wkd.sh /build-wkd.sh
|
||||||
|
RUN sh /build-wkd.sh /keys /out
|
||||||
|
|
||||||
|
FROM nginx:1.30.4-alpine
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
COPY --from=build /out/ /usr/share/nginx/html/.well-known/openpgpkey/
|
||||||
|
EXPOSE 80
|
||||||
27
README.md
Normal file
27
README.md
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# wkd — OpenPGP Web Key Directory (publieke PGP-sleutels, advanced method)
|
||||||
|
|
||||||
|
Mailclients (GnuPG, Thunderbird, Mailvelope, Proton-lookup, …) vinden hiermee automatisch de **publieke**
|
||||||
|
PGP-sleutel bij een mailadres. Route-B image-app op de **derden-box**; per domein een subdomein
|
||||||
|
`openpgpkey.<domein>` → `https://openpgpkey.<domein>/.well-known/openpgpkey/<domein>/hu/<hash>?l=<local-part>`.
|
||||||
|
|
||||||
|
```
|
||||||
|
push naar main → Forgejo Actions (act_runner docgen): stage 1 bouwt met gpg-wks-client de hu/-boom
|
||||||
|
uit keys/<domein>/<adres>.asc, stage 2 = nginx die 'm serveert
|
||||||
|
→ push naar git.doc-gen.eu/wsfcqyxbu1/wkd:latest
|
||||||
|
→ host-watcher (docgen, |remote-entry) ziet de nieuwe digest → Coolify deployt op de derden-box
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Alles staat in git** (publieke sleutels zijn geen geheim); geen volumes, geen env, geen back-up nodig.
|
||||||
|
- **Alleen publieke sleutels.** De build faalt op een `PRIVATE KEY BLOCK`, op een bestandsnaam die niet
|
||||||
|
bij de domeinmap hoort, en op een sleutel zonder UID met exact dat adres.
|
||||||
|
- **Hash-regel** (spec, draft-koch-openpgp-webkey-service): `hu/<z-base-32(SHA-1(local-part in lowercase))>`;
|
||||||
|
wij rekenen 'm nooit zelf uit — `gpg-wks-client --install-key` doet dat, en exporteert alléén de UID
|
||||||
|
van dat adres (andere UIDs moeten eruit). Controle: `gpg-wks-client --print-wkd-hash <adres>`.
|
||||||
|
- **Sleutel toevoegen/vernieuwen:** `keys/<domein>/<adres>.asc` (= `gpg --export --armor <adres>`) →
|
||||||
|
commit + push. Nieuw domein = nieuwe map `keys/<domein>/` + DNS `openpgpkey.<domein>` + domein op de
|
||||||
|
Coolify-app.
|
||||||
|
- **Headers:** `Content-Type: application/octet-stream`, `Access-Control-Allow-Origin: *`; `policy` is leeg
|
||||||
|
(verplicht bestand). Toegangslog staat uit (geen sporen van wie welk adres opvraagt).
|
||||||
|
- **Versie:** `FROM alpine:<tag>` (build) + `FROM nginx:<tag>-alpine` (runtime) in de `Dockerfile`.
|
||||||
|
|
||||||
|
Beheer/runbook: infra-repo `wSFcQyxBu1/infra`, `runbooks/wkd-pgp-sleutels.md`; issue infra#88.
|
||||||
42
build-wkd.sh
Normal file
42
build-wkd.sh
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# Bouwt de WKD-boom (advanced method) uit keys/<domein>/<adres>.asc met gpg-wks-client.
|
||||||
|
# Gebruik: build-wkd.sh <keys-dir> <out-dir> → <out-dir>/<domein>/{policy,hu/<hash>}
|
||||||
|
# Faalt hard (build breekt) als een .asc geen publieke sleutel is, een privésleutel bevat, de
|
||||||
|
# bestandsnaam niet bij de map hoort, of de sleutel geen UID met precies dat adres heeft.
|
||||||
|
set -eu
|
||||||
|
KEYS=$1; OUT=$2
|
||||||
|
export GNUPGHOME=$(mktemp -d); chmod 700 "$GNUPGHOME"
|
||||||
|
mkdir -p "$OUT"
|
||||||
|
n=0
|
||||||
|
for domdir in "$KEYS"/*/; do
|
||||||
|
[ -d "$domdir" ] || continue
|
||||||
|
domain=$(basename "$domdir" | tr 'A-Z' 'a-z')
|
||||||
|
mkdir -p "$OUT/$domain/hu"
|
||||||
|
: > "$OUT/$domain/policy" # verplicht; leeg volstaat (spec §3.1)
|
||||||
|
for f in "$domdir"*.asc; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
addr=$(basename "$f" .asc)
|
||||||
|
case "$addr" in
|
||||||
|
*@"$domain") ;;
|
||||||
|
*) echo "FOUT: $f — bestandsnaam moet <local-part>@$domain.asc zijn" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
if grep -q 'PRIVATE KEY BLOCK' "$f"; then
|
||||||
|
echo "FOUT: $f bevat een PRIVÉSLEUTEL — nooit in deze repo" >&2; exit 1
|
||||||
|
fi
|
||||||
|
gpg --batch --quiet --import "$f"
|
||||||
|
# exact-match op het adres (<addr>), niet substring
|
||||||
|
fpr=$(gpg --batch --with-colons --list-keys "<$addr>" 2>/dev/null | awk -F: '/^fpr/{print $10; exit}')
|
||||||
|
if [ -z "$fpr" ]; then
|
||||||
|
echo "FOUT: $f — geen sleutel met een UID die exact <$addr> bevat" >&2; exit 1
|
||||||
|
fi
|
||||||
|
# --install-key: exporteert binair, filtert op deze UID, schrijft <domain>/hu/<hash> (+ policy)
|
||||||
|
gpg-wks-client --batch --quiet --directory "$OUT" --install-key "$fpr" "$addr"
|
||||||
|
hash=$(gpg-wks-client --print-wkd-hash "$addr" | awk '{print $1}')
|
||||||
|
[ -s "$OUT/$domain/hu/$hash" ] || { echo "FOUT: $OUT/$domain/hu/$hash niet aangemaakt" >&2; exit 1; }
|
||||||
|
echo "OK $addr → $domain/hu/$hash ($fpr)"
|
||||||
|
n=$((n+1))
|
||||||
|
done
|
||||||
|
done
|
||||||
|
rm -rf "$GNUPGHOME"
|
||||||
|
echo "Klaar: $n sleutel(s)."
|
||||||
|
find "$OUT" -type f | sort
|
||||||
8
keys/2487.eu/README.md
Normal file
8
keys/2487.eu/README.md
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# keys/2487.eu — publieke PGP-sleutels voor `*@2487.eu`
|
||||||
|
|
||||||
|
Eén bestand per adres: **`<local-part>@2487.eu.asc`** = `gpg --export --armor <adres>` (ASCII-armored
|
||||||
|
**publieke** sleutel; de UID moet exact dat adres bevatten — de build controleert dat en faalt anders).
|
||||||
|
|
||||||
|
- **Nooit** een privésleutel (`PRIVATE KEY BLOCK`) of passphrase hier — de build weigert die.
|
||||||
|
- Sleutel vernieuwen/intrekken = het `.asc` vervangen (of verwijderen) + commit + push.
|
||||||
|
- Een domeinmap zónder `.asc` levert alleen het (lege) `policy`-bestand — dat is genoeg voor een geldige WKD.
|
||||||
25
nginx.conf
Normal file
25
nginx.conf
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
# WKD-server (advanced method). Alleen /.well-known/openpgpkey/ heeft inhoud.
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
server_tokens off;
|
||||||
|
access_log off; # sleutelopvragingen niet loggen (geen adres-sporen)
|
||||||
|
|
||||||
|
location = / {
|
||||||
|
default_type text/plain;
|
||||||
|
return 200 "OpenPGP Web Key Directory (advanced method). Zie /.well-known/openpgpkey/<domein>/policy\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
location /.well-known/openpgpkey/ {
|
||||||
|
# spec: sleutel binair als application/octet-stream; CORS-* voor browser-clients (Mailvelope e.d.)
|
||||||
|
types { }
|
||||||
|
default_type application/octet-stream;
|
||||||
|
add_header Access-Control-Allow-Origin "*" always;
|
||||||
|
add_header Cache-Control "public, max-age=3600" always;
|
||||||
|
autoindex off;
|
||||||
|
try_files $uri =404; # ?l=<local-part> wordt genegeerd (statisch bestand)
|
||||||
|
}
|
||||||
|
|
||||||
|
location / { return 404; }
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue