Wir realisieren mittels /etc/accounts eine netzwerktaugliche keyvalue storage flatfile database mit openssh bordmitteln und kurzen shellscripten, analog zu einem ldap oder active directory, nur wesentlich minimalistischer. Unsere administrativ ausführbare Scripte liegen dabei unter /etc/accounts als dotfile und sind mit der bash auto completion bequem listbar und jederzeit erweiterbar.
Wir binden openssh mit zwei universellen set-config und get-config systemusern ein, um werte aus dem config Unterverzeichnis zu lesen und zu erfragen.
Jedem virtuellen User-Account geben wir dabei eine eindeutige ID, im Beispiel "u000023". Accounts sind bequem deaktivierbar, indem man ein u Verzeichnis von 1 nach 0 verschiebt. In 2 kann man neue u-Verzeichnisse vorbereiten und dann atomar nach 1 bewegen, um sie zu aktivieren.
Innerhalb eines u-Verzeichnisse kann man beliebige Unterordner und Dateien definieren, um Wertepaare und Hierarchie abzubilden. Dazu gleich mehr im Punkt populate /etc/accounts.
Wir arbeiten als root auf dem System:
## zwei neue systemuser mit zufallspasswort erstellen useradd -m -p"$(tr -dc "0-9a-zA-Z" < /dev/urandom | head -c 32 | openssl passwd -5 -stdin)" "get-config" useradd -m -p"$(tr -dc "0-9a-zA-Z" < /dev/urandom | head -c 32 | openssl passwd -5 -stdin)" "set-config" ## unsere flatfile database in /etc/accounts beginnen mkdir -p /etc/accounts/0 mkdir -p /etc/accounts/1 mkdir -p /etc/accounts/2 chown -R root:root /etc/accounts/ chmod +x /etc/accounts/.show.ssh.active.authorizedkeys chmod +x /etc/accounts/.get.config touch /etc/accounts/.show.ssh.active.authorizedkeys touch /etc/accounts/.get.config chown root:root /etc/accounts/.show.ssh.active.authorizedkeys chown root:root /etc/accounts/.get.config
Testdaten in /etc/accounts ablegen
Wir bauen uns eine Flatfile Datenbank, die alle unserer Accountdaten enthält. Dabei brauchen wir nur Dateien und Verzeichnisse, um sehr umfangreiche und universelle Strukturen zu realisieren:
Wir leiten auch den fingerprint vom angegebenen publickey ab. Verwende daher bitte in der nächsten Variable deinen eigenen publickey, wenn du das Ergebnis später auch Testen können willst!
Alles andere kannst du so lassen.
publickey="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOID6tldu9PbX6ksmDH0SVm6bkv6VQv39WVvg2hw8MuF" state="1" accountdir="/etc/accounts" uid="u000023" mkdir -p ${accountdir}/${state}/${uid} cd ${accountdir}/${state}/${uid} ## +++ Demouser +++ mkdir -p name.user/maxmustermann mkdir -p name.short/MaMu mkdir -p name.title/Mr. mkdir -p name.vorname/Max mkdir -p name.nachname/Mustermann mkdir -p private.birthday/19700101 mkdir -p ssh.pubkeys # filename deines pubkeys wird dein md5 fingerprint mit dem prefix md5. # Im Beispiel also md5.b62391a58d612bcc2c01e1f74af8144a # content der datei ist dein publickey echo "${publickey}" | tee ssh.pubkeys/$(echo "${publickey}" | ssh-keygen -E md5 -l -f- | cut -d " " -f2 | cut -d: -f2- | tr -dc "0-9a-f" | sed "s/^/md5./g") ## +++ Democonfig des Users +++ mkdir -p config/alpha.allow/ mkdir -p config/beta.allow/ mkdir -p config/gamma.allow/ cp ssh.pubkeys/md5.* config/alpha.allow/ cp ssh.pubkeys/md5.* config/beta.allow/ echo "1237487569" > config/alpha.uid echo "C:\Programme\Gibts\Nicht" > config/alpha.path echo "bulgur" > config/beta.username echo "#009933" > config/beta.color01 echo "#C13399" > config/beta.color02 echo "#A9362E" > config/beta.color03 echo "erbsen" > config/gamma.gem echo "127.0.0.1" > config/gamma.ip touch config/beta.pass.hex.clientspecified chown "set-config" config/beta.pass.hex.clientspecified
Das Ergebnis kannst du dir anzeigen lassen mittels
cd find /etc/accounts
im obigen Beispiel wäre es also diese Ausgabe/Struktur zu sehen:
/etc/accounts/1/u000023 /etc/accounts/1/u000023/name.nachname /etc/accounts/1/u000023/name.nachname/Mustermann /etc/accounts/1/u000023/name.vorname /etc/accounts/1/u000023/name.vorname/Max /etc/accounts/1/u000023/name.title /etc/accounts/1/u000023/name.title/Mr. /etc/accounts/1/u000023/private.birthday /etc/accounts/1/u000023/private.birthday/19700101 /etc/accounts/1/u000023/name.user /etc/accounts/1/u000023/name.user/maxmustermann /etc/accounts/1/u000023/ssh.pubkeys /etc/accounts/1/u000023/ssh.pubkeys/md5.b62391a58d612bcc2c01e1f74af8144a /etc/accounts/1/u000023/config /etc/accounts/1/u000023/config/beta.color03 /etc/accounts/1/u000023/config/beta.pass.hex.clientspecified /etc/accounts/1/u000023/config/gamma.allow /etc/accounts/1/u000023/config/gamma.ip /etc/accounts/1/u000023/config/beta.username /etc/accounts/1/u000023/config/alpha.path /etc/accounts/1/u000023/config/gamma.gem /etc/accounts/1/u000023/config/alpha.uid /etc/accounts/1/u000023/config/beta.allow /etc/accounts/1/u000023/config/beta.allow/md5.b62391a58d612bcc2c01e1f74af8144a /etc/accounts/1/u000023/config/alpha.allow /etc/accounts/1/u000023/config/alpha.allow/md5.b62391a58d612bcc2c01e1f74af8144a /etc/accounts/1/u000023/config/beta.color02 /etc/accounts/1/u000023/config/beta.color01 /etc/accounts/1/u000023/name.short /etc/accounts/1/u000023/name.short/MaMu
Du kannst nach diesem Schema beliebig viele weitere accounts mit anderer uid erstellen. inaktive accounts erhalten den State "0", aktive erhalten den State "1" und Accounts in Vorbereitung kannst du in "2" ablegen.
Wie du siehst, kodieren wir einige Dinge direkt im Dateinamen, weil dies bei der direkten Verzeichnis-Durchsicht bei der schnell Zuordnung hilft.
Andere Dinge wiederum werden als payload im Content einer Datei abgelegt, vorallem wenn dieser Inhalt aufgrund des Zeichenumfangs auch gar nicht im Dateinamen selbst abbildbar wäre.
Wir verwenden zudem geschickt auch Verzeichnisnamen als Informationsträger. Generell ist das config/*.allow also immer ein Verzeichnis, in dem die erlaubten ssh publickeys liegen, die auf diesen configdatensatz zugreifen und ihn ggf. partiell modifizieren können. Das prefix for dem .allow ist dabei identisch mit dem configname/keyname prefix. die allow pubkeys müssen keine Dateien sein, sondern könnten auch als symlinks zu den keys in ssh.pubkeys des Nutzers realisiert sein. Es müssen also keine Kopien sein. Allerdings können es auch vollständig andere keys sein, die nichts mit dem useraccount zu tun haben und nur spezielle zum config lesen/setzen definiert wurden.
In der sshd config stellen auf die Fingerprint-Hash Anzeige md5 um, statt sha256. Zudem definieren wir für die beiden neuen user get-config und set-config ein AuthorizedKeysCommand. Wir rufen dabei /etc/accounts/.show.ssh.active.authorizedkeys mit drei Parametern auf, sodass die dynamisch erzeugte Liste mit "restrict,command=" beginnen wird - und dabei übergeben wir auch das als forced command "/etc/accounts/.get.config" unser script, sowie die wichtigen Parameter %k und %f. openssh ersetzt diese automatisch mit den verwendeten publickkey %k des sich gerade einloggenden SSH Nutzers und dem fingerprint %f des verwendeten publickeys.
FingerprintHash md5 Match User get-config AuthorizedKeysCommand /etc/accounts/.show.ssh.active.authorizedkeys /etc/accounts/.get.config %k %f AuthorizedKeysFile none AuthorizedKeysCommandUser root Match User set-config AuthorizedKeysCommand /etc/accounts/.show.ssh.active.authorizedkeys /etc/accounts/.set.config %k %f AuthorizedKeysFile none AuthorizedKeysCommandUser root
Denke daran, ggf. die bei dir vorhandene globale Config Zeile "AllowUsers" um die neuen systemuser get-config und set-config zu erweitern! Dort sind alle user space separated aufgeführt, denen du allgemein ssh Zugriffs getattest.
Vergiss nicht, "service ssh reload" auszuführen, wenn du die Config geändert hast.
In diesem Script füllen wir die prepend variable, sobald mehr als ein parameter übergeben wurde. (was ja bei unserem Command-Aufruf durch ssh dann immer der Fall sein wird). Ansonsten werden alleverfügbaren Publickeys ohne forced command aufgelistet.
Wir sind damit sehr universell aufgestellt und können beim Listengenerierungs-Aufruf also beliebige Forced-Commands als ersten parameter mit angeben, oder das gleiche Script auch zur Allgemeinen Freigabe verwenden.
Alle erlaubten ssh pubkeys erzeugen wir dabei dynamisch aus den Publickeys Dateien, die in /etc/accounts/1/... beim jeweiligen aktiven user liegen. diese geben wir dann auch auf stdout aus
#!/bin/sh test -n "$1" && prepend="restrict,command=\"$@\" " || prepend="" export accountdir="/etc/accounts/1" find "${accountdir}" -maxdepth 3 -type f -wholename "${accountdir}/*/ssh.pubkeys/md5.*" -exec head -n 1 "{}" \; | sed "s|^|${prepend}|g"
#!/bin/sh # reinhard@finalmedia.de # Fr 12. Apr 20:44:28 CEST 2024 test "$#" -ge 2 || exit 1 # Variablen MUESSEN exportiert werden export confname="$(timeout 6 head -n 1 | head -c 32 | tr -dc "0-9a-z")" export fingerprint="$(echo "$2" | grep -i "^md5:" | cut -d: -f2- | tr -dc "0-9a-f\n")" export pubkey="$(echo "$1" | tr -dc "0-9a-zA-Z/+=")" export accountdir="/etc/accounts/1" test $DEBUG && echo "# systemuser: $(whoami) (soll: get-config)" >&2 test $DEBUG && echo "# publickey: $1" >&2 test $DEBUG && echo "# fingerprint: $2" >&2 echo "[${confname}]" find "${accountdir}" -not -type d -wholename "${accountdir}/*/config/${confname}.allow/md5.${fingerprint}" \ -execdir sh -c 'grep -q "$pubkey" md5.${fingerprint} && find ../ -not -type d -maxdepth 1 -mindepth 1 -name "${confname}.*" -printf "$(pwd -P)/../%f\n"' \; | while read p do # key/value paare ausgeben basename "$p" | tr "\n" "=" head -n1 "$p" | tr -d "\n" echo done
#!/bin/sh # reinhard@finalmedia.de # Sa 13. Apr 11:49:22 CEST 2024 # keyvaluefile muss bereits existieren, damit es änderbar ist. # set setzt also nur, legt aber nicht neu an. (wichtiges sicherheitsfeature) # zudem muss die datei dem user set-config gehören oder # die datei betreffende datei muss chmod 666 aufweisen. test "$#" -ge 2 || exit 1 # Variablen MUESSEN exportiert werden export line="$(timeout 10 head -n 1 | head -c 1024 | tr -dc "=0-9a-zA-Z@ :;.+[]()!#/_-")" export confname="$(echo "$line" | cut -d= -f1 | cut -d. -f 1 | tr -dc "0-9a-z")" export conffullkeyname="$(echo "$line" | cut -d= -f1 | tr -dc "0-9a-z.")" export confvalue="$(echo "$line" | cut -d= -f2-)" export fingerprint="$(echo "$2" | grep -i "^md5:" | cut -d: -f2- | tr -dc "0-9a-f\n")" export pubkey="$(echo "$1" | tr -dc "0-9a-zA-Z/+=")" export accountdir="/etc/accounts/1" # Abbruch, wenn der conffullkeyname nicht auf .clientspecified endet echo "${conffullkeyname}" | grep -q "\.clientspecified$" || exit 1 test $DEBUG && echo "# systemuser: $(whoami) (soll: set-config)" >&2 test $DEBUG && echo "# publickey: $1" >&2 test $DEBUG && echo "# fingerprint: $2" >&2 # alle conffullkeyname einträge finden und neuen wert setzen, wenn mit aktuellem fingerprint und pubkey erlaubt find "${accountdir}" -not -type d -wholename "${accountdir}/*/config/${confname}.allow/md5.${fingerprint}" \ -execdir sh -c 'grep -q "$pubkey" md5.${fingerprint} && find ../ -not -type d -maxdepth 1 -mindepth 1 -name "${conffullkeyname}" -printf "$(pwd -P)/../%f\n"' \; | while read p do echo "${confvalue}" | tee "$p" | tr -d "\n" echo head -n 1 "$p" | tr -d "\n" echo done
Wenn du die gesamte config für das "beta" tool abrufen willst, übergibst du via ssh also den string "beta" auf stdin und verbindest dich als user "get-config"
ssh get-config@localhost -T <<< beta
Um über den ssh client eine Config zu schreiben (nur für die .clientspecified values möglich), nutzt du:
ssh set-config@localhost -T <<< beta.pass.hex.clientspecified=a3b763baeb72abb29e728cb25
Du kannst auch mit alternativen Keys und Confignames testen, die wir ja testweise im Verzeichnis angelegt haben.
ssh -i irgendeinkeyfile get-config@localhost -T <<< gamma
Dieses Howto ist als "root" user angegeben und du konfigurierst ja auch sshd und die /etc/accounts struktur als root. Nur das Script .show.ssh.active.authorizedkeys läuft dabei auch als root. die dort aufgerufenen eigentlichen Scripte zur User-Interaktion (also die forced commands) .set.config und .get.config laufen jeweils unter dem neuen systemuser "set-config" und "get-config", dafür sorgt open-sshd.
Daher müssen auch die Dateien, die der set-config verändern können soll, dem user set-config gehören also 644 (empfholen) oder aber world writeable sein, also 666.
Tipp: Wenn du in der /etc/ssh/sshd_config SetEnv DEBUG=1 setzt, erhält der sich einloggende User auf stderr die zusätzlichen debug Informationen, da die Scripte .get.config und .set.config diese Varible dann ebenso erhalten. Dein Auszug aus der /etc/ssh/sshd_config sähe dann so aus:
FingerprintHash md5 Match User get-config AuthorizedKeysCommand /etc/accounts/.show.ssh.active.authorizedkeys /etc/accounts/.get.config %k %f AuthorizedKeysFile none AuthorizedKeysCommandUser root SetEnv DEBUG=1 Match User set-config AuthorizedKeysCommand /etc/accounts/.show.ssh.active.authorizedkeys /etc/accounts/.set.config %k %f AuthorizedKeysFile none AuthorizedKeysCommandUser root SetEnv DEBUG=1Vergiss nicht, "service ssh reload" auszuführen, wenn du die Config geändert hast.
Es empfhielt sich auch noch dieses Script anzulegen, um eine bequeme Config Übersicht abrufen zu können.
#!/bin/sh find "/etc/accounts/" -type f -wholename "/etc/accounts/1/*/config/*"
Denke daran auch ein chmod +x darauf zu setzen. Dann kannst du den Befehl "/etc/accounts/.show.all.config" absetzen.