diff --git a/README.md b/README.md index c125509..96a1779 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ TARGET_DIR=~/work/rta-handbook \ ## Daily Flow ```sh +section0-docs auth login section0-docs doctor section0-docs pull @@ -69,16 +70,20 @@ the lab operator side; collaborators do not need the home-lab repo. ## Contributor Setup -Anonymous clone/read works on the private lab network. Pushing requires an -account or access token with write access to `section0/rta-handbook`. +Anonymous clone/read works on the private lab network. Pushing requires +Authentik login through the helper. Run this once per checkout: ```sh -section0-docs configure +section0-docs auth login ``` -Then prove write access without changing `main`: +The login opens Authentik in your browser, configures the Git author from your +identity, and installs the repo Git credential. After that, normal Git tooling +works too. + +Prove write access without changing `main`: ```sh section0-docs push-test @@ -87,7 +92,8 @@ section0-docs push-test `push-test` does not commit files and does not change `main`. It pushes the current commit to a temporary scratch branch, then deletes that branch immediately. If Git prompts for a password, use a Gitea access token rather -than your normal account password. +than your normal account password. If login is healthy, Git should usually get +the credential from your local credential store. ## End-To-End Smoke Test diff --git a/scripts/setup-section0-docs.sh b/scripts/setup-section0-docs.sh index c8ae22e..931f839 100755 --- a/scripts/setup-section0-docs.sh +++ b/scripts/setup-section0-docs.sh @@ -6,6 +6,7 @@ TARGET_DIR="${TARGET_DIR:-$HOME/Developer/Section0/rta-handbook}" COMMAND_DIR="${COMMAND_DIR:-$HOME/.local/bin}" COMMAND_NAME="${COMMAND_NAME:-section0-docs}" COMMAND_PATH="$COMMAND_DIR/$COMMAND_NAME" +SERVER_URL="${SECTION0_SERVER_URL:-https://ops.virgil.info/md-to-section0-api}" step() { printf "\n==> %s\n" "$*" @@ -37,12 +38,17 @@ cat > "$COMMAND_PATH" </dev/null 2>&1 || { + echo "python3 is required for Authentik login JSON handling" >&2 + exit 1 + } +} + +json_get() { + need_python + python3 -c 'import json,sys; data=json.load(sys.stdin); cur=data +for part in sys.argv[1].split("."): + cur = cur.get(part, "") if isinstance(cur, dict) else "" +print(cur if cur is not None else "")' "\$1" +} + +open_url() { + if command -v open >/dev/null 2>&1; then + open "\$1" >/dev/null 2>&1 || true + elif command -v xdg-open >/dev/null 2>&1; then + xdg-open "\$1" >/dev/null 2>&1 || true + fi +} + +credential_host() { + need_python + python3 -c 'from urllib.parse import urlparse; import sys +url=urlparse(sys.argv[1]) +print(url.netloc)' "\$1" +} + +install_git_credential() { + remote="\$1" + username="\$2" + password="\$3" + host="\$(credential_host "\$remote")" + protocol="\$(printf "%s" "\$remote" | sed -n "s#^\\([^:/]*\\)://.*#\\1#p")" + [ -n "\$protocol" ] || protocol="http" + if [ "\$(uname -s 2>/dev/null || true)" = "Darwin" ]; then + git -C "\$REPO_DIR" config credential.helper osxkeychain + fi + printf "protocol=%s\nhost=%s\nusername=%s\npassword=%s\n\n" "\$protocol" "\$host" "\$username" "\$password" \ + | git -C "\$REPO_DIR" credential approve +} + +auth_login() { + need_python + mkdir -p "\$SESSION_DIR" + device_json="\$(curl -fsS -X POST "\$SERVER_URL/device")" + code="\$(printf "%s" "\$device_json" | json_get code)" + auth_url="\$(printf "%s" "\$device_json" | json_get authUrl)" + [ -n "\$code" ] && [ -n "\$auth_url" ] || { + echo "login server did not return a device code" >&2 + exit 1 + } + echo "Opening Authentik login:" + echo " \$auth_url" + open_url "\$auth_url" + echo "Waiting for login..." + token_json="" + i=0 + while [ "\$i" -lt 90 ]; do + token_json="\$(curl -fsS "\$SERVER_URL/device/\$code/token" 2>/dev/null || true)" + access_token="\$(printf "%s" "\${token_json:-{}}" | json_get accessToken 2>/dev/null || true)" + [ -n "\$access_token" ] && break + i=\$((i + 1)) + sleep 2 + done + [ -n "\${access_token:-}" ] || { + echo "timed out waiting for Authentik login" >&2 + exit 1 + } + access_json="\$(curl -fsS -X POST -H "Authorization: Bearer \$access_token" "\$SERVER_URL/section0/git/access")" + ok="\$(printf "%s" "\$access_json" | json_get ok)" + [ "\$ok" = "True" ] || [ "\$ok" = "true" ] || { + echo "Git access broker did not return credentials:" >&2 + printf "%s\n" "\$access_json" >&2 + exit 1 + } + remote="\$(printf "%s" "\$access_json" | json_get remote)" + git_username="\$(printf "%s" "\$access_json" | json_get git.username)" + git_password="\$(printf "%s" "\$access_json" | json_get git.password)" + author_name="\$(printf "%s" "\$access_json" | json_get author.name)" + author_email="\$(printf "%s" "\$access_json" | json_get author.email)" + [ -n "\$remote" ] && [ -n "\$git_username" ] && [ -n "\$git_password" ] || { + echo "Git access broker response was incomplete" >&2 + exit 1 + } + git -C "\$REPO_DIR" remote set-url origin "\$remote" + git -C "\$REPO_DIR" config user.name "\$author_name" + git -C "\$REPO_DIR" config user.email "\$author_email" + install_git_credential "\$remote" "\$git_username" "\$git_password" + python3 -c 'import json,sys +token=json.loads(sys.argv[1]) +access=json.loads(sys.argv[2]) +print(json.dumps({ + "authenticated": True, + "serverUrl": sys.argv[3], + "authentik": token.get("authentik", {}), + "author": access.get("author", {}), + "remote": access.get("remote", ""), + "credentialUser": access.get("git", {}).get("username", "") +}, indent=2))' "\$token_json" "\$access_json" "\$SERVER_URL" > "\$SESSION_PATH" + chmod 600 "\$SESSION_PATH" + echo "Authenticated: \$author_email" + echo "Git remote: \$remote" + echo "Git user: \$git_username" + echo "Session: \$SESSION_PATH" +} + case "\${1:-help}" in + auth) + case "\${2:-}" in + login) + auth_login + ;; + status) + if [ -f "\$SESSION_PATH" ]; then + cat "\$SESSION_PATH" + else + echo "not authenticated; run: section0-docs auth login" >&2 + exit 1 + fi + ;; + *) + echo "usage: section0-docs auth " >&2 + exit 1 + ;; + esac + ;; configure) current_name="\$(git -C "\$REPO_DIR" config user.name || true)" current_email="\$(git -C "\$REPO_DIR" config user.email || true)" @@ -146,6 +282,7 @@ Remote: $REPO_URL Helper: $COMMAND_PATH Daily flow: + $COMMAND_NAME auth login $COMMAND_NAME doctor $COMMAND_NAME pull # edit Markdown