Increase platform abstraction cohesion
This commit is contained in:
@@ -1,26 +1,56 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
AUTH_PY_IN_CONTAINER="/code/utilities/prosody/auth_django.py"
|
||||
STACK_ID="${GIA_STACK_ID:-${STACK_ID:-}}"
|
||||
STACK_ID="$(echo "$STACK_ID" | tr -cs 'a-zA-Z0-9._-' '-' | sed 's/^-*//; s/-*$//')"
|
||||
if [ -n "$STACK_ID" ]; then
|
||||
GIA_CONTAINER="gia_${STACK_ID}"
|
||||
else
|
||||
GIA_CONTAINER="gia"
|
||||
fi
|
||||
AUTH_ENDPOINT="${PROSODY_AUTH_ENDPOINT:-http://127.0.0.1:8000/internal/prosody/auth/}"
|
||||
PROSODY_SECRET="${XMPP_SECRET:-}"
|
||||
|
||||
b64url() {
|
||||
printf '%s' "$1" | base64 | tr -d '\n=' | tr '+/' '-_'
|
||||
}
|
||||
|
||||
http_get() {
|
||||
url="$1"
|
||||
if command -v wget >/dev/null 2>&1; then
|
||||
wget -qO- -T 5 "$url" 2>/dev/null
|
||||
return
|
||||
fi
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -fsS --max-time 5 "$url" 2>/dev/null
|
||||
return
|
||||
fi
|
||||
if command -v lua >/dev/null 2>&1; then
|
||||
lua - "$url" <<'LUA'
|
||||
local http = require("socket.http")
|
||||
local ltn12 = require("ltn12")
|
||||
http.TIMEOUT = 5
|
||||
local chunks = {}
|
||||
local _, code = http.request({
|
||||
url = arg[1],
|
||||
sink = ltn12.sink.table(chunks),
|
||||
})
|
||||
if tonumber(code) and tonumber(code) >= 200 and tonumber(code) < 300 then
|
||||
io.write(table.concat(chunks))
|
||||
end
|
||||
LUA
|
||||
return
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Prosody external auth uses line-oriented stdin/stdout.
|
||||
# We execute one short-lived auth check per line to avoid stale stdin issues
|
||||
# in long-lived `podman exec -i` sessions after disconnects/restarts.
|
||||
while IFS= read -r line; do
|
||||
if [ -z "$line" ]; then
|
||||
if [ -z "$line" ] || [ -z "$PROSODY_SECRET" ]; then
|
||||
printf '0\n'
|
||||
continue
|
||||
fi
|
||||
printf '%s\n' "$line" | podman exec -i "$GIA_CONTAINER" sh -lc '
|
||||
cd /code &&
|
||||
. /venv/bin/activate &&
|
||||
exec python -u '"$AUTH_PY_IN_CONTAINER"' --once
|
||||
'
|
||||
secret_b64="$(b64url "$PROSODY_SECRET")"
|
||||
line_b64="$(b64url "$line")"
|
||||
result="$(http_get "$AUTH_ENDPOINT?secret_b64=$secret_b64&line_b64=$line_b64" || printf '0')"
|
||||
case "$result" in
|
||||
1|1$'\n')
|
||||
printf '1\n'
|
||||
;;
|
||||
*)
|
||||
printf '0\n'
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
@@ -5,6 +5,19 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
STACK_ENV="${STACK_ENV:-$ROOT_DIR/stack.env}"
|
||||
ENSURE_XMPP_SECRET_SCRIPT="$ROOT_DIR/utilities/prosody/ensure_xmpp_secret.sh"
|
||||
|
||||
podman_cmd() {
|
||||
if [[ "$(id -u)" -eq 0 ]] && id code >/dev/null 2>&1; then
|
||||
local quoted=()
|
||||
local arg
|
||||
for arg in "$@"; do
|
||||
quoted+=("$(printf '%q' "$arg")")
|
||||
done
|
||||
su -s /bin/sh code -c "podman ${quoted[*]}"
|
||||
return
|
||||
fi
|
||||
podman "$@"
|
||||
}
|
||||
|
||||
if [[ -f "$STACK_ENV" ]]; then
|
||||
set -a
|
||||
. "$STACK_ENV"
|
||||
@@ -31,20 +44,105 @@ name_with_stack() {
|
||||
POD_NAME="$(name_with_stack "gia")"
|
||||
PROSODY_CONTAINER="$(name_with_stack "prosody_gia")"
|
||||
|
||||
resolve_runtime_names() {
|
||||
local candidates
|
||||
candidates="$(
|
||||
podman_cmd pod ps --format '{{.Name}}' 2>/dev/null \
|
||||
| grep -E '^((pod_)?gia)($|_[a-zA-Z0-9._-]+$)' || true
|
||||
)"
|
||||
local expected=()
|
||||
if [[ -n "$STACK_ID" ]]; then
|
||||
expected+=("gia_${STACK_ID}" "pod_gia_${STACK_ID}")
|
||||
else
|
||||
expected+=("gia" "pod_gia")
|
||||
fi
|
||||
local name
|
||||
for name in "${expected[@]}"; do
|
||||
if printf '%s\n' "$candidates" | grep -qx "$name"; then
|
||||
POD_NAME="$name"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$POD_NAME" != "gia" ]] && [[ "$POD_NAME" != "pod_gia" ]] && ! podman_cmd pod exists "$POD_NAME" >/dev/null 2>&1; then
|
||||
POD_NAME="$(name_with_stack "gia")"
|
||||
fi
|
||||
|
||||
if podman_cmd pod exists "$POD_NAME" >/dev/null 2>&1; then
|
||||
if [[ "$POD_NAME" == pod_gia* ]]; then
|
||||
local suffix="${POD_NAME#pod_gia}"
|
||||
suffix="${suffix#_}"
|
||||
if [[ -n "$suffix" ]]; then
|
||||
PROSODY_CONTAINER="prosody_gia_${suffix}"
|
||||
else
|
||||
PROSODY_CONTAINER="prosody_gia"
|
||||
fi
|
||||
elif [[ "$POD_NAME" == gia_* ]]; then
|
||||
local suffix="${POD_NAME#gia_}"
|
||||
PROSODY_CONTAINER="prosody_gia_${suffix}"
|
||||
else
|
||||
PROSODY_CONTAINER="prosody_gia"
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
# Fallback: if only one gia-like pod exists, use it.
|
||||
local count
|
||||
count="$(printf '%s\n' "$candidates" | sed '/^$/d' | wc -l | tr -d ' ')"
|
||||
if [[ "$count" != "1" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
local detected
|
||||
detected="$(printf '%s\n' "$candidates" | sed '/^$/d' | head -n1)"
|
||||
[[ -z "$detected" ]] && return
|
||||
|
||||
POD_NAME="$detected"
|
||||
if [[ "$POD_NAME" == pod_gia_* ]]; then
|
||||
local suffix="${POD_NAME#pod_gia_}"
|
||||
PROSODY_CONTAINER="prosody_gia_${suffix}"
|
||||
elif [[ "$POD_NAME" == pod_gia ]]; then
|
||||
PROSODY_CONTAINER="prosody_gia"
|
||||
elif [[ "$POD_NAME" == gia_* ]]; then
|
||||
local suffix="${POD_NAME#gia_}"
|
||||
PROSODY_CONTAINER="prosody_gia_${suffix}"
|
||||
else
|
||||
PROSODY_CONTAINER="prosody_gia"
|
||||
fi
|
||||
echo "Info: auto-detected pod '$POD_NAME' and Prosody container '$PROSODY_CONTAINER'." >&2
|
||||
}
|
||||
|
||||
PROSODY_CONFIG_FILE="${QUADLET_PROSODY_CONFIG_FILE:-$ROOT_DIR/utilities/prosody/prosody.cfg.lua}"
|
||||
PROSODY_CERTS_DIR="${QUADLET_PROSODY_CERTS_DIR:-$ROOT_DIR/.podman/gia_prosody_certs}"
|
||||
PROSODY_DATA_DIR="${QUADLET_PROSODY_DATA_DIR:-$ROOT_DIR/.podman/gia_prosody_data}"
|
||||
PROSODY_LOGS_DIR="${QUADLET_PROSODY_LOGS_DIR:-$ROOT_DIR/.podman/gia_prosody_logs}"
|
||||
PROSODY_IMAGE="${PROSODY_IMAGE:-docker.io/prosody/prosody-alpine:latest}"
|
||||
PROSODY_IMAGE="${PROSODY_IMAGE:-docker.io/prosody/prosody:latest}"
|
||||
if id code >/dev/null 2>&1; then
|
||||
PROSODY_RUN_USER_DEFAULT="$(id -u code):$(id -g code)"
|
||||
else
|
||||
PROSODY_RUN_USER_DEFAULT="$(id -u):$(id -g)"
|
||||
fi
|
||||
PROSODY_RUN_USER="${PROSODY_RUN_USER:-$PROSODY_RUN_USER_DEFAULT}"
|
||||
|
||||
mkdir -p "$PROSODY_CERTS_DIR" "$PROSODY_DATA_DIR" "$PROSODY_LOGS_DIR"
|
||||
|
||||
up() {
|
||||
resolve_runtime_names
|
||||
local run_args=()
|
||||
local pod_state=""
|
||||
if podman pod exists "$POD_NAME"; then
|
||||
pod_state="$(podman pod inspect "$POD_NAME" --format '{{.State}}' 2>/dev/null || true)"
|
||||
if [[ "$pod_state" == "Running" ]]; then
|
||||
if podman_cmd pod exists "$POD_NAME"; then
|
||||
pod_state="$(podman_cmd pod inspect "$POD_NAME" --format '{{.State}}' 2>/dev/null || true)"
|
||||
if [[ -z "$pod_state" ]]; then
|
||||
pod_state="$(podman_cmd pod ps --format '{{.Name}} {{.Status}}' 2>/dev/null | awk -v pod="$POD_NAME" '$1==pod {print $2}')"
|
||||
fi
|
||||
if [[ "$pod_state" != "Running" && "$pod_state" != "Degraded" ]]; then
|
||||
podman_cmd pod start "$POD_NAME" >/dev/null 2>&1 || true
|
||||
pod_state="$(podman_cmd pod inspect "$POD_NAME" --format '{{.State}}' 2>/dev/null || true)"
|
||||
if [[ -z "$pod_state" ]]; then
|
||||
pod_state="$(podman_cmd pod ps --format '{{.Name}} {{.Status}}' 2>/dev/null | awk -v pod="$POD_NAME" '$1==pod {print $2}')"
|
||||
fi
|
||||
fi
|
||||
if [[ "$pod_state" == "Running" || "$pod_state" == "Degraded" ]]; then
|
||||
run_args+=(--pod "$POD_NAME")
|
||||
else
|
||||
echo "Warning: pod '$POD_NAME' state is '$pod_state'; starting $PROSODY_CONTAINER standalone with explicit ports." >&2
|
||||
@@ -54,17 +152,20 @@ up() {
|
||||
echo "Warning: pod '$POD_NAME' not found; starting $PROSODY_CONTAINER standalone with explicit ports." >&2
|
||||
run_args+=(-p 5222:5222 -p 5269:5269 -p 5280:5280 -p 8888:8888)
|
||||
fi
|
||||
podman run -d \
|
||||
podman_cmd run -d \
|
||||
--replace \
|
||||
--name "$PROSODY_CONTAINER" \
|
||||
"${run_args[@]}" \
|
||||
--env-file "$STACK_ENV" \
|
||||
--user "$PROSODY_RUN_USER" \
|
||||
--entrypoint prosody \
|
||||
-v "$PROSODY_CONFIG_FILE:/etc/prosody/prosody.cfg.lua:ro" \
|
||||
-v "$PROSODY_CERTS_DIR:/etc/prosody/certs" \
|
||||
-v "$PROSODY_DATA_DIR:/var/lib/prosody" \
|
||||
-v "$PROSODY_LOGS_DIR:/var/log/prosody" \
|
||||
-v "$ROOT_DIR:/code" \
|
||||
"$PROSODY_IMAGE" >/dev/null
|
||||
"$PROSODY_IMAGE" \
|
||||
-F >/dev/null
|
||||
if [[ " ${run_args[*]} " == *" --pod "* ]]; then
|
||||
echo "Started $PROSODY_CONTAINER in pod $POD_NAME"
|
||||
else
|
||||
@@ -73,16 +174,19 @@ up() {
|
||||
}
|
||||
|
||||
down() {
|
||||
podman rm -f "$PROSODY_CONTAINER" >/dev/null 2>&1 || true
|
||||
resolve_runtime_names
|
||||
podman_cmd rm -f "$PROSODY_CONTAINER" >/dev/null 2>&1 || true
|
||||
echo "Stopped $PROSODY_CONTAINER"
|
||||
}
|
||||
|
||||
status() {
|
||||
podman ps --format "table {{.Names}}\t{{.Status}}" | grep -E "^$PROSODY_CONTAINER\b" || true
|
||||
resolve_runtime_names
|
||||
podman_cmd ps --format "table {{.Names}}\t{{.Status}}" | grep -E "^$PROSODY_CONTAINER\b" || true
|
||||
}
|
||||
|
||||
logs() {
|
||||
podman logs -f "$PROSODY_CONTAINER"
|
||||
resolve_runtime_names
|
||||
podman_cmd logs -f "$PROSODY_CONTAINER"
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
|
||||
84
utilities/prosody/modules/mod_auth_gia.lua
Normal file
84
utilities/prosody/modules/mod_auth_gia.lua
Normal file
@@ -0,0 +1,84 @@
|
||||
-- GIA auth provider for legacy Prosody builds that do not ship
|
||||
-- external auth modules. Delegates auth checks to external_auth_command.
|
||||
|
||||
local new_sasl = require "util.sasl".new;
|
||||
|
||||
local host = module.host;
|
||||
local log = module._log;
|
||||
local auth_cmd = module:get_option_string(
|
||||
"external_auth_command",
|
||||
"/code/utilities/prosody/auth_django.sh"
|
||||
);
|
||||
|
||||
local provider = {};
|
||||
|
||||
local function shell_quote(value)
|
||||
return "'" .. tostring(value or ""):gsub("'", "'\\''") .. "'";
|
||||
end
|
||||
|
||||
local function run_external(line)
|
||||
local cmd = "printf %s\\\\n " .. shell_quote(line) .. " | " .. shell_quote(auth_cmd);
|
||||
local handle = io.popen(cmd, "r");
|
||||
if not handle then
|
||||
return false;
|
||||
end
|
||||
local output = handle:read("*a") or "";
|
||||
handle:close();
|
||||
output = output:gsub("%s+", "");
|
||||
return output == "1";
|
||||
end
|
||||
|
||||
function provider.test_password(username, password)
|
||||
if not username or username == "" then
|
||||
return nil, "Auth failed. Invalid username.";
|
||||
end
|
||||
if not password or password == "" then
|
||||
return nil, "Auth failed. Invalid password.";
|
||||
end
|
||||
local ok = run_external("auth:" .. username .. ":" .. host .. ":" .. password);
|
||||
if ok then
|
||||
return true;
|
||||
end
|
||||
return nil, "Auth failed. Invalid username or password.";
|
||||
end
|
||||
|
||||
function provider.user_exists(username)
|
||||
if not username or username == "" then
|
||||
return nil, "Auth failed. Invalid username.";
|
||||
end
|
||||
if run_external("isuser:" .. username .. ":" .. host) then
|
||||
return true;
|
||||
end
|
||||
return nil, "Auth failed. Invalid username.";
|
||||
end
|
||||
|
||||
function provider.set_password()
|
||||
return nil, "method not implemented";
|
||||
end
|
||||
|
||||
function provider.users()
|
||||
return function() return nil end;
|
||||
end
|
||||
|
||||
function provider.create_user()
|
||||
return nil, "method not implemented";
|
||||
end
|
||||
|
||||
function provider.delete_user()
|
||||
return nil, "method not implemented";
|
||||
end
|
||||
|
||||
function provider.get_sasl_handler()
|
||||
return new_sasl(host, {
|
||||
plain_test = function(_, username, password)
|
||||
local ok = provider.test_password(username, password);
|
||||
if ok then
|
||||
return true, true;
|
||||
end
|
||||
return false, nil;
|
||||
end
|
||||
});
|
||||
end
|
||||
|
||||
log("debug", "initializing GIA auth provider for host '%s'", host);
|
||||
module:provides("auth", provider);
|
||||
@@ -1,19 +1,17 @@
|
||||
local env = os.getenv
|
||||
local domain = env("DOMAIN") or "example.com"
|
||||
local xmpp_component = env("XMPP_JID") or ("jews." .. domain)
|
||||
local share_host = env("XMPP_SHARE_HOST") or ("share." .. domain)
|
||||
local xmpp_secret = env("XMPP_SECRET") or ""
|
||||
|
||||
if xmpp_secret == "" then
|
||||
error("XMPP_SECRET is required for Prosody component authentication")
|
||||
end
|
||||
|
||||
sasl_mechanisms = { "PLAIN", "SCRAM-SHA-1", "SCRAM-SHA-256" }
|
||||
sasl_mechanisms = { "PLAIN" }
|
||||
plugin_paths = { "/code/utilities/prosody/modules" }
|
||||
|
||||
daemonize = false
|
||||
pidfile = "/run/prosody/prosody.pid"
|
||||
pidfile = "/tmp/prosody.pid"
|
||||
|
||||
admins = { env("XMPP_ADMIN_JID") or ("admin@" .. domain) }
|
||||
admins = { env("XMPP_ADMIN_JID") or "admin@example.com" }
|
||||
|
||||
modules_enabled = {
|
||||
"disco";
|
||||
@@ -21,30 +19,20 @@ modules_enabled = {
|
||||
"saslauth";
|
||||
"tls";
|
||||
"blocklist";
|
||||
"bookmarks";
|
||||
"carbons";
|
||||
"dialback";
|
||||
"limits";
|
||||
"pep";
|
||||
"private";
|
||||
"smacks";
|
||||
"vcard4";
|
||||
"vcard_legacy";
|
||||
"cloud_notify";
|
||||
"csi_simple";
|
||||
"invites";
|
||||
"invites_adhoc";
|
||||
"invites_register";
|
||||
"ping";
|
||||
"time";
|
||||
"uptime";
|
||||
"version";
|
||||
"mam";
|
||||
"turn_external";
|
||||
"vcard4";
|
||||
"vcard_legacy";
|
||||
"admin_adhoc";
|
||||
"admin_shell";
|
||||
"announce";
|
||||
"auth_external_insecure";
|
||||
"http";
|
||||
}
|
||||
|
||||
@@ -55,7 +43,7 @@ limits = {
|
||||
s2sin = { rate = "100mb/s"; };
|
||||
}
|
||||
|
||||
authentication = "external_insecure"
|
||||
authentication = "gia"
|
||||
archive_expires_after = "1w"
|
||||
|
||||
log = {
|
||||
@@ -65,20 +53,28 @@ log = {
|
||||
}
|
||||
|
||||
certificates = "certs"
|
||||
ssl = {
|
||||
key = "/etc/prosody/certs/cert.pem";
|
||||
certificate = "/etc/prosody/certs/cert.pem";
|
||||
}
|
||||
|
||||
component_ports = { 8888 }
|
||||
component_interfaces = { "0.0.0.0" }
|
||||
|
||||
VirtualHost domain
|
||||
authentication = "external_insecure"
|
||||
external_auth_command = "/code/utilities/prosody/auth_django.sh"
|
||||
certificate = "/etc/prosody/certs/cert.pem"
|
||||
|
||||
Component xmpp_component
|
||||
component_secret = xmpp_secret
|
||||
|
||||
Component share_host "http_file_share"
|
||||
|
||||
http_ports = { 5280 }
|
||||
http_interfaces = { "0.0.0.0", "::" }
|
||||
http_external_url = "https://" .. share_host .. "/"
|
||||
http_external_url = "https://share.example.com/"
|
||||
|
||||
VirtualHost "example.com"
|
||||
authentication = "gia"
|
||||
external_auth_command = "/code/utilities/prosody/auth_django.sh"
|
||||
ssl = {
|
||||
key = "/etc/prosody/certs/cert.pem";
|
||||
certificate = "/etc/prosody/certs/cert.pem";
|
||||
}
|
||||
|
||||
Component "jews.example.com"
|
||||
component_secret = xmpp_secret
|
||||
ssl = {
|
||||
key = "/etc/prosody/certs/cert.pem";
|
||||
certificate = "/etc/prosody/certs/cert.pem";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user