diff options
| author | Nikolay Govorov <me@govorov.online> | 2026-08-22 00:18:27 +0100 |
|---|---|---|
| committer | Nikolay Govorov <me@govorov.online> | 2026-08-22 00:18:27 +0100 |
| commit | 455b20c4e918933dd92f522f1fa212fba71c1ace (patch) | |
| tree | e38644322755aec69312072ee27a94c4de544659 | |
| parent | fc4d5518c3096fd68f08a300595c638e37454982 (diff) | |
| download | tar tar.gz tar.bz2 tar.lz tar.xz tar.zst zip | |
Supports multiple SSH keys for the administrator.
Diffstat
| -rw-r--r-- | README.md | 2 | +1 −1 |
| -rw-r--r-- | charts/gilti/README.md | 9 | +5 −4 |
| -rw-r--r-- | charts/gilti/templates/deployment.yaml | 3 | +0 −3 |
| -rw-r--r-- | charts/gilti/values.schema.json | 3 | +1 −2 |
| -rw-r--r-- | charts/gilti/values.yaml | 1 | +0 −1 |
| -rwxr-xr-x | scripts/entrypoint.sh | 48 | +39 −9 |
| -rwxr-xr-x | tests/chart.sh | 3 | +3 −0 |
| -rwxr-xr-x | tests/smoke.sh | 23 | +22 −1 |
8 files changed, 71 insertions, 21 deletions
diff --git a/README.md b/README.md index 488c58b..6c3ff10 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ service for Dimidium Labs. ## Container -A fresh state directory requires one administrator public key: +A fresh state directory requires an administrator public key: ```console docker build -t gilti:dev . diff --git a/charts/gilti/README.md b/charts/gilti/README.md index 38433fe..46eea9f 100644 --- a/charts/gilti/README.md +++ b/charts/gilti/README.md @@ -8,7 +8,7 @@ uninstalling the release does not delete authoritative Git data. ## Bootstrap A fresh volume requires an existing Secret containing the administrator's SSH -public key. The default key name is `admin.pub`: +public key as `admin.pub`: ```console kubectl create secret generic gilti-bootstrap --from-file=admin.pub @@ -16,9 +16,10 @@ helm upgrade --install gilti . \ --set bootstrap.existingSecret=gilti-bootstrap ``` -The key is ignored after successful initialization and the Secret may then be -removed from values. A partially initialized volume is never overwritten -automatically. +Additional `*.pub` entries in the same Secret are committed to `gitolite-admin` +as additional keys for the same `admin` identity. Bootstrap keys are ignored +after successful initialization and the Secret may then be removed from values. +A partially initialized volume is never overwritten automatically. ## Publishing repositories diff --git a/charts/gilti/templates/deployment.yaml b/charts/gilti/templates/deployment.yaml index e358172..1193fca 100644 --- a/charts/gilti/templates/deployment.yaml +++ b/charts/gilti/templates/deployment.yaml @@ -103,9 +103,6 @@ spec: {{- if .Values.bootstrap.existingSecret }} secret: secretName: {{ .Values.bootstrap.existingSecret }} - items: - - key: {{ .Values.bootstrap.key }} - path: admin.pub {{- else }} emptyDir: {} {{- end }} diff --git a/charts/gilti/values.schema.json b/charts/gilti/values.schema.json index a8c1e28..b7ace1b 100644 --- a/charts/gilti/values.schema.json +++ b/charts/gilti/values.schema.json @@ -16,8 +16,7 @@ "bootstrap": { "type": "object", "properties": { - "existingSecret": { "type": "string" }, - "key": { "type": "string", "minLength": 1 } + "existingSecret": { "type": "string" } } }, "cgit": { diff --git a/charts/gilti/values.yaml b/charts/gilti/values.yaml index a793142..04518b5 100644 --- a/charts/gilti/values.yaml +++ b/charts/gilti/values.yaml @@ -16,7 +16,6 @@ fullnameOverride: "" bootstrap: # Secret key is needed only while initializing a fresh persistent volume. existingSecret: "" - key: admin.pub cgit: rootTitle: Gilti diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh index b32a3bc..4859c63 100755 --- a/scripts/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -12,7 +12,8 @@ cache_dir=/var/cache/cgit git_home=$state/git host_key_dir=$state/ssh -admin_key=${GILTI_ADMIN_KEY_FILE:-/run/gilti-bootstrap/admin.pub} +bootstrap_dir=/run/gilti-bootstrap +admin_key=${GILTI_ADMIN_KEY_FILE:-$bootstrap_dir/admin.pub} log() { printf 'gilti: %s\n' "$*" >&2 @@ -22,6 +23,41 @@ run_as_git() { su-exec git:git env HOME="$git_home" USER=git LOGNAME=git "$@" } +validate_public_key_file() { + key_file=$1 + [ "$(awk 'NF && $1 !~ /^#/ { count++ } END { print count + 0 }' "$key_file")" -eq 1 ] && + ssh-keygen -l -f "$key_file" >/dev/null 2>&1 +} + +validate_bootstrap_keys() { + [ -r "$admin_key" ] || { + log "fresh state requires an admin public key at $admin_key" + exit 1 + } + for key_file in "$bootstrap_dir"/*.pub; do + [ -e "$key_file" ] || continue + validate_public_key_file "$key_file" || { + log "bootstrap key $key_file is not exactly one valid SSH public key" + exit 1 + } + done +} + +stage_additional_admin_keys() { + install -d -m 0750 -o git -g git "$git_home/.gitolite" + install -d -m 0750 -o git -g git "$git_home/.gitolite/keydir" + install -d -m 0750 -o git -g git "$git_home/.gitolite/logs" + count=0 + for key_file in "$bootstrap_dir"/*.pub; do + [ -e "$key_file" ] || continue + [ "$key_file" = "$admin_key" ] && continue + count=$((count + 1)) + destination=$git_home/.gitolite/keydir/gilti-bootstrap-$count + install -d -m 0750 -o git -g git "$destination" + install -m 0644 -o git -g git "$key_file" "$destination/admin.pub" + done +} + prepare_runtime() { [ "$(id -u)" -eq 0 ] || { log "the supervisor must start as root"; exit 1; } install -d -m 0755 -o root -g root "$state" @@ -64,14 +100,8 @@ initialize() { exit 1 ;; fresh) - [ -r "$admin_key" ] || { - log "fresh state requires an admin public key at $admin_key" - exit 1 - } - ssh-keygen -l -f "$admin_key" >/dev/null 2>&1 || { - log "the bootstrap admin key is not a valid SSH public key" - exit 1 - } + validate_bootstrap_keys + stage_additional_admin_keys log "initializing Gitolite" run_as_git gitolite setup -pk "$admin_key" ;; diff --git a/tests/chart.sh b/tests/chart.sh index 3bd3810..d649656 100755 --- a/tests/chart.sh +++ b/tests/chart.sh @@ -15,6 +15,7 @@ injected=$(mktemp) trap 'rm -f "$rendered" "$invalid" "$injected"' EXIT helm template gilti "$chart" \ + --set bootstrap.existingSecret=gilti-bootstrap \ --set httpRoute.enabled=true \ --set 'httpRoute.hostnames[0]=git.example.test' \ --set 'httpRoute.parentRefs[0].name=public' \ @@ -25,6 +26,8 @@ grep -q '^kind: HTTPRoute$' "$rendered" grep -q '^kind: TCPRoute$' "$rendered" grep -q '^apiVersion: gateway.networking.k8s.io/v1$' "$rendered" grep -q 'helm.sh/resource-policy: keep' "$rendered" +grep -q 'secretName: gilti-bootstrap' "$rendered" +grep -q 'mountPath: /run/gilti-bootstrap' "$rendered" cat >"$injected" <<'EOF' cgit: diff --git a/tests/smoke.sh b/tests/smoke.sh index 5b99023..a64a97b 100755 --- a/tests/smoke.sh +++ b/tests/smoke.sh @@ -24,13 +24,30 @@ cleanup() { trap cleanup EXIT INT TERM ssh-keygen -q -t ed25519 -N '' -f "$work/admin" +ssh-keygen -q -t ed25519 -N '' -f "$work/admin-2" ssh-keygen -q -t ed25519 -N '' -f "$work/stranger" +mkdir "$work/bootstrap" +cp "$work/admin.pub" "$work/bootstrap/admin.pub" +cp "$work/admin-2.pub" "$work/bootstrap/admin-2.pub" "$engine" volume create "$volume" >/dev/null +printf '%s\n' 'not an SSH key' >"$work/bootstrap/bad.pub" +if "$engine" run --rm \ + --cap-drop ALL \ + --cap-add CHOWN --cap-add DAC_OVERRIDE --cap-add FOWNER \ + --cap-add SETGID --cap-add SETUID --cap-add SYS_CHROOT \ + --mount "type=volume,src=$volume,dst=/var/lib/gilti" \ + --mount "type=bind,src=$work/bootstrap,dst=/run/gilti-bootstrap,readonly" \ + "$image" init >/dev/null 2>&1; then + echo 'initialization accepted a malformed additional key' >&2 + exit 1 +fi +rm "$work/bootstrap/bad.pub" + start() { key_mount= if [ "${1:-with-key}" = with-key ]; then - key_mount="--mount type=bind,src=$work/admin.pub,dst=/run/gilti-bootstrap/admin.pub,readonly" + key_mount="--mount type=bind,src=$work/bootstrap,dst=/run/gilti-bootstrap,readonly" fi # shellcheck disable=SC2086 "$engine" run -d --name "$name" \ @@ -76,6 +93,8 @@ fi ssh_opts="-o BatchMode=yes -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=$work/known_hosts -i $work/admin -p $ssh_port" # shellcheck disable=SC2086 ssh $ssh_opts git@127.0.0.1 info | grep -q 'hello admin' +ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ + -i "$work/admin-2" -p "$ssh_port" git@127.0.0.1 info | grep -q 'hello admin' if ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ -i "$work/stranger" -p "$ssh_port" git@127.0.0.1 info >/dev/null 2>&1; then @@ -127,3 +146,5 @@ fingerprint_after=$(ssh-keyscan -p "$ssh_port" 127.0.0.1 2>/dev/null | ssh-keyge exit 1 } curl -fsS "http://127.0.0.1:$http_port/" | grep -q 'testing' +ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ + -i "$work/admin-2" -p "$ssh_port" git@127.0.0.1 info | grep -q 'hello admin' |
