Merge pull request #15 from profullstack/feat/irc-store-auth

feat(irc): gate IRC on the BBS user store; remove ssh irc@; external clients only
This commit is contained in:
Anthony Ettinger 2026-06-14 20:23:54 -07:00 committed by GitHub
commit 336ff00fa3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 92 additions and 720 deletions

View file

@ -4,22 +4,22 @@
# membership. setup.sh installs this to /usr/local/bin/ergo-auth-member and
# wires it into /etc/ergo/ircd.yaml (accounts.auth-script).
#
# AgentBBS members are real OS users (tilde.town model; setup.sh provisions an
# OS account per member). IRC is members-only, so a login is approved iff the
# requested account name is a real OS user with uid >= MIN_UID (which excludes
# system accounts like root/ergo/agentbbs). The passphrase is intentionally
# IGNORED — membership (being an OS user) IS the credential, by design (see
# docs/irc.md). Anyone who knows a member's name can connect as them; that
# tradeoff was chosen deliberately for this private, TLS-only network.
# The single source of truth is the BBS user store (the bbs.profullstack.com
# accounts), queried via a loopback agentbbs endpoint (/irc-auth) that answers
# {"member":bool,"premium":bool}. IRC is members-only, so a login is approved iff
# the account is a member. The passphrase is intentionally IGNORED — BBS
# membership IS the credential, by design (see docs/irc.md). Anyone who knows a
# member's name can connect as them; that tradeoff was chosen deliberately for
# this private, TLS-only network.
#
# Protocol (Ergo): one JSON object on stdin per attempt, one JSON line on stdout
# then exit. Input keys: accountName, passphrase, certfp, ip. Output:
# {"success":bool,"accountName":str,"error":str}.
#
# args: ["<min-uid>"] # defaults to 1000
# args: ["<auth-url>"] # defaults to http://127.0.0.1:8088/irc-auth
set -uo pipefail
MIN_UID="${1:-1000}"
AUTH_URL="${1:-http://127.0.0.1:8088/irc-auth}"
# Always emit valid JSON and exit 0 — Ergo reads the JSON, not the exit code;
# a non-zero exit / no output is treated as a script error, not a clean deny.
@ -41,17 +41,13 @@ case "$acct" in
*[!A-Za-z0-9._-]* | "." | ".." ) deny "invalid account name" ;;
esac
# Resolve the OS account; getent passwd returns name:passwd:uid:gid:...
entry="$(getent passwd "$acct" 2>/dev/null || true)"
[ -n "$entry" ] || deny "not a member"
# Ask the BBS store (loopback) whether this account is a member. curl URL-encodes
# the account name; a failed/timed-out request denies (fail closed).
resp="$(curl -fsS --max-time 5 --get --data-urlencode "account=${acct}" "$AUTH_URL" 2>/dev/null || true)"
member="$(printf '%s' "$resp" | jq -r '.member // false' 2>/dev/null || true)"
uid="$(printf '%s' "$entry" | cut -d: -f3)"
case "$uid" in
''|*[!0-9]*) deny "not a member" ;;
esac
if [ "$uid" -ge "$MIN_UID" ]; then
if [ "$member" = "true" ]; then
printf '{"success":true,"accountName":"%s"}\n' "$acct"
else
deny "system accounts cannot use IRC"
deny "not a member"
fi

View file

@ -18,10 +18,10 @@
# for a mixed humans + agents network: it is MEMBERS-ONLY — every client must
# authenticate with SASL, self-service registration is OFF, and an auth-script
# (deploy/ergo/auth-script.sh, installed as /usr/local/bin/ergo-auth-member)
# approves a login only if the account name is a real OS user (uid>=1000).
# BBS members are provisioned as OS users (tilde.town model; setup.sh §4b/§9a2),
# so "OS user" == "BBS member". Message history (CHATHISTORY) is enabled so
# reconnecting agents and web clients can replay. See docs/irc.md.
# approves a login only if the BBS user store says the account is a member (it
# queries the loopback agentbbs /irc-auth endpoint — the single user-level source
# of truth). Message history (CHATHISTORY) is enabled so reconnecting agents and
# web clients can replay. See docs/irc.md.
#
# Most settings keep Ergo's recommended defaults — read the inline comments
# before changing one. A few worth knowing about:
@ -53,8 +53,8 @@ server:
key: __TLS_DIR__/privkey.pem
min-tls-version: 1.2
# Loopback plaintext (6667) — never exposed (firewall blocks it). Used by
# the in-BBS bridge and local tooling on the agentbbs box only.
# Loopback plaintext (6667) — never exposed (firewall blocks it). For
# on-box tooling/bridges only (must still SASL as a member).
"127.0.0.1:6667":
"[::1]:6667":
@ -597,13 +597,14 @@ accounts:
# pluggable authentication mechanism, via subprocess invocation
# see the manual for details on how to write an authentication plugin script
auth-script:
# MEMBERS-ONLY gate: ergo-auth-member approves a login iff the account
# name is a real OS user with uid >= the arg (BBS members are OS users).
# MEMBERS-ONLY gate: ergo-auth-member asks the BBS user store (via the
# loopback /irc-auth endpoint passed as the arg) whether the account is a
# member, and approves the login iff so.
enabled: true
command: "/usr/local/bin/ergo-auth-member"
# min-uid is passed as a constant arg (excludes system accounts like
# ergo/root); the per-attempt auth data is sent over stdin/stdout:
args: ["1000"]
# the loopback auth URL is passed as a constant arg; the per-attempt auth
# data (accountName/passphrase/ip) is sent over stdin/stdout:
args: ["__IRC_AUTH_URL__"]
# auto-create the Ergo account on first successful (member) auth, so
# members never have to register:
autocreate: true