authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-01-22 01:54:20-08:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-01-22 23:36:21-08:00
loge402d6ef71dfc310caeeb4d3579bddd9d0710594
tree5f1900497cf85c0c5a0f8bd4626576a23e207549
parentc94f61522e07ad2783764708af19703355c3e999
signaturelock-open Commit is signed but in an unrecognized format.

feat: ssh auth for git services

to avoid having a separate user like `ssh evil-git@git.evil.inc`, instead the gambit is to have two intercept scripts to perform routing based on the repo that is being pushed to.

9 files changed, 119 insertions(+), 28 deletions(-)

compose.yaml+2-2
...@@ -152,7 +152,6 @@ services:...@@ -152,7 +152,6 @@ services:
152 image: code.forgejo.org/forgejo/forgejo:13152 image: code.forgejo.org/forgejo/forgejo:13
153 entrypoint: ["/bin/sh", "/custom/init/entry.sh"]153 entrypoint: ["/bin/sh", "/custom/init/entry.sh"]
154 ports:154 ports:
155 - 40243:22
156 - 40244:3000 # port used for forgejo-runner155 - 40244:3000 # port used for forgejo-runner
157 environment:156 environment:
158 USER_UID: "$USER_ID"157 USER_UID: "$USER_ID"
...@@ -167,6 +166,7 @@ services:...@@ -167,6 +166,7 @@ services:
167 FORGEJO_OAUTH2_JWT_SECRET: "${FORGEJO_OAUTH2_JWT_SECRET:?}"166 FORGEJO_OAUTH2_JWT_SECRET: "${FORGEJO_OAUTH2_JWT_SECRET:?}"
168 FORGEJO_SECURITY_SECRET_KEY: "${FORGEJO_SECURITY_SECRET_KEY:?}"167 FORGEJO_SECURITY_SECRET_KEY: "${FORGEJO_SECURITY_SECRET_KEY:?}"
169 FORGEJO_SECURITY_INTERNAL_TOKEN: "${FORGEJO_SECURITY_INTERNAL_TOKEN:?}"168 FORGEJO_SECURITY_INTERNAL_TOKEN: "${FORGEJO_SECURITY_INTERNAL_TOKEN:?}"
169 HOME_INFRA_HOST_DIR: "${HOME_INFRA_HOST_DIR:?}"
170 restart: unless-stopped170 restart: unless-stopped
171 volumes:171 volumes:
172 - "${APP_ROOT}/keycloak:/shared-keys:ro"172 - "${APP_ROOT}/keycloak:/shared-keys:ro"
...@@ -590,7 +590,6 @@ services:...@@ -590,7 +590,6 @@ services:
590 image: code.forgejo.org/forgejo/forgejo:13590 image: code.forgejo.org/forgejo/forgejo:13
591 entrypoint: ["/bin/sh", "/custom/init/entry.sh"]591 entrypoint: ["/bin/sh", "/custom/init/entry.sh"]
592 ports:592 ports:
593 - 22666:22
594 - 40666:3000 # port used for forgejo-runner593 - 40666:3000 # port used for forgejo-runner
595 environment:594 environment:
596 USER_UID: "$USER_ID"595 USER_UID: "$USER_ID"
...@@ -604,6 +603,7 @@ services:...@@ -604,6 +603,7 @@ services:
604 FORGEJO_SECURITY_SECRET_KEY: "${EVIL_FORGEJO_SECURITY_SECRET_KEY}"603 FORGEJO_SECURITY_SECRET_KEY: "${EVIL_FORGEJO_SECURITY_SECRET_KEY}"
605 FORGEJO_SECURITY_INTERNAL_TOKEN: "${EVIL_FORGEJO_SECURITY_INTERNAL_TOKEN}"604 FORGEJO_SECURITY_INTERNAL_TOKEN: "${EVIL_FORGEJO_SECURITY_INTERNAL_TOKEN}"
606 POSTGRES_PASSWORD_EVIL_FORGEJO: "${POSTGRES_PASSWORD_EVIL_FORGEJO:?}"605 POSTGRES_PASSWORD_EVIL_FORGEJO: "${POSTGRES_PASSWORD_EVIL_FORGEJO:?}"
606 HOME_INFRA_HOST_DIR: "${HOME_INFRA_HOST_DIR:?}"
607 restart: unless-stopped607 restart: unless-stopped
608 volumes:608 volumes:
609 - "${APP_ROOT}/evil-infra/forgejo:/data:rw"609 - "${APP_ROOT}/evil-infra/forgejo:/data:rw"
config/evil-infra/forgejo/init/app.ini+3-3
...@@ -31,13 +31,13 @@ DOMAIN=git.evil.inc...@@ -31,13 +31,13 @@ DOMAIN=git.evil.inc
31SSH_DOMAIN=git.evil.inc31SSH_DOMAIN=git.evil.inc
32HTTP_PORT=300032HTTP_PORT=3000
33ROOT_URL=https://git.evil.inc33ROOT_URL=https://git.evil.inc
34DISABLE_SSH=false
35SSH_PORT=22
36SSH_LISTEN_PORT=22
37LFS_JWT_SECRET=$FORGEJO_SERVER_LFS_JWT_SECRET34LFS_JWT_SECRET=$FORGEJO_SERVER_LFS_JWT_SECRET
38LFS_START_SERVER=true35LFS_START_SERVER=true
39OFFLINE_MODE=true36OFFLINE_MODE=true
40LANDING_PAGE=/evil37LANDING_PAGE=/evil
38START_SSH_SERVER=false
39SSH_CREATE_AUTHORIZED_KEYS_FILE=false
40SSH_AUTHORIZED_KEYS_COMMAND_TEMPLATE=$HOME_INFRA_HOST_DIR/config/forgejo/ssh/route.sh {{.Key.ID}}
4141
42[database]42[database]
43DB_TYPE=postgres43DB_TYPE=postgres
config/forgejo/init/app.ini+3-3
...@@ -32,13 +32,13 @@ DOMAIN=git.$HOME_DOMAIN...@@ -32,13 +32,13 @@ DOMAIN=git.$HOME_DOMAIN
32SSH_DOMAIN=git.$HOME_DOMAIN32SSH_DOMAIN=git.$HOME_DOMAIN
33HTTP_PORT=300033HTTP_PORT=3000
34ROOT_URL=https://git.$HOME_DOMAIN/34ROOT_URL=https://git.$HOME_DOMAIN/
35DISABLE_SSH=false
36SSH_PORT=22
37SSH_LISTEN_PORT=22
38LFS_JWT_SECRET=$FORGEJO_SERVER_LFS_JWT_SECRET35LFS_JWT_SECRET=$FORGEJO_SERVER_LFS_JWT_SECRET
39LFS_START_SERVER=true36LFS_START_SERVER=true
40OFFLINE_MODE=true37OFFLINE_MODE=true
41LANDING_PAGE=/clo38LANDING_PAGE=/clo
39START_SSH_SERVER=false
40SSH_CREATE_AUTHORIZED_KEYS_FILE=false
41SSH_AUTHORIZED_KEYS_COMMAND_TEMPLATE=$HOME_INFRA_HOST_DIR/config/forgejo/ssh/route.sh {{.Key.ID}}
4242
43[database]43[database]
44DB_TYPE=postgres44DB_TYPE=postgres
config/forgejo/ssh/config created+3
...@@ -0,0 +1,3 @@
1Match User git
2 AuthorizedKeysCommand /bin/python /mnt/storage1/apps/home-infra/config/forgejo/ssh/keys.py %u %t %k
3 AuthorizedKeysCommandUser git
config/forgejo/ssh/keys.py created+57
...@@ -0,0 +1,57 @@
1#!/usr/bin/env python3
2import sys
3import subprocess
4import re
5
6user, key_type, key = sys.argv[1:4]
7
8
9def get_key_id(container: str):
10 try:
11 result = subprocess.run(
12 [
13 "sudo",
14 "/usr/bin/docker",
15 "exec",
16 "-u",
17 "3000:3000",
18 container,
19 "/usr/local/bin/forgejo",
20 "keys",
21 "-c",
22 "/custom/conf/app.ini",
23 "-e",
24 "git",
25 "-u",
26 user,
27 "-t",
28 key_type,
29 "-k",
30 key,
31 ],
32 capture_output=True,
33 text=True,
34 check=False,
35 )
36 match = re.search(r"route\.sh (\d+)", result.stdout)
37 return match.group(1) if match else None
38 except:
39 return None
40
41
42clover_id = get_key_id("forgejo")
43evil_id = get_key_id("evil-forgejo")
44
45if not clover_id and not evil_id:
46 sys.exit(0)
47
48args: list[str] = []
49if clover_id:
50 args.extend(["--clover", clover_id])
51if evil_id:
52 args.extend(["--evil", evil_id])
53
54cmd = "/mnt/storage1/apps/home-infra/config/forgejo/ssh/route.sh " + " ".join(args)
55print(
56 f'command="{cmd}",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict {key_type} {key}'
57)
config/forgejo/ssh/route.sh created+48
...@@ -0,0 +1,48 @@
1#!/bin/sh
2clover_id=""
3evil_id=""
4
5while [ $# -gt 0 ]; do
6 case "$1" in
7 --clover)
8 clover_id="$2"
9 shift 2
10 ;;
11 --evil)
12 evil_id="$2"
13 shift 2
14 ;;
15 *) shift ;;
16 esac
17done
18
19repo="${SSH_ORIGINAL_COMMAND#* }"
20repo="${repo%\'*}"
21repo="${repo#\'}"
22repo="${repo#/}"
23
24case "$repo" in
25*[!a-zA-Z0-9/_.-]*)
26 echo "invalid repo name: $repo" >&2
27 exit 1
28 ;;
29*..*)
30 echo "invalid repo name: $repo" >&2
31 exit 1
32 ;;
33/*)
34 echo "invalid repo name: $repo" >&2
35 exit 1
36 ;;
37esac
38
39if [ -d "/mnt/storage1/apps/forgejo/git/repositories/${repo}" ]; then
40 [ -z "$clover_id" ] && echo "ssh key is not configured for repository on git.paperclover.net" >&2 && exit 1
41 exec sudo docker exec -i -u git forgejo /usr/bin/env SSH_ORIGINAL_COMMAND="$SSH_ORIGINAL_COMMAND" /usr/local/bin/forgejo --config=/custom/conf/app.ini serv key-"$clover_id"
42elif [ -d "/mnt/storage1/apps/evil-infra/forgejo/git/repositories/${repo}" ]; then
43 [ -z "$evil_id" ] && echo "ssh key is not configured for repository on git.evil.inc" >&2 && exit 1
44 exec sudo docker exec -i -u git evil-forgejo /usr/bin/env SSH_ORIGINAL_COMMAND="$SSH_ORIGINAL_COMMAND" /usr/local/bin/forgejo --config=/custom/conf/app.ini serv key-"$evil_id"
45else
46 echo "repo not found" >&2
47 exit 1
48fi
docker.sh created+2
...@@ -0,0 +1,2 @@
1#!/bin/sh
2ssh clo@192.168.0.1 -- cd /mnt/storage1/apps/home-infra \&\& sudo docker "$@"
generate-env.sh+1
...@@ -10,6 +10,7 @@ fi...@@ -10,6 +10,7 @@ fi
10printf "generating...\n"10printf "generating...\n"
1111
12template() {12template() {
13 add "HOME_INFRA_HOST_DIR" "$PWD"
13 add "HOME_DOMAIN" "local.test"14 add "HOME_DOMAIN" "local.test"
14 add "ADMIN_EMAIL" ""15 add "ADMIN_EMAIL" ""
15 add "USER_ID" "$(id -u)"16 add "USER_ID" "$(id -u)"
readme.md-20
...@@ -3,26 +3,6 @@...@@ -3,26 +3,6 @@
3this codebase is used for all docker services on my home nas, including personal3this codebase is used for all docker services on my home nas, including personal
4customization.4customization.
55
6## usage
7
8```
9git clone https://git.paperclover.net/clo/home-infra.git
10cd home-infra
11
12# setup repo
13rm config/font/*
14git clone https://git.paperclover.net/clo/sitegen.git
15cp .env.example .env
16
17# fill secrets
18vi .env
19vi secrets/openvpn-credentials.txt
20
21docker compose up -d
22```
23
24## license
25
26this repository is for example usage only. you may not re-host it in full,6this repository is for example usage only. you may not re-host it in full,
27privately or publicly, but you may learn from my usage. please note that some7privately or publicly, but you may learn from my usage. please note that some
28components here are under licenses which do not permit redistribution.8components here are under licenses which do not permit redistribution.