kanidm-setup, all the way: desk groups from the content, no prefix, onboarding that works, the first person, redaktør, and Gitea sign-in
The desk groups are read from the content's qualifies: and named as it names them - this Kanidm is the vel's own - with the old tomtervel_* groups renamed in place. The onboarding account manages every desk group, since adding a member takes that right (measured: 404 without). Sign-in scopes to idm_all_persons. SEED_ADMIN_EMAIL and RESPONSIBLE_GROUP (redaktor) reach portal.env, and bootstrap no longer drops the onboarding token when it rewrites portal.env. A second OAuth2 client lets redaktor sign in to prosjekt.klingenbergbygg.no; on the Gitea host the script adds that sign-in source itself. The login page's own name and logo need the admin account; the script now says so rather than failing with 'Item not found'. .env.example quotes SITE_NAME, which has a space and broke sourcing it. Tested end to end against a local Kanidm 1.11.2, including a rerun and a legacy tomtervel_kasserer with a member. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GT37Z1Xtfd9pUuQtMTg6Yt
This commit is contained in:
+19
-1
@@ -14,11 +14,29 @@ PORTAL_RELEASE=v0.5.3
|
|||||||
# The content this instance serves, and reloads live on every push.
|
# The content this instance serves, and reloads live on every push.
|
||||||
CONTENT_REPO=https://prosjekt.klingenbergbygg.no/tomtervel/questions
|
CONTENT_REPO=https://prosjekt.klingenbergbygg.no/tomtervel/questions
|
||||||
CONTENT_BRANCH=main
|
CONTENT_BRANCH=main
|
||||||
SITE_NAME=Tomter Vel
|
SITE_NAME="Tomter Vel"
|
||||||
|
|
||||||
# Generated once by bootstrap.sh if left empty.
|
# Generated once by bootstrap.sh if left empty.
|
||||||
NATS_PASSWORD=
|
NATS_PASSWORD=
|
||||||
|
|
||||||
|
# The first person in: portal invites this address into the site's most
|
||||||
|
# privileged group (the board, on the vel - the group whose desk may
|
||||||
|
# invite into the most groups) and mails them the one-time link, once.
|
||||||
|
# Everyone else they invite themselves, from their desk.
|
||||||
|
SEED_ADMIN_EMAIL=
|
||||||
|
SEED_ADMIN_NAME=
|
||||||
|
|
||||||
|
# The people who ask the questions: everyone a page names as
|
||||||
|
# responsible gets an account (portal makes it at start and mails them)
|
||||||
|
# and joins this group. Kanidm names are ASCII: redaktor, "Redaktør".
|
||||||
|
RESPONSIBLE_GROUP=redaktor
|
||||||
|
|
||||||
|
# A project Gitea they may sign in to with the same account: kanidm-setup
|
||||||
|
# makes its OAuth2 client (only RESPONSIBLE_GROUP may use it) and, on the
|
||||||
|
# host that runs that Gitea, adds the sign-in source. Empty: no Gitea.
|
||||||
|
GITEA_URL=https://prosjekt.klingenbergbygg.no
|
||||||
|
GITEA_AUTH_NAME=tomtervel
|
||||||
|
|
||||||
# Filled in by bootstrap.sh after it creates the Kanidm client.
|
# Filled in by bootstrap.sh after it creates the Kanidm client.
|
||||||
OAUTH2_CLIENT_ID=tomtervel-portal
|
OAUTH2_CLIENT_ID=tomtervel-portal
|
||||||
OAUTH2_CLIENT_SECRET=
|
OAUTH2_CLIENT_SECRET=
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ kanidm/server.toml
|
|||||||
nats/nats.conf
|
nats/nats.conf
|
||||||
certs/
|
certs/
|
||||||
.kanidm-recovered
|
.kanidm-recovered
|
||||||
|
gitea-oauth.secret
|
||||||
|
|||||||
@@ -64,6 +64,36 @@ Then `sudo systemctl reload caddy`, point the content repo's `lint-and-reload`
|
|||||||
reload step at `nats://127.0.0.1:4223`, and retire the old systemd portal:
|
reload step at `nats://127.0.0.1:4223`, and retire the old systemd portal:
|
||||||
`sudo systemctl disable --now app@tomtervel-portal`.
|
`sudo systemctl disable --now app@tomtervel-portal`.
|
||||||
|
|
||||||
|
## Accounts, all the way
|
||||||
|
|
||||||
|
`kanidm-setup.sh` sets up everything portal needs from this Kanidm,
|
||||||
|
and is safe to rerun:
|
||||||
|
|
||||||
|
- **Desk groups, named as the content names them.** Read from every
|
||||||
|
`qualifies:` under `questions/` in the content repo, so the list
|
||||||
|
cannot drift from the pages. This Kanidm is the vel's own, so there is
|
||||||
|
no prefix: the board's group is `styret`. Groups from before are
|
||||||
|
renamed in place (`tomtervel_styret` → `styret`), members and all.
|
||||||
|
- **Sign-in.** The OAuth2 client's scope map is `idm_all_persons`:
|
||||||
|
everyone here is the vel's, and what they see is decided by their
|
||||||
|
desk groups in the `groups` claim.
|
||||||
|
- **Onboarding.** The `portal-onboarding` service account manages
|
||||||
|
every desk group, so an invite from a desk and a `grants:` can add
|
||||||
|
people to them. Its token goes into `portal.env`.
|
||||||
|
- **The first person in.** `SEED_ADMIN_EMAIL` in `.env`: at start,
|
||||||
|
portal invites them into the most privileged group (the board) and
|
||||||
|
mails the link, once. Everyone else they invite from their desk.
|
||||||
|
- **The people who ask the questions.** Everyone a page names as
|
||||||
|
`responsible` gets an account at start, a mail saying they are listed
|
||||||
|
as responsible for asking that question, and a place in
|
||||||
|
`RESPONSIBLE_GROUP` (`redaktor`). Signed in, they can suggest changes
|
||||||
|
to their own pages.
|
||||||
|
- **The project Gitea.** With `GITEA_URL` set, `redaktor` may sign in
|
||||||
|
to it with the same account: its own OAuth2 client, only for that
|
||||||
|
group. Run on the Gitea host, the script adds the sign-in source
|
||||||
|
itself (and requires the `redaktor` claim there too); elsewhere it
|
||||||
|
leaves the secret in `gitea-oauth.secret` and prints the one command.
|
||||||
|
|
||||||
## People and desks
|
## People and desks
|
||||||
|
|
||||||
Each group in the content (`qualifies:` under `questions/`) is a
|
Each group in the content (`qualifies:` under `questions/`) is a
|
||||||
@@ -168,6 +198,11 @@ older server does not have, so `system domain set-displayname` and
|
|||||||
about versions. The CLI warns on every call; the warning is worth
|
about versions. The CLI warns on every call; the warning is worth
|
||||||
reading.
|
reading.
|
||||||
|
|
||||||
|
The same "Item not found" also comes back when the versions do match
|
||||||
|
and the account is `idm_admin`: the instance's own name and logo are
|
||||||
|
system settings only `admin` may change. `kanidm-setup.sh` sets them
|
||||||
|
when an `admin` session exists and says so when it does not.
|
||||||
|
|
||||||
The host's CLI comes from pacman and moves on its own, so the image is
|
The host's CLI comes from pacman and moves on its own, so the image is
|
||||||
what to bump: `image: kanidm/server:<version>` in `compose.yml`, then
|
what to bump: `image: kanidm/server:<version>` in `compose.yml`, then
|
||||||
`podman compose up -d kanidm`.
|
`podman compose up -d kanidm`.
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ fi
|
|||||||
# Rendered configs (gitignored).
|
# Rendered configs (gitignored).
|
||||||
sed "s|\${ID_HOST}|$ID_HOST|g" kanidm/server.toml.tpl > kanidm/server.toml
|
sed "s|\${ID_HOST}|$ID_HOST|g" kanidm/server.toml.tpl > kanidm/server.toml
|
||||||
sed "s|\${NATS_PASSWORD}|$NATS_PASSWORD|g" nats/nats.conf.tpl > nats/nats.conf
|
sed "s|\${NATS_PASSWORD}|$NATS_PASSWORD|g" nats/nats.conf.tpl > nats/nats.conf
|
||||||
|
# The onboarding token is written by kanidm-setup.sh, once; a rerun of
|
||||||
|
# this script must not lose it, or every invite fails closed again.
|
||||||
|
kept_token=$(grep '^KANIDM_API_TOKEN=' portal.env 2>/dev/null | head -1 || true)
|
||||||
cat > portal.env <<EOF
|
cat > portal.env <<EOF
|
||||||
NATS_URL=nats://portal:$NATS_PASSWORD@nats:4222
|
NATS_URL=nats://portal:$NATS_PASSWORD@nats:4222
|
||||||
KANIDM_URL=https://$ID_HOST
|
KANIDM_URL=https://$ID_HOST
|
||||||
@@ -31,7 +34,11 @@ COOKIE_SECURE=true
|
|||||||
CONTENT_REPO=$CONTENT_REPO
|
CONTENT_REPO=$CONTENT_REPO
|
||||||
CONTENT_BRANCH=$CONTENT_BRANCH
|
CONTENT_BRANCH=$CONTENT_BRANCH
|
||||||
SITE_NAME=$SITE_NAME
|
SITE_NAME=$SITE_NAME
|
||||||
|
SEED_ADMIN_EMAIL=${SEED_ADMIN_EMAIL:-}
|
||||||
|
SEED_ADMIN_NAME=${SEED_ADMIN_NAME:-}
|
||||||
|
RESPONSIBLE_GROUP=${RESPONSIBLE_GROUP:-}
|
||||||
EOF
|
EOF
|
||||||
|
[ -n "$kept_token" ] && echo "$kept_token" >> portal.env
|
||||||
chmod 600 portal.env
|
chmod 600 portal.env
|
||||||
|
|
||||||
# Kanidm's internal certificate: Caddy holds the public one.
|
# Kanidm's internal certificate: Caddy holds the public one.
|
||||||
|
|||||||
+145
-64
@@ -1,105 +1,186 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# The portal's OAuth2 client and one Kanidm group per desk, mapped into
|
# Everything portal needs from this Kanidm, all the way: the OAuth2
|
||||||
# the `groups` claim under the names the pages use in `qualifies`.
|
# client people sign in through, one group per desk named exactly as
|
||||||
# Portal reads that claim at login (portal src/auth.rs). Rerunnable.
|
# the content names it, and the service account portal onboards people
|
||||||
|
# with - so that inviting someone from a desk, a decision that grants a
|
||||||
|
# group, and the first person in (SEED_ADMIN_EMAIL) all simply work.
|
||||||
|
# Rerunnable: each step skips what is already there.
|
||||||
#
|
#
|
||||||
# Logs in first (interactive, idm_admin's password from bootstrap.sh),
|
# Logs in first if needed (interactive, idm_admin's password from
|
||||||
# then writes the client secret into .env and portal.env and restarts
|
# bootstrap.sh), writes the client secret and the onboarding token into
|
||||||
# the portal. Needs the kanidm CLI on this machine.
|
# portal.env, and restarts portal. Needs the kanidm CLI and curl.
|
||||||
set -eu
|
set -eu
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
. ./.env
|
. ./.env
|
||||||
K="kanidm -D idm_admin -H https://$ID_HOST"
|
K="kanidm -D idm_admin -H https://$ID_HOST"
|
||||||
C=$OAUTH2_CLIENT_ID
|
C=$OAUTH2_CLIENT_ID
|
||||||
|
SA=portal-onboarding
|
||||||
|
|
||||||
# Already signed in? Then do not ask again: this script is meant to be
|
# Already signed in? Then do not ask again: this script is meant to be
|
||||||
# rerunnable, and the session outlives a single run.
|
# rerunnable, and the session outlives a single run.
|
||||||
$K self whoami >/dev/null 2>&1 || $K login
|
$K self whoami >/dev/null 2>&1 || $K login
|
||||||
has_client() { $K system oauth2 get "$1" 2>/dev/null | grep -q '^name:'; }
|
has_client() { $K system oauth2 get "$1" 2>/dev/null | grep -q '^name:'; }
|
||||||
has_group() { $K group get "$1" 2>/dev/null | grep -q '^name:'; }
|
has_group() { $K group get "$1" 2>/dev/null | grep -q '^name:'; }
|
||||||
|
has_account() { $K service-account get "$1" 2>/dev/null | grep -q '^name:'; }
|
||||||
|
|
||||||
has_client $C || $K system oauth2 create $C "$SITE_NAME" "https://$PORTAL_HOST"
|
# The desk groups are whatever the content gates pages on: every
|
||||||
$K system oauth2 add-redirect-url $C "https://$PORTAL_HOST/auth/callback" || true
|
# `qualifies:` under questions/ in the content repo. Read from the repo,
|
||||||
|
# so this list cannot drift from the pages. This Kanidm is the vel's
|
||||||
|
# own, so a group is called exactly what the content calls it.
|
||||||
|
host=$(printf '%s' "$CONTENT_REPO" | sed -E 's|^(https?://[^/]+)/.*|\1|')
|
||||||
|
path=$(printf '%s' "$CONTENT_REPO" | sed -E 's|^https?://[^/]+/||')
|
||||||
|
tree=$(curl -sf "$host/api/v1/repos/$path/git/trees/$CONTENT_BRANCH?recursive=true&per_page=1000") || {
|
||||||
|
echo "could not read the content repo's file list ($CONTENT_REPO)" >&2; exit 1; }
|
||||||
|
groups=$(printf '%s' "$tree" | grep -oE '"path":"questions/[^"]+\.yaml"' | sed -E 's/^"path":"//; s/"$//' | while read -r f; do
|
||||||
|
curl -sf "$CONTENT_REPO/raw/branch/$CONTENT_BRANCH/$f" | sed -nE 's/^qualifies:[[:space:]]*"?([a-z0-9_-]+)"?[[:space:]]*$/\1/p'
|
||||||
|
done | sort -u)
|
||||||
|
[ -n "$groups" ] || { echo "the content gates no page on a group - nothing for a desk to sign in to" >&2; exit 1; }
|
||||||
|
echo "desk groups from the content: $(echo $groups)"
|
||||||
|
|
||||||
# The desk groups: every group the content gates a directory on. Keep
|
# The onboarding account. In idm_people_on_boarding it may create a
|
||||||
# this list equal to the `qualifies` values under questions/.
|
# person and issue a first credential reset and nothing else, so its
|
||||||
has_group tomtervel_members || $K group create tomtervel_members
|
# token cannot touch anyone's existing credentials; idm_people_pii_read
|
||||||
for g in kasserer styret komiteer nabohjelp arrangementer elvesti horingsinstans lekeplasser miljogate pendlerforhold trafikk; do
|
# lets an invite find someone who already has an account by their
|
||||||
has_group tomtervel_$g || $K group create tomtervel_$g
|
# email and add them instead of making a second account. Portal creates
|
||||||
$K group add-members tomtervel_members tomtervel_$g
|
# the person with their address in the same call, because this account
|
||||||
|
# may create but not modify.
|
||||||
|
if ! has_account $SA; then
|
||||||
|
$K service-account create $SA "Portal onboarding" idm_admin
|
||||||
|
fi
|
||||||
|
$K group add-members idm_people_on_boarding $SA >/dev/null
|
||||||
|
$K group add-members idm_people_pii_read $SA >/dev/null || true
|
||||||
|
|
||||||
|
# One group per desk, managed by the onboarding account: adding a
|
||||||
|
# member to a group takes the right to manage that group, and a desk
|
||||||
|
# invite or a `grants:` does exactly that. Groups made before this
|
||||||
|
# script dropped its old `tomtervel_` prefix are renamed in place, so
|
||||||
|
# their members and claim maps come along.
|
||||||
|
for g in $groups; do
|
||||||
|
if ! has_group "$g" && has_group "tomtervel_$g"; then
|
||||||
|
$K group rename "tomtervel_$g" "$g"
|
||||||
|
fi
|
||||||
|
has_group "$g" || $K group create "$g" $SA
|
||||||
|
$K group set-entry-manager "$g" $SA >/dev/null
|
||||||
done
|
done
|
||||||
|
|
||||||
# Portal asks for openid, profile and email; groups arrive as a claim.
|
# The people who ask the questions: everyone a page names as
|
||||||
$K system oauth2 update-scope-map $C tomtervel_members openid profile email
|
# responsible. Portal puts them here itself at start (RESPONSIBLE_GROUP);
|
||||||
for g in kasserer styret komiteer nabohjelp arrangementer elvesti horingsinstans lekeplasser miljogate pendlerforhold trafikk; do
|
# the group only has to exist and be portal's to fill. Kanidm names are
|
||||||
$K system oauth2 update-claim-map $C groups tomtervel_$g $g
|
# ASCII, so "Redaktør" is its description.
|
||||||
|
if [ -n "${RESPONSIBLE_GROUP:-}" ]; then
|
||||||
|
has_group "$RESPONSIBLE_GROUP" || $K group create "$RESPONSIBLE_GROUP" $SA
|
||||||
|
$K group set-entry-manager "$RESPONSIBLE_GROUP" $SA >/dev/null
|
||||||
|
$K group set-description "$RESPONSIBLE_GROUP" "${RESPONSIBLE_GROUP_LABEL:-Redaktør}: står som ansvarlig for et spørsmål på siden" >/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The OAuth2 client. Everyone in this Kanidm is the vel's, so anyone
|
||||||
|
# may sign in; what they see is decided by their desk groups, which
|
||||||
|
# arrive in the `groups` claim under their own names.
|
||||||
|
has_client $C || $K system oauth2 create $C "$SITE_NAME" "https://$PORTAL_HOST"
|
||||||
|
$K system oauth2 add-redirect-url $C "https://$PORTAL_HOST/auth/callback" 2>/dev/null || true
|
||||||
|
$K system oauth2 update-scope-map $C idm_all_persons openid profile email
|
||||||
|
for g in $groups; do
|
||||||
|
$K system oauth2 update-claim-map $C groups "$g" "$g"
|
||||||
done
|
done
|
||||||
$K system oauth2 update-claim-map-join $C groups array
|
$K system oauth2 update-claim-map-join $C groups array
|
||||||
|
# The old umbrella group the scope map used to name, if this Kanidm
|
||||||
|
# still has it: idm_all_persons does its job now.
|
||||||
|
if has_group tomtervel_members; then
|
||||||
|
$K system oauth2 delete-scope-map $C tomtervel_members 2>/dev/null || true
|
||||||
|
$K group delete tomtervel_members
|
||||||
|
fi
|
||||||
|
|
||||||
# What a member sees when they land on the login page. Out of the box
|
# What a member sees when they land on the login page. Out of the box
|
||||||
# Kanidm says "kanidm" and shows its own mark, which tells a neighbour
|
# Kanidm says "kanidm" and shows its own mark, which tells a neighbour
|
||||||
# nothing about whose site they are signing in to - and a login page
|
# nothing about whose site they are signing in to. The logo comes from
|
||||||
# that looks like it belongs to no one is the kind of thing people
|
# the content repo, the same file site.yaml uses as the favicon.
|
||||||
# rightly hesitate over.
|
|
||||||
#
|
#
|
||||||
# The logo comes from the content repo, because that is already where
|
# The client's name and logo are idm_admin's to set. The instance's own
|
||||||
# this site's identity lives: the same file site.yaml uses as the
|
# (the login page's heading and mark) are system settings only `admin`
|
||||||
# favicon. Branding follows the content, not this repo.
|
# may change - idm_admin gets a bare "Item not found" - so that half
|
||||||
$K system domain set-displayname "$SITE_NAME" || true
|
# runs when an admin session exists, and otherwise says what to run.
|
||||||
$K system oauth2 set-displayname $C "$SITE_NAME" || true
|
A="kanidm -D admin -H https://$ID_HOST"
|
||||||
|
$K system oauth2 set-displayname $C "$SITE_NAME" >/dev/null || true
|
||||||
logo=$(mktemp --suffix=.svg)
|
logo=$(mktemp --suffix=.svg)
|
||||||
if curl -sfL "$CONTENT_REPO/raw/branch/$CONTENT_BRANCH/images/logo.svg" -o "$logo" && [ -s "$logo" ]; then
|
if curl -sfL "$CONTENT_REPO/raw/branch/$CONTENT_BRANCH/images/logo.svg" -o "$logo" && [ -s "$logo" ]; then
|
||||||
$K system domain set-image "$logo" svg || echo "note: could not set the instance logo"
|
$K system oauth2 set-image $C "$logo" svg >/dev/null || echo "note: could not set the client logo"
|
||||||
$K system oauth2 set-image $C "$logo" svg || echo "note: could not set the client logo"
|
|
||||||
else
|
else
|
||||||
echo "note: no images/logo.svg in the content repo - leaving the default mark"
|
echo "note: no images/logo.svg in the content repo - leaving the default mark"
|
||||||
|
: > "$logo"
|
||||||
|
fi
|
||||||
|
if $A self whoami >/dev/null 2>&1; then
|
||||||
|
$A system domain set-displayname "$SITE_NAME" >/dev/null || echo "note: could not set the login page's name"
|
||||||
|
[ -s "$logo" ] && { $A system domain set-image "$logo" svg >/dev/null || echo "note: could not set the login page's logo"; }
|
||||||
|
else
|
||||||
|
echo "note: the login page's own name and logo need the admin account (bootstrap.sh printed its password):"
|
||||||
|
echo " $A login # then rerun this script"
|
||||||
fi
|
fi
|
||||||
rm -f "$logo"
|
rm -f "$logo"
|
||||||
|
|
||||||
secret=$($K system oauth2 show-basic-secret $C 2>/dev/null | tail -1)
|
# Sign-in to a project Gitea for the people who ask the questions: its
|
||||||
sed -i "s|^OAUTH2_CLIENT_SECRET=.*|OAUTH2_CLIENT_SECRET=$secret|" .env portal.env
|
# own OAuth2 client, which only RESPONSIBLE_GROUP may use, carrying that
|
||||||
|
# group in the `groups` claim so Gitea can require it as well. Gitea
|
||||||
# Onboarding from a desk. A committee inviting a neighbour, and any
|
# does not do PKCE (Kanidm's own Gitea example turns it off), and short
|
||||||
# state whose `grants:` makes someone a member, both go through Kanidm
|
# usernames keep Gitea accounts "kari", not "kari@id.vel...".
|
||||||
# as this service account - not as a person, and not as an admin. It is
|
if [ -n "${GITEA_URL:-}" ] && [ -n "${RESPONSIBLE_GROUP:-}" ]; then
|
||||||
# in idm_people_on_boarding, which may create people and issue a first
|
G=${GITEA_OAUTH2_CLIENT_ID:-prosjekt}
|
||||||
# credential reset and nothing else, so the token cannot touch anyone's
|
N=${GITEA_AUTH_NAME:-tomtervel}
|
||||||
# existing credentials. idm_people_pii_read lets an invite find someone
|
has_client $G || $K system oauth2 create $G "${GITEA_LABEL:-Prosjekt}" "$GITEA_URL/user/login"
|
||||||
# who already has an account by their email, and add them to the group
|
$K system oauth2 add-redirect-url $G "$GITEA_URL/user/oauth2/$N/callback" 2>/dev/null || true
|
||||||
# instead of making a second account for the same person.
|
$K system oauth2 update-scope-map $G "$RESPONSIBLE_GROUP" openid profile email >/dev/null
|
||||||
#
|
$K system oauth2 update-claim-map $G groups "$RESPONSIBLE_GROUP" "$RESPONSIBLE_GROUP" >/dev/null
|
||||||
# The token is shown once, by Kanidm, at creation. Written straight into
|
$K system oauth2 update-claim-map-join $G groups array >/dev/null
|
||||||
# the env files here and never printed.
|
$K system oauth2 warning-insecure-client-disable-pkce $G >/dev/null
|
||||||
SA=portal-onboarding
|
$K system oauth2 prefer-short-username $G >/dev/null
|
||||||
if ! $K service-account get $SA >/dev/null 2>&1; then
|
gsecret=$($K system oauth2 show-basic-secret $G 2>/dev/null | tail -1)
|
||||||
$K service-account create $SA "Portal onboarding" idm_admin
|
discover="https://$ID_HOST/oauth2/openid/$G/.well-known/openid-configuration"
|
||||||
$K group add-members idm_people_on_boarding $SA
|
# On the host that runs that Gitea, add or update its source here;
|
||||||
$K group add-members idm_people_pii_read $SA || true
|
# anywhere else, leave the secret in a file only this user can read.
|
||||||
|
if command -v gitea >/dev/null 2>&1 && sudo -n -u gitea true 2>/dev/null; then
|
||||||
|
GT="sudo -n -u gitea gitea --config ${GITEA_CONFIG:-/etc/gitea/app.ini} admin auth"
|
||||||
|
id=$($GT list 2>/dev/null | awk -v n="$N" '$2 == n { print $1 }')
|
||||||
|
if [ -n "$id" ]; then
|
||||||
|
$GT update-oauth --id "$id" --key "$G" --secret "$gsecret" --auto-discover-url "$discover" \
|
||||||
|
--required-claim-name groups --required-claim-value "$RESPONSIBLE_GROUP" --scopes "openid profile email" >/dev/null
|
||||||
|
echo "Gitea sign-in source '$N' updated"
|
||||||
|
else
|
||||||
|
$GT add-oauth --name "$N" --provider openidConnect --key "$G" --secret "$gsecret" --auto-discover-url "$discover" \
|
||||||
|
--required-claim-name groups --required-claim-value "$RESPONSIBLE_GROUP" --scopes "openid profile email" >/dev/null
|
||||||
|
echo "Gitea sign-in source '$N' added: $GITEA_URL/user/login has a 'Sign in with $N' button"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
umask 077; printf '%s\n' "$gsecret" > gitea-oauth.secret
|
||||||
|
echo "note: no gitea CLI here - on the Gitea host, add the source with the secret in gitea-oauth.secret:"
|
||||||
|
echo " gitea admin auth add-oauth --name $N --provider openidConnect --key $G --secret \"\$(cat gitea-oauth.secret)\" \\"
|
||||||
|
echo " --auto-discover-url $discover --required-claim-name groups --required-claim-value $RESPONSIBLE_GROUP --scopes 'openid profile email'"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Secrets into portal.env, never printed. The token is shown once, by
|
||||||
|
# Kanidm, at creation; a rerun keeps the one portal.env already has.
|
||||||
|
setenv() { if grep -q "^$1=" portal.env; then sed -i "s|^$1=.*|$1=$2|" portal.env; else printf '%s=%s\n' "$1" "$2" >> portal.env; fi; }
|
||||||
|
secret=$($K system oauth2 show-basic-secret $C 2>/dev/null | tail -1)
|
||||||
|
sed -i "s|^OAUTH2_CLIENT_SECRET=.*|OAUTH2_CLIENT_SECRET=$secret|" .env
|
||||||
|
setenv OAUTH2_CLIENT_SECRET "$secret"
|
||||||
if ! grep -q '^KANIDM_API_TOKEN=.' portal.env 2>/dev/null; then
|
if ! grep -q '^KANIDM_API_TOKEN=.' portal.env 2>/dev/null; then
|
||||||
token=$($K service-account api-token generate $SA "portal" --readwrite | tail -1)
|
token=$($K service-account api-token generate $SA "portal" --readwrite 2>/dev/null | tail -1)
|
||||||
if [ -n "$token" ]; then
|
if [ -n "$token" ]; then
|
||||||
grep -q '^KANIDM_API_TOKEN=' portal.env \
|
setenv KANIDM_API_TOKEN "$token"
|
||||||
&& sed -i "s|^KANIDM_API_TOKEN=.*|KANIDM_API_TOKEN=$token|" portal.env \
|
|
||||||
|| printf 'KANIDM_API_TOKEN=%s\n' "$token" >> portal.env
|
|
||||||
echo "onboarding token written to portal.env"
|
echo "onboarding token written to portal.env"
|
||||||
else
|
else
|
||||||
# Loudly, and with whatever the CLI said: the first version of this
|
|
||||||
# passed `--rw` for a flag that is spelled `--readwrite`, swallowed
|
|
||||||
# the error, and reported a bare warning that said nothing about
|
|
||||||
# why. A step that leaves invites broken should not be quiet about
|
|
||||||
# how it failed.
|
|
||||||
echo "warning: could not generate the onboarding token - invites will fail closed until it is set" >&2
|
echo "warning: could not generate the onboarding token - invites will fail closed until it is set" >&2
|
||||||
echo " retry by hand: $K service-account api-token generate $SA portal --readwrite" >&2
|
echo " retry by hand: $K service-account api-token generate $SA portal --readwrite" >&2
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
# The first person in, if .env names one: portal invites them into the
|
||||||
|
# most privileged group on its next start, once, and mails the link.
|
||||||
|
if [ -n "${SEED_ADMIN_EMAIL:-}" ]; then setenv SEED_ADMIN_EMAIL "$SEED_ADMIN_EMAIL"; fi
|
||||||
|
if [ -n "${SEED_ADMIN_NAME:-}" ]; then setenv SEED_ADMIN_NAME "$SEED_ADMIN_NAME"; fi
|
||||||
|
if [ -n "${RESPONSIBLE_GROUP:-}" ]; then setenv RESPONSIBLE_GROUP "$RESPONSIBLE_GROUP"; fi
|
||||||
|
|
||||||
podman compose restart portal
|
podman compose up -d portal
|
||||||
echo "client $C configured; portal restarted with its secret"
|
echo "portal restarted: signs in through $C, onboards through $SA, desks: $(echo $groups)"
|
||||||
|
if [ -n "${SEED_ADMIN_EMAIL:-}" ]; then echo "the first person, $SEED_ADMIN_EMAIL, gets their invite from portal now (once)"; fi
|
||||||
echo
|
echo
|
||||||
echo "Give people their desk (membership is read at login):"
|
echo "Everyone after that is invited from a desk. By hand, if ever needed:"
|
||||||
echo " $K group add-members tomtervel_styret <person>"
|
echo " $K group add-members styret <person>"
|
||||||
echo "Create a person:"
|
|
||||||
echo " $K person create <name> '<Display Name>' && $K person update <name> --mail <email>"
|
|
||||||
echo " $K person credential create-reset-token <name>"
|
|
||||||
|
|||||||
Reference in New Issue
Block a user