Merge pull request #390 from outis1one/claude/frigate-authelia-openid-0l1htj
Claude/frigate authelia openid 0l1htj
This commit is contained in:
+352
-50
@@ -244,10 +244,13 @@ install_authelia() {
|
|||||||
echo " 11) Un-protect a site (undoes option 10 for one site)"
|
echo " 11) Un-protect a site (undoes option 10 for one site)"
|
||||||
echo " 12) Export/import user data (backup accounts + 2FA before a reinstall,"
|
echo " 12) Export/import user data (backup accounts + 2FA before a reinstall,"
|
||||||
echo " or restore a previous export)"
|
echo " or restore a previous export)"
|
||||||
|
echo " 13) Make sure admins always have access to every site (old and new —"
|
||||||
|
echo " safe to re-run any time)"
|
||||||
|
echo " 14) Rename an outside-access group (e.g. \"customer1\" -> \"acme-corp\")"
|
||||||
echo " 0) Leave as-is / exit"
|
echo " 0) Leave as-is / exit"
|
||||||
echo ""
|
echo ""
|
||||||
local EXISTING_CHOICE=""
|
local EXISTING_CHOICE=""
|
||||||
prompt_text " Choice [1-12, 0 to exit]:" "0" EXISTING_CHOICE
|
prompt_text " Choice [1-14, 0 to exit]:" "0" EXISTING_CHOICE
|
||||||
case "$EXISTING_CHOICE" in
|
case "$EXISTING_CHOICE" in
|
||||||
1)
|
1)
|
||||||
add_authelia_domain
|
add_authelia_domain
|
||||||
@@ -296,6 +299,14 @@ install_authelia() {
|
|||||||
_authelia_export_import_users_menu
|
_authelia_export_import_users_menu
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
|
13)
|
||||||
|
_authelia_ensure_admin_access_everywhere
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
14)
|
||||||
|
_authelia_rename_group
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
0|*)
|
0|*)
|
||||||
echo " Keeping existing Authelia. (Edit config/users.yml then: cd $AUTHELIA_DIR && docker compose restart authelia)"
|
echo " Keeping existing Authelia. (Edit config/users.yml then: cd $AUTHELIA_DIR && docker compose restart authelia)"
|
||||||
return 0
|
return 0
|
||||||
@@ -451,6 +462,14 @@ authentication_backend:
|
|||||||
access_control:
|
access_control:
|
||||||
default_policy: deny
|
default_policy: deny
|
||||||
rules:
|
rules:
|
||||||
|
# Admins always match first, on every domain this instance protects — this
|
||||||
|
# rule must stay above any per-service scoping rule (old or new) below it,
|
||||||
|
# or a group's "deny elsewhere" rule could accidentally catch an admin.
|
||||||
|
# add_authelia_domain and _authelia_scope_access both preserve this
|
||||||
|
# ordering automatically — see _authelia_ensure_admin_bypass.
|
||||||
|
- domain: "*.${AUTHELIA_DOMAIN}"
|
||||||
|
subject: "group:admins"
|
||||||
|
policy: two_factor
|
||||||
- domain: "*.${AUTHELIA_DOMAIN}"
|
- domain: "*.${AUTHELIA_DOMAIN}"
|
||||||
policy: two_factor
|
policy: two_factor
|
||||||
|
|
||||||
@@ -733,9 +752,16 @@ add_authelia_domain() {
|
|||||||
NEW_PORTAL_DOMAIN="${NEW_PORTAL_SUBDOMAIN}.${NEW_DOMAIN}"
|
NEW_PORTAL_DOMAIN="${NEW_PORTAL_SUBDOMAIN}.${NEW_DOMAIN}"
|
||||||
|
|
||||||
# ── access_control.rules: insert right after "rules:" ────────────────────
|
# ── access_control.rules: insert right after "rules:" ────────────────────
|
||||||
|
# The admin-bypass rule goes first, this domain's own catch-all second —
|
||||||
|
# both inserted together in one shot so a fresh domain never has a window
|
||||||
|
# where the catch-all exists without the bypass above it. See
|
||||||
|
# _authelia_ensure_admin_bypass for why this ordering has to hold.
|
||||||
awk -v domain="$NEW_DOMAIN" '
|
awk -v domain="$NEW_DOMAIN" '
|
||||||
{ print }
|
{ print }
|
||||||
/^ rules:$/ && !done {
|
/^ rules:$/ && !done {
|
||||||
|
print " - domain: \"*." domain "\""
|
||||||
|
print " subject: \"group:admins\""
|
||||||
|
print " policy: two_factor"
|
||||||
print " - domain: \"*." domain "\""
|
print " - domain: \"*." domain "\""
|
||||||
print " policy: two_factor"
|
print " policy: two_factor"
|
||||||
done=1
|
done=1
|
||||||
@@ -861,7 +887,10 @@ remove_authelia_domain() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local -a apex_domains
|
local -a apex_domains
|
||||||
mapfile -t apex_domains < <(grep -oE '^ - domain: "\*\.[^"]+"' "$CONFIG_FILE" | sed -E 's/^ - domain: "\*\.(.+)"$/\1/')
|
# Each apex domain now has two rules sharing this exact domain string —
|
||||||
|
# its admin-bypass rule and its plain catch-all (see
|
||||||
|
# _authelia_ensure_admin_bypass) — so dedupe or it'd list every domain twice.
|
||||||
|
mapfile -t apex_domains < <(grep -oE '^ - domain: "\*\.[^"]+"' "$CONFIG_FILE" | sed -E 's/^ - domain: "\*\.(.+)"$/\1/' | awk '!seen[$0]++')
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
if [ "${#apex_domains[@]}" -eq 0 ]; then
|
if [ "${#apex_domains[@]}" -eq 0 ]; then
|
||||||
@@ -913,6 +942,23 @@ remove_authelia_domain() {
|
|||||||
prompt_yn " Continue? (y/n):" "n" CONFIRM_RM
|
prompt_yn " Continue? (y/n):" "n" CONFIRM_RM
|
||||||
[[ "$CONFIRM_RM" =~ ^[Yy]$ ]] || { log_info "Cancelled — nothing changed."; return 0; }
|
[[ "$CONFIRM_RM" =~ ^[Yy]$ ]] || { log_info "Cancelled — nothing changed."; return 0; }
|
||||||
|
|
||||||
|
# ── access_control.rules: remove this domain's admin-bypass rule first ────
|
||||||
|
# (3 lines: domain/subject/policy — see _authelia_ensure_admin_bypass) —
|
||||||
|
# has to run before the plain catch-all removal below, which only knows
|
||||||
|
# how to strip a 2-line domain/policy pair and would otherwise leave this
|
||||||
|
# rule's own "policy:" line orphaned, the exact corruption class this
|
||||||
|
# instance was repaired from once already.
|
||||||
|
awk -v domain="$RM_DOMAIN" '
|
||||||
|
BEGIN { skip=0 }
|
||||||
|
skip > 0 { skip--; next }
|
||||||
|
$0 == " - domain: \"*." domain "\"" {
|
||||||
|
getline nxt
|
||||||
|
if (nxt == " subject: \"group:admins\"") { skip=1; next }
|
||||||
|
print; print nxt; next
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
' "$CONFIG_FILE" > "$CONFIG_FILE.tmp" && mv "$CONFIG_FILE.tmp" "$CONFIG_FILE"
|
||||||
|
|
||||||
# ── access_control.rules: remove the "- domain: "*.X"" + "policy: ..." pair ──
|
# ── access_control.rules: remove the "- domain: "*.X"" + "policy: ..." pair ──
|
||||||
awk -v domain="$RM_DOMAIN" '
|
awk -v domain="$RM_DOMAIN" '
|
||||||
BEGIN { skip=0 }
|
BEGIN { skip=0 }
|
||||||
@@ -1543,7 +1589,85 @@ _authelia_create_user_noninteractive() {
|
|||||||
# domain that's already scoped just report the existing group instead of
|
# domain that's already scoped just report the existing group instead of
|
||||||
# duplicating rules.
|
# duplicating rules.
|
||||||
#
|
#
|
||||||
|
# Idempotent: makes sure a "subject: group:admins" rule exists for $domain's
|
||||||
|
# access_control.rules, above every other rule for that domain, then returns
|
||||||
|
# the 1-indexed line number its "policy:" line ended up on (via ADMIN_BYPASS_LINE,
|
||||||
|
# not stdout — awk already uses stdout for the rewritten file). Callers that
|
||||||
|
# insert a NEW rule for this domain (_authelia_scope_access's deny-elsewhere
|
||||||
|
# rule in particular) must insert it AFTER that line, never at the literal
|
||||||
|
# top of "rules:" — Authelia takes the first matching rule, so a deny rule
|
||||||
|
# landing above the admin-bypass rule would catch an admin who's also (by
|
||||||
|
# mistake, or by some future feature) a member of the group being denied.
|
||||||
|
# install_authelia and add_authelia_domain both bake this rule in directly
|
||||||
|
# at creation time instead of calling this — it's for retrofitting an apex
|
||||||
|
# domain that predates this (see _authelia_ensure_admin_access_everywhere,
|
||||||
|
# the menu-driven bulk version of this for an existing install) and for
|
||||||
|
# _authelia_scope_access's own just-in-time safety net.
|
||||||
|
_authelia_ensure_admin_bypass() {
|
||||||
|
local config_file="$1" domain="$2"
|
||||||
|
ADMIN_BYPASS_LINE=""
|
||||||
|
ADMIN_BYPASS_ADDED="false"
|
||||||
|
|
||||||
|
local result
|
||||||
|
result="$(awk -v domain="$domain" '
|
||||||
|
{ lines[NR] = $0 }
|
||||||
|
END {
|
||||||
|
for (i = 1; i < NR; i++) {
|
||||||
|
if (lines[i] == " - domain: \"*." domain "\"" && lines[i+1] == " subject: \"group:admins\"") {
|
||||||
|
print i + 2
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "$config_file")"
|
||||||
|
|
||||||
|
if [ -n "$result" ]; then
|
||||||
|
ADMIN_BYPASS_LINE="$result"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
ADMIN_BYPASS_ADDED="true"
|
||||||
|
awk -v domain="$domain" '
|
||||||
|
{ print }
|
||||||
|
/^ rules:$/ && !done {
|
||||||
|
print " - domain: \"*." domain "\""
|
||||||
|
print " subject: \"group:admins\""
|
||||||
|
print " policy: two_factor"
|
||||||
|
done=1
|
||||||
|
}
|
||||||
|
' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
|
||||||
|
chown 1000:1000 "$config_file" 2>/dev/null || true
|
||||||
|
|
||||||
|
# The block we just inserted is always lines 2-4 counting from " rules:"
|
||||||
|
# (which awk just placed the block right after) — but simplest and least
|
||||||
|
# fragile is to just re-run the same lookup now that it exists.
|
||||||
|
ADMIN_BYPASS_LINE="$(awk -v domain="$domain" '
|
||||||
|
{ lines[NR] = $0 }
|
||||||
|
END {
|
||||||
|
for (i = 1; i < NR; i++) {
|
||||||
|
if (lines[i] == " - domain: \"*." domain "\"" && lines[i+1] == " subject: \"group:admins\"") {
|
||||||
|
print i + 2
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "$config_file")"
|
||||||
|
}
|
||||||
|
|
||||||
# Args: SERVICE_ID DOMAIN
|
# Args: SERVICE_ID DOMAIN
|
||||||
|
#
|
||||||
|
# service_id is only ever used as the SUGGESTED group name when creating a
|
||||||
|
# brand-new group — the actual group is whatever the user picks or types
|
||||||
|
# below, so the same group (e.g. "customer1-only") can be attached to
|
||||||
|
# several different sites over time instead of getting a fresh
|
||||||
|
# "<service_id>-only" group every call. The "-only" suffix itself is kept
|
||||||
|
# internally (never shown to the user, who just sees "customer1") because
|
||||||
|
# it's load-bearing elsewhere: _authelia_list_scoped_groups,
|
||||||
|
# _authelia_report_access_scope, edit_authelia_user's per-user group
|
||||||
|
# toggle, and _authelia_unprotect_site's cleanup all already key off that
|
||||||
|
# exact suffix pattern to find "this is a site-scoping group, not some
|
||||||
|
# other group a user happens to be in" — dropping it would mean touching
|
||||||
|
# all four of those instead of just this one function.
|
||||||
_authelia_scope_access() {
|
_authelia_scope_access() {
|
||||||
local service_id="$1" domain="$2"
|
local service_id="$1" domain="$2"
|
||||||
local authelia_dir="$DOCKER_DIR/authelia"
|
local authelia_dir="$DOCKER_DIR/authelia"
|
||||||
@@ -1552,54 +1676,100 @@ _authelia_scope_access() {
|
|||||||
|
|
||||||
[ -f "$config_file" ] || return 0
|
[ -f "$config_file" ] || return 0
|
||||||
|
|
||||||
local group="${service_id}-only"
|
echo ""
|
||||||
|
echo " Who should be able to reach $domain via Authelia?"
|
||||||
|
echo " 0) Native — your own users, no extra restriction (default — same"
|
||||||
|
echo " access as everything else)"
|
||||||
|
echo " 1) Outside access — a named group of specific users only"
|
||||||
|
local scope_choice=""
|
||||||
|
prompt_text " Choice [0 for native, 1 for outside access]:" "0" scope_choice
|
||||||
|
[ "$scope_choice" = "1" ] || return 0
|
||||||
|
|
||||||
if grep -qF "subject: \"group:${group}\"" "$config_file" 2>/dev/null; then
|
local -a existing_groups
|
||||||
log_info "Access to $domain is already scoped to group '$group'."
|
mapfile -t existing_groups < <(_authelia_list_scoped_groups "$users_file")
|
||||||
|
local group="" is_new_group="true" i
|
||||||
|
if [ "${#existing_groups[@]}" -gt 0 ]; then
|
||||||
|
echo " Existing outside-access groups:"
|
||||||
|
for i in "${!existing_groups[@]}"; do
|
||||||
|
echo " $((i + 1))) ${existing_groups[$i]%-only}"
|
||||||
|
done
|
||||||
|
echo " Pick a number to add $domain to one of these, or type a new group"
|
||||||
|
echo " name (e.g. \"customer1\") to create one."
|
||||||
|
else
|
||||||
|
echo " No outside-access groups exist yet — type a name to create one"
|
||||||
|
echo " (e.g. \"customer1\")."
|
||||||
|
fi
|
||||||
|
local group_choice=""
|
||||||
|
prompt_text " Group:" "${service_id}" group_choice
|
||||||
|
if [ -z "$group_choice" ]; then
|
||||||
|
log_warning "No group entered — leaving $domain open to all Authelia users."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ "$group_choice" =~ ^[0-9]+$ ]] && [ "$group_choice" -ge 1 ] && [ "$group_choice" -le "${#existing_groups[@]}" ]; then
|
||||||
|
group="${existing_groups[$((group_choice - 1))]}"
|
||||||
|
is_new_group="false"
|
||||||
|
else
|
||||||
|
local clean_name
|
||||||
|
clean_name="$(echo "$group_choice" | tr -cs 'a-zA-Z0-9_-' '-' | sed 's/^-*//;s/-*$//')"
|
||||||
|
if [ -z "$clean_name" ]; then
|
||||||
|
log_warning "Invalid group name — leaving $domain open to all Authelia users."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
group="${clean_name}-only"
|
||||||
|
for i in "${existing_groups[@]}"; do
|
||||||
|
[ "$i" = "$group" ] && is_new_group="false"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -A1 -F " - domain: \"${domain}\"" "$config_file" 2>/dev/null | grep -qF " subject: \"group:${group}\""; then
|
||||||
|
log_info "$domain is already scoped to group '${group%-only}'."
|
||||||
log_info "Manage its members via this menu's \"Edit an existing user\" (toggle their groups by hand in users.yml), or the universal-access report below."
|
log_info "Manage its members via this menu's \"Edit an existing user\" (toggle their groups by hand in users.yml), or the universal-access report below."
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
local -a usernames
|
||||||
echo " Who should be able to reach $domain via Authelia?"
|
if [ "$is_new_group" = "false" ]; then
|
||||||
echo " 1) Specific users only"
|
log_info "Reusing existing group '${group%-only}' — its current members already have access."
|
||||||
echo " 0) Any Authelia user (default — same access as everything else)"
|
local add_more=""
|
||||||
local scope_choice=""
|
prompt_yn " Add more users to '${group%-only}' now? (y/n):" "n" add_more
|
||||||
prompt_text " Choice [1, or 0 for any user]:" "0" scope_choice
|
if [[ ! "$add_more" =~ ^[Yy]$ ]]; then
|
||||||
[ "$scope_choice" = "1" ] || return 0
|
usernames=()
|
||||||
|
|
||||||
local -a existing_users
|
|
||||||
mapfile -t existing_users < <(_authelia_list_usernames "$users_file")
|
|
||||||
local i
|
|
||||||
if [ "${#existing_users[@]}" -gt 0 ]; then
|
|
||||||
echo " Existing Authelia users:"
|
|
||||||
for i in "${!existing_users[@]}"; do
|
|
||||||
echo " $((i + 1))) ${existing_users[$i]}"
|
|
||||||
done
|
|
||||||
echo " Pick by number (space-separated), and/or type new usernames directly"
|
|
||||||
echo " to create them — mix freely, e.g. \"1 3 newperson\"."
|
|
||||||
else
|
|
||||||
echo " No existing Authelia users yet — type usernames below to create them fresh."
|
|
||||||
fi
|
|
||||||
echo " Anyone typed (not picked by number) who doesn't already have an"
|
|
||||||
echo " Authelia account gets one created — you'll get their temporary"
|
|
||||||
echo " password to hand over."
|
|
||||||
local raw_users=""
|
|
||||||
prompt_text " Usernames/numbers:" "" raw_users
|
|
||||||
local -a raw_tokens usernames
|
|
||||||
read -ra raw_tokens <<< "$raw_users"
|
|
||||||
if [ "${#raw_tokens[@]}" -eq 0 ]; then
|
|
||||||
log_warning "No usernames entered — leaving $domain open to all Authelia users."
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
local t
|
|
||||||
for t in "${raw_tokens[@]}"; do
|
|
||||||
if [[ "$t" =~ ^[0-9]+$ ]] && [ "$t" -ge 1 ] && [ "$t" -le "${#existing_users[@]}" ]; then
|
|
||||||
usernames+=("${existing_users[$((t - 1))]}")
|
|
||||||
else
|
|
||||||
usernames+=("$t")
|
|
||||||
fi
|
fi
|
||||||
done
|
fi
|
||||||
|
|
||||||
|
if [ "$is_new_group" = "true" ] || [[ "${add_more:-}" =~ ^[Yy]$ ]]; then
|
||||||
|
local -a existing_users
|
||||||
|
mapfile -t existing_users < <(_authelia_list_usernames "$users_file")
|
||||||
|
if [ "${#existing_users[@]}" -gt 0 ]; then
|
||||||
|
echo " Existing Authelia users:"
|
||||||
|
for i in "${!existing_users[@]}"; do
|
||||||
|
echo " $((i + 1))) ${existing_users[$i]}"
|
||||||
|
done
|
||||||
|
echo " Pick by number (space-separated), and/or type new usernames directly"
|
||||||
|
echo " to create them — mix freely, e.g. \"1 3 newperson\"."
|
||||||
|
else
|
||||||
|
echo " No existing Authelia users yet — type usernames below to create them fresh."
|
||||||
|
fi
|
||||||
|
echo " Anyone typed (not picked by number) who doesn't already have an"
|
||||||
|
echo " Authelia account gets one created — you'll get their temporary"
|
||||||
|
echo " password to hand over."
|
||||||
|
local raw_users=""
|
||||||
|
prompt_text " Usernames/numbers:" "" raw_users
|
||||||
|
local -a raw_tokens
|
||||||
|
read -ra raw_tokens <<< "$raw_users"
|
||||||
|
if [ "${#raw_tokens[@]}" -eq 0 ] && [ "$is_new_group" = "true" ]; then
|
||||||
|
log_warning "No usernames entered — leaving $domain open to all Authelia users."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local t
|
||||||
|
for t in "${raw_tokens[@]}"; do
|
||||||
|
if [[ "$t" =~ ^[0-9]+$ ]] && [ "$t" -ge 1 ] && [ "$t" -le "${#existing_users[@]}" ]; then
|
||||||
|
usernames+=("${existing_users[$((t - 1))]}")
|
||||||
|
else
|
||||||
|
usernames+=("$t")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
local u start_end start end
|
local u start_end start end
|
||||||
for u in "${usernames[@]}"; do
|
for u in "${usernames[@]}"; do
|
||||||
@@ -1609,7 +1779,7 @@ _authelia_scope_access() {
|
|||||||
start_end="$(_authelia_user_line_range "$users_file" "$u")"
|
start_end="$(_authelia_user_line_range "$users_file" "$u")"
|
||||||
start="${start_end% *}"; end="${start_end#* }"
|
start="${start_end% *}"; end="${start_end#* }"
|
||||||
_authelia_toggle_group "$users_file" "$start" "$end" "$group" "true"
|
_authelia_toggle_group "$users_file" "$start" "$end" "$group" "true"
|
||||||
log_success "Added '$u' to group '$group'"
|
log_success "Added '$u' to group '${group%-only}'"
|
||||||
else
|
else
|
||||||
local email_default="${u}@${SITE_DOMAIN:-example.com}"
|
local email_default="${u}@${SITE_DOMAIN:-example.com}"
|
||||||
if _authelia_create_user_noninteractive "$u" "$u" "$email_default" "$group"; then
|
if _authelia_create_user_noninteractive "$u" "$u" "$email_default" "$group"; then
|
||||||
@@ -1624,6 +1794,16 @@ _authelia_scope_access() {
|
|||||||
# before access_control's existing "*.${AUTHELIA_DOMAIN}" catch-all.
|
# before access_control's existing "*.${AUTHELIA_DOMAIN}" catch-all.
|
||||||
local authelia_domain
|
local authelia_domain
|
||||||
authelia_domain="$(awk '/^ cookies:$/{f=1; next} f && /domain:/{print $3; exit}' "$config_file")"
|
authelia_domain="$(awk '/^ cookies:$/{f=1; next} f && /domain:/{print $3; exit}' "$config_file")"
|
||||||
|
|
||||||
|
# ...but BELOW the admin-bypass rule for this apex — otherwise the
|
||||||
|
# deny-elsewhere rule below would outrank it, and an admin who ever ends
|
||||||
|
# up in this group (by mistake, or a future feature) would be locked out
|
||||||
|
# of every other domain on the instance. Ensures the bypass rule exists
|
||||||
|
# first (retrofits it if this instance predates the feature), then
|
||||||
|
# inserts right after it rather than at the literal top of "rules:".
|
||||||
|
_authelia_ensure_admin_bypass "$config_file" "$authelia_domain"
|
||||||
|
local anchor_line="$ADMIN_BYPASS_LINE"
|
||||||
|
|
||||||
local scope_rules=" - domain: \"${domain}\"
|
local scope_rules=" - domain: \"${domain}\"
|
||||||
subject: \"group:${group}\"
|
subject: \"group:${group}\"
|
||||||
policy: two_factor
|
policy: two_factor
|
||||||
@@ -1631,9 +1811,10 @@ _authelia_scope_access() {
|
|||||||
subject: \"group:${group}\"
|
subject: \"group:${group}\"
|
||||||
policy: deny"
|
policy: deny"
|
||||||
|
|
||||||
awk -v block="$scope_rules" '
|
awk -v block="$scope_rules" -v anchor="$anchor_line" '
|
||||||
/^ rules:$/ && !done { print; print block; done=1; next }
|
|
||||||
{ print }
|
{ print }
|
||||||
|
anchor != "" && NR == anchor + 0 { print block }
|
||||||
|
anchor == "" && /^ rules:$/ && !done { print block; done=1 }
|
||||||
' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
|
' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
|
||||||
chown 1000:1000 "$config_file" 2>/dev/null || true
|
chown 1000:1000 "$config_file" 2>/dev/null || true
|
||||||
|
|
||||||
@@ -1641,7 +1822,128 @@ _authelia_scope_access() {
|
|||||||
prompt_yn " Restart Authelia to apply this scoping? (y/n):" "y" restart_auth
|
prompt_yn " Restart Authelia to apply this scoping? (y/n):" "y" restart_auth
|
||||||
if [[ "$restart_auth" =~ ^[Yy]$ ]]; then
|
if [[ "$restart_auth" =~ ^[Yy]$ ]]; then
|
||||||
(cd "$authelia_dir" && docker compose restart authelia 2>/dev/null) \
|
(cd "$authelia_dir" && docker compose restart authelia 2>/dev/null) \
|
||||||
&& log_success "Authelia restarted — $domain is now restricted to group '$group'." \
|
&& log_success "Authelia restarted — $domain is now restricted to group '${group%-only}'." \
|
||||||
|
|| log_warning "Restart failed — check: docker compose logs authelia"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Renames an existing outside-access group everywhere it appears — every
|
||||||
|
# "subject: group:<old>" line in configuration.yml's access_control.rules,
|
||||||
|
# and every member's "- <old>" entry under their own groups: list in
|
||||||
|
# users.yml. A plain find/replace on the "-only"-suffixed internal name;
|
||||||
|
# the display name typed at the prompt (what _authelia_scope_access shows
|
||||||
|
# without the suffix) is what the user actually renames.
|
||||||
|
_authelia_rename_group() {
|
||||||
|
local users_file="$DOCKER_DIR/authelia/config/users.yml"
|
||||||
|
local config_file="$DOCKER_DIR/authelia/config/configuration.yml"
|
||||||
|
[ -f "$config_file" ] || { log_warning "No configuration.yml found — install Authelia first."; return 1; }
|
||||||
|
|
||||||
|
local -a existing_groups
|
||||||
|
mapfile -t existing_groups < <(_authelia_list_scoped_groups "$users_file")
|
||||||
|
if [ "${#existing_groups[@]}" -eq 0 ]; then
|
||||||
|
log_info "No outside-access groups exist yet."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo " Outside-access groups:"
|
||||||
|
local i
|
||||||
|
for i in "${!existing_groups[@]}"; do
|
||||||
|
echo " $((i + 1))) ${existing_groups[$i]%-only}"
|
||||||
|
done
|
||||||
|
echo " 0) Cancel"
|
||||||
|
local choice=""
|
||||||
|
prompt_text " Which group to rename? [0]:" "0" choice
|
||||||
|
if ! [[ "$choice" =~ ^[0-9]+$ ]] || [ "$choice" -lt 1 ] || [ "$choice" -gt "${#existing_groups[@]}" ]; then
|
||||||
|
log_info "Cancelled — nothing changed."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local old_group="${existing_groups[$((choice - 1))]}"
|
||||||
|
|
||||||
|
local new_name=""
|
||||||
|
prompt_text " New name for '${old_group%-only}':" "" new_name
|
||||||
|
local clean_name
|
||||||
|
clean_name="$(echo "$new_name" | tr -cs 'a-zA-Z0-9_-' '-' | sed 's/^-*//;s/-*$//')"
|
||||||
|
if [ -z "$clean_name" ]; then
|
||||||
|
log_warning "No name entered — nothing changed."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local new_group="${clean_name}-only"
|
||||||
|
if [ "$new_group" = "$old_group" ]; then
|
||||||
|
log_info "Same name — nothing changed."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
for i in "${existing_groups[@]}"; do
|
||||||
|
if [ "$i" = "$new_group" ]; then
|
||||||
|
log_warning "A group named '${clean_name}' already exists — pick a different name, or use that group directly instead of renaming into it."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
sed -i "s/subject: \"group:${old_group}\"/subject: \"group:${new_group}\"/g" "$config_file"
|
||||||
|
sed -i "s/^ - ${old_group}\$/ - ${new_group}/g" "$users_file"
|
||||||
|
chown 1000:1000 "$config_file" "$users_file" 2>/dev/null || true
|
||||||
|
log_success "Renamed '${old_group%-only}' to '${clean_name}' — updated every access rule and member using it."
|
||||||
|
|
||||||
|
local restart_auth=""
|
||||||
|
prompt_yn " Restart Authelia to apply? (y/n):" "y" restart_auth
|
||||||
|
if [[ "$restart_auth" =~ ^[Yy]$ ]]; then
|
||||||
|
(cd "$DOCKER_DIR/authelia" && docker compose restart authelia 2>/dev/null) \
|
||||||
|
&& log_success "Authelia restarted" \
|
||||||
|
|| log_warning "Restart failed — check: docker compose logs authelia"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Menu-driven, idempotent bulk version of _authelia_ensure_admin_bypass —
|
||||||
|
# backfills the admin-bypass rule for every apex domain currently on this
|
||||||
|
# instance in one pass. install_authelia and add_authelia_domain bake the
|
||||||
|
# rule in automatically for anything created from here on, and
|
||||||
|
# _authelia_scope_access retrofits it just-in-time for whichever domain it's
|
||||||
|
# scoping — this is for catching every OTHER domain an install already had
|
||||||
|
# before this feature existed (or just double-checking one that's fine).
|
||||||
|
# Safe to re-run any time; only touches domains actually missing the rule.
|
||||||
|
_authelia_ensure_admin_access_everywhere() {
|
||||||
|
local config_file="$DOCKER_DIR/authelia/config/configuration.yml"
|
||||||
|
[ -f "$config_file" ] || { log_warning "No configuration.yml found — install Authelia first."; return 1; }
|
||||||
|
|
||||||
|
local -a apex_domains
|
||||||
|
mapfile -t apex_domains < <(grep -oE '^ - domain: "\*\.[^"]+"' "$config_file" | sed -E 's/^ - domain: "\*\.(.+)"$/\1/' | awk '!seen[$0]++')
|
||||||
|
|
||||||
|
if [ "${#apex_domains[@]}" -eq 0 ]; then
|
||||||
|
log_info "No apex domains found on this instance."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo " Checking the admin-bypass rule for every domain on this instance:"
|
||||||
|
local d any_added="false"
|
||||||
|
for d in "${apex_domains[@]}"; do
|
||||||
|
_authelia_ensure_admin_bypass "$config_file" "$d"
|
||||||
|
if [ "$ADMIN_BYPASS_ADDED" = "true" ]; then
|
||||||
|
echo " ${d} — added"
|
||||||
|
any_added="true"
|
||||||
|
else
|
||||||
|
echo " ${d} — already present"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$any_added" = "false" ]; then
|
||||||
|
log_success "Every domain already had it — nothing to change."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Members of the 'admins' group now always have access to every domain"
|
||||||
|
log_success "listed above, regardless of any per-service scoping already in place —"
|
||||||
|
log_success "or added later, since _authelia_scope_access checks for this automatically"
|
||||||
|
log_success "from now on."
|
||||||
|
log_warning "Make sure your admin account(s) are actually in the 'admins' group in"
|
||||||
|
log_warning "config/users.yml — the default admin created at install time already is."
|
||||||
|
|
||||||
|
local restart_auth=""
|
||||||
|
prompt_yn " Restart Authelia to apply? (y/n):" "y" restart_auth
|
||||||
|
if [[ "$restart_auth" =~ ^[Yy]$ ]]; then
|
||||||
|
(cd "$DOCKER_DIR/authelia" && docker compose restart authelia 2>/dev/null) \
|
||||||
|
&& log_success "Authelia restarted" \
|
||||||
|| log_warning "Restart failed — check: docker compose logs authelia"
|
|| log_warning "Restart failed — check: docker compose logs authelia"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -1664,7 +1966,7 @@ _authelia_report_access_scope() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo " Universal access (every protected domain):"
|
echo " Native (universal — every protected domain, no outside-access group):"
|
||||||
local -a universal=() restricted=()
|
local -a universal=() restricted=()
|
||||||
local u start_end start end groups_in_range
|
local u start_end start end groups_in_range
|
||||||
for u in "${all_users[@]}"; do
|
for u in "${all_users[@]}"; do
|
||||||
@@ -1681,7 +1983,7 @@ _authelia_report_access_scope() {
|
|||||||
[ "${#universal[@]}" -eq 0 ] && echo " (none)"
|
[ "${#universal[@]}" -eq 0 ] && echo " (none)"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo " Scoped to specific services only:"
|
echo " Outside access (limited to a named group):"
|
||||||
if [ "${#restricted[@]}" -eq 0 ]; then
|
if [ "${#restricted[@]}" -eq 0 ]; then
|
||||||
echo " (none)"
|
echo " (none)"
|
||||||
else
|
else
|
||||||
|
|||||||
Reference in New Issue
Block a user