aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNikolay Govorov <me@govorov.online>2026-08-22 00:18:27 +0100
committerNikolay Govorov <me@govorov.online>2026-08-22 00:18:27 +0100
commit455b20c4e918933dd92f522f1fa212fba71c1ace (patch)
treee38644322755aec69312072ee27a94c4de544659
parentfc4d5518c3096fd68f08a300595c638e37454982 (diff)
downloadtar
tar.gz
tar.bz2
tar.lz
tar.xz
tar.zst
zip
Supports multiple SSH keys for the administrator.
Diffstat
-rw-r--r--README.md2+1 −1
-rw-r--r--charts/gilti/README.md9+5 −4
-rw-r--r--charts/gilti/templates/deployment.yaml3+0 −3
-rw-r--r--charts/gilti/values.schema.json3+1 −2
-rw-r--r--charts/gilti/values.yaml1+0 −1
-rwxr-xr-xscripts/entrypoint.sh48+39 −9
-rwxr-xr-xtests/chart.sh3+3 −0
-rwxr-xr-xtests/smoke.sh23+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'