scripts: per-app SSO first-login scripts (gitea/redmine/discourse)
Each script: full OIDC webflow per app (gitea /user/oauth2/cloudron, redmine /oauth?oauth_provider=1 GET-form, discourse /auth/oidc with CSRF + confirm-page token), TOTP from vault seed, session verify. Ran fleet-wide 2026-09-09: accounts created for all 32 org identities; gitea tokens + redmine/discourse api keys minted via admin levers and stored per identity in the vault. https://projects.knownelement.com/issues/942
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
# Discourse SSO first-login via Cloudron OIDC (openid_connect).
|
||||
# usage: discourse-sso-login.sh <cloudron-username> [vault-item]
|
||||
# Verifies a logged-in session at /my/summary.
|
||||
set -uo pipefail
|
||||
U="${1:?usage: discourse-sso-login.sh <cloudron-username> [vault-item]}"
|
||||
ITEM="${2:-$U Cloudron}"
|
||||
BASE="https://community.turnsys.com"
|
||||
IDP="https://my.knownelement.com"
|
||||
JAR="/tmp/discourse-jar-$U.txt"; rm -f "$JAR"
|
||||
SM() { if [ "$(id -un)" = "TSGCOO" ]; then /data2/TSGCOO/.local/bin/sm "$@" </dev/null; else sudo -u TSGCOO /data2/TSGCOO/.local/bin/sm "$@" </dev/null; fi; }
|
||||
PW=$(SM get "$ITEM" --field password)
|
||||
[ -n "$PW" ] || { echo "FAIL: no password in vault item $ITEM" >&2; exit 1; }
|
||||
SEED=$(SM get "$ITEM" --field totp_seed 2>/dev/null || true)
|
||||
|
||||
# Discourse gates omniauth behind CSRF + a confirm page
|
||||
CSRF=$(curl -sk -b "$JAR" -c "$JAR" -H 'X-Requested-With: XMLHttpRequest' "$BASE/session/csrf" | sed -n 's/.*"csrf":"\([^"]*\)".*/\1/p')
|
||||
[ -n "$CSRF" ] || { echo "FAIL: no csrf token" >&2; exit 2; }
|
||||
CONFIRM_HTML=$(curl -sk -b "$JAR" -c "$JAR" -H "X-CSRF-Token: $CSRF" -H 'X-Requested-With: XMLHttpRequest' "$BASE/auth/oidc")
|
||||
FORMTOKEN=$(printf '%s' "$CONFIRM_HTML" | grep -o 'name="authenticity_token"[^>]*value="[^"]*"' | head -1 | sed 's/.*value="//; s/"$//')
|
||||
[ -n "$FORMTOKEN" ] || { echo "FAIL: no confirm form token" >&2; exit 2; }
|
||||
INT=$(curl -sk -b "$JAR" -c "$JAR" -X POST "$BASE/auth/oidc" \
|
||||
--data-urlencode "authenticity_token=$FORMTOKEN" --data-urlencode "button=continue" \
|
||||
-o /dev/null -w '%{redirect_url}')
|
||||
[ -n "$INT" ] || { echo "FAIL: no oidc redirect" >&2; exit 2; }
|
||||
# follow the provider auth URL down to the interaction
|
||||
INT=$(curl -sk -b "$JAR" -c "$JAR" -o /dev/null -w '%{url_effective}' -L --max-redirs 8 "$INT")
|
||||
case "$INT" in
|
||||
*/openid/interaction/*) : ;;
|
||||
*) echo "FAIL: no interaction reached: $INT" >&2; exit 3 ;;
|
||||
esac
|
||||
UIDPATH=$(printf '%s' "$INT" | grep -o '/openid/interaction/[^?]*')
|
||||
|
||||
BODY="{\"username\":\"$U\",\"password\":\"$PW\""
|
||||
if [ -n "$SEED" ]; then
|
||||
TCODE=$(bash "$(dirname "$0")/totp.sh" "$SEED")
|
||||
BODY="$BODY,\"totpToken\":\"$TCODE\""
|
||||
fi
|
||||
BODY="$BODY}"
|
||||
LOGIN_RESP=$(curl -sk -b "$JAR" -c "$JAR" -X POST "$IDP$UIDPATH/login" -H 'Content-Type: application/json' -d "$BODY")
|
||||
unset BODY
|
||||
case "$LOGIN_RESP" in
|
||||
*redirectTo*) : ;;
|
||||
*) echo "FAIL: login rejected: $LOGIN_RESP" >&2; exit 5 ;;
|
||||
esac
|
||||
RED=$(printf '%s' "$LOGIN_RESP" | sed -n 's/.*"redirectTo":"\([^"]*\)".*/\1/p')
|
||||
|
||||
FINAL=$(curl -sk -b "$JAR" -c "$JAR" -o /dev/null -w '%{url_effective}' -L --max-redirs 8 "$RED")
|
||||
case "$FINAL" in
|
||||
*/openid/interaction/*)
|
||||
CUIDPATH=$(printf '%s' "$FINAL" | grep -o '/openid/interaction/[^?]*')
|
||||
CLOC=$(curl -sk -b "$JAR" -c "$JAR" -D - -o /dev/null -X POST "$IDP$CUIDPATH/confirm" -d '' | grep -i '^location:' | head -1 | tr -d '\r' | sed 's/^[Ll]ocation: //')
|
||||
[ -n "$CLOC" ] || { echo "FAIL: consent confirm produced no redirect" >&2; exit 6; }
|
||||
CLOC=$(printf '%s' "$CLOC" | sed 's|^https//|https://|; s|^http//|http://|')
|
||||
case "$CLOC" in http://*|https://*) : ;; /*) CLOC="$IDP$CLOC" ;; *) CLOC="$IDP/$CLOC" ;; esac
|
||||
FINAL=$(curl -sk -b "$JAR" -c "$JAR" -o /dev/null -w '%{url_effective}' -L --max-redirs 8 "$CLOC")
|
||||
;;
|
||||
esac
|
||||
|
||||
WHO=$(curl -sk -b "$JAR" "$BASE/session/current.json" | sed -n 's/.*"username":"\([^"]*\)".*/\1/p' | head -1)
|
||||
if [ -n "$WHO" ]; then
|
||||
echo "OK: $WHO logged into discourse (session verified)"
|
||||
exit 0
|
||||
fi
|
||||
echo "FAIL: no discourse session, landed: $FINAL" >&2
|
||||
exit 7
|
||||
@@ -0,0 +1,57 @@
|
||||
#!/bin/bash
|
||||
# Gitea SSO first-login via Cloudron OIDC — creates/binds the gitea account.
|
||||
# usage: gitea-sso-login.sh <cloudron-username> [vault-item]
|
||||
# Verifies a logged-in session at /user/settings and echoes the gitea username.
|
||||
set -uo pipefail
|
||||
U="${1:?usage: gitea-sso-login.sh <cloudron-username> [vault-item]}"
|
||||
ITEM="${2:-$U Cloudron}"
|
||||
BASE="https://git.knownelement.com"
|
||||
IDP="https://my.knownelement.com"
|
||||
ENTRY="$BASE/user/oauth2/cloudron"
|
||||
JAR="/tmp/gitea-jar-$U.txt"; rm -f "$JAR"
|
||||
SM() { if [ "$(id -un)" = "TSGCOO" ]; then /data2/TSGCOO/.local/bin/sm "$@" </dev/null; else sudo -u TSGCOO /data2/TSGCOO/.local/bin/sm "$@" </dev/null; fi; }
|
||||
PW=$(SM get "$ITEM" --field password)
|
||||
[ -n "$PW" ] || { echo "FAIL: no password in vault item $ITEM" >&2; exit 1; }
|
||||
SEED=$(SM get "$ITEM" --field totp_seed 2>/dev/null || true)
|
||||
|
||||
INT=$(curl -sk -c "$JAR" -o /dev/null -w '%{url_effective}' -L --max-redirs 8 "$ENTRY")
|
||||
case "$INT" in
|
||||
*/openid/interaction/*) : ;;
|
||||
*) echo "FAIL: no interaction reached: $INT" >&2; exit 3 ;;
|
||||
esac
|
||||
UIDPATH=$(printf '%s' "$INT" | grep -o '/openid/interaction/[^?]*')
|
||||
|
||||
BODY="{\"username\":\"$U\",\"password\":\"$PW\""
|
||||
if [ -n "$SEED" ]; then
|
||||
TCODE=$(bash "$(dirname "$0")/totp.sh" "$SEED")
|
||||
BODY="$BODY,\"totpToken\":\"$TCODE\""
|
||||
fi
|
||||
BODY="$BODY}"
|
||||
LOGIN_RESP=$(curl -sk -b "$JAR" -c "$JAR" -X POST "$IDP$UIDPATH/login" -H 'Content-Type: application/json' -d "$BODY")
|
||||
unset BODY
|
||||
case "$LOGIN_RESP" in
|
||||
*redirectTo*) : ;;
|
||||
*) echo "FAIL: login rejected: $LOGIN_RESP" >&2; exit 5 ;;
|
||||
esac
|
||||
RED=$(printf '%s' "$LOGIN_RESP" | sed -n 's/.*"redirectTo":"\([^"]*\)".*/\1/p')
|
||||
|
||||
FINAL=$(curl -sk -b "$JAR" -c "$JAR" -o /dev/null -w '%{url_effective}' -L --max-redirs 8 "$RED")
|
||||
# consent interaction may appear for app clients
|
||||
case "$FINAL" in
|
||||
*/openid/interaction/*)
|
||||
CUIDPATH=$(printf '%s' "$FINAL" | grep -o '/openid/interaction/[^?]*')
|
||||
CLOC=$(curl -sk -b "$JAR" -c "$JAR" -D - -o /dev/null -X POST "$IDP$CUIDPATH/confirm" -d '' | grep -i '^location:' | head -1 | tr -d '\r' | sed 's/^[Ll]ocation: //')
|
||||
[ -n "$CLOC" ] || { echo "FAIL: consent confirm produced no redirect" >&2; exit 6; }
|
||||
CLOC=$(printf '%s' "$CLOC" | sed 's|^https://|https://|; s|^https//|https://|; s|^http//|http://|')
|
||||
case "$CLOC" in http://*|https://*) : ;; /*) CLOC="$IDP$CLOC" ;; *) CLOC="$IDP/$CLOC" ;; esac
|
||||
FINAL=$(curl -sk -b "$JAR" -c "$JAR" -o /dev/null -w '%{url_effective}' -L --max-redirs 8 "$CLOC")
|
||||
;;
|
||||
esac
|
||||
|
||||
CODE=$(curl -sk -b "$JAR" -o /dev/null -w '%{http_code}' "$BASE/user/settings")
|
||||
if [ "$CODE" = "200" ]; then
|
||||
echo "OK: $U logged into gitea (session verified)"
|
||||
exit 0
|
||||
fi
|
||||
echo "FAIL: no gitea session (settings HTTP $CODE), landed: $FINAL" >&2
|
||||
exit 7
|
||||
@@ -0,0 +1,56 @@
|
||||
#!/bin/bash
|
||||
# Redmine SSO first-login via Cloudron OIDC (redmine_oauth plugin, GET form).
|
||||
# usage: redmine-sso-login.sh <cloudron-username> [vault-item]
|
||||
# Verifies a logged-in session at /my/account.
|
||||
set -uo pipefail
|
||||
U="${1:?usage: redmine-sso-login.sh <cloudron-username> [vault-item]}"
|
||||
ITEM="${2:-$U Cloudron}"
|
||||
BASE="https://projects.knownelement.com"
|
||||
IDP="https://my.knownelement.com"
|
||||
JAR="/tmp/redmine-jar-$U.txt"; rm -f "$JAR"
|
||||
SM() { if [ "$(id -un)" = "TSGCOO" ]; then /data2/TSGCOO/.local/bin/sm "$@" </dev/null; else sudo -u TSGCOO /data2/TSGCOO/.local/bin/sm "$@" </dev/null; fi; }
|
||||
PW=$(SM get "$ITEM" --field password)
|
||||
[ -n "$PW" ] || { echo "FAIL: no password in vault item $ITEM" >&2; exit 1; }
|
||||
SEED=$(SM get "$ITEM" --field totp_seed 2>/dev/null || true)
|
||||
|
||||
# entry: the oauth GET form kick-off
|
||||
INT=$(curl -sk -b "$JAR" -c "$JAR" -o /dev/null -w '%{url_effective}' -L --max-redirs 8 "$BASE/oauth?oauth_provider=1")
|
||||
case "$INT" in
|
||||
*/openid/interaction/*) : ;;
|
||||
*) echo "FAIL: no interaction reached: $INT" >&2; exit 3 ;;
|
||||
esac
|
||||
UIDPATH=$(printf '%s' "$INT" | grep -o '/openid/interaction/[^?]*')
|
||||
|
||||
BODY="{\"username\":\"$U\",\"password\":\"$PW\""
|
||||
if [ -n "$SEED" ]; then
|
||||
TCODE=$(bash "$(dirname "$0")/totp.sh" "$SEED")
|
||||
BODY="$BODY,\"totpToken\":\"$TCODE\""
|
||||
fi
|
||||
BODY="$BODY}"
|
||||
LOGIN_RESP=$(curl -sk -b "$JAR" -c "$JAR" -X POST "$IDP$UIDPATH/login" -H 'Content-Type: application/json' -d "$BODY")
|
||||
unset BODY
|
||||
case "$LOGIN_RESP" in
|
||||
*redirectTo*) : ;;
|
||||
*) echo "FAIL: login rejected: $LOGIN_RESP" >&2; exit 5 ;;
|
||||
esac
|
||||
RED=$(printf '%s' "$LOGIN_RESP" | sed -n 's/.*"redirectTo":"\([^"]*\)".*/\1/p')
|
||||
|
||||
FINAL=$(curl -sk -b "$JAR" -c "$JAR" -o /dev/null -w '%{url_effective}' -L --max-redirs 8 "$RED")
|
||||
case "$FINAL" in
|
||||
*/openid/interaction/*)
|
||||
CUIDPATH=$(printf '%s' "$FINAL" | grep -o '/openid/interaction/[^?]*')
|
||||
CLOC=$(curl -sk -b "$JAR" -c "$JAR" -D - -o /dev/null -X POST "$IDP$CUIDPATH/confirm" -d '' | grep -i '^location:' | head -1 | tr -d '\r' | sed 's/^[Ll]ocation: //')
|
||||
[ -n "$CLOC" ] || { echo "FAIL: consent confirm produced no redirect" >&2; exit 6; }
|
||||
CLOC=$(printf '%s' "$CLOC" | sed 's|^https//|https://|; s|^http//|http://|')
|
||||
case "$CLOC" in http://*|https://*) : ;; /*) CLOC="$IDP$CLOC" ;; *) CLOC="$IDP/$CLOC" ;; esac
|
||||
FINAL=$(curl -sk -b "$JAR" -c "$JAR" -o /dev/null -w '%{url_effective}' -L --max-redirs 8 "$CLOC")
|
||||
;;
|
||||
esac
|
||||
|
||||
CODE=$(curl -sk -b "$JAR" -o /dev/null -w '%{http_code}' "$BASE/my/account")
|
||||
if [ "$CODE" = "200" ]; then
|
||||
echo "OK: $U logged into redmine (session verified)"
|
||||
exit 0
|
||||
fi
|
||||
echo "FAIL: no redmine session (my/account HTTP $CODE), landed: $FINAL" >&2
|
||||
exit 7
|
||||
Reference in New Issue
Block a user