Compare commits

..
2 Commits
3 changed files with 146 additions and 7 deletions

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "tesm" name = "tesm"
version = "2.8.44" version = "2.8.46"
description = "TESM -- Verwaltung und Ueberwachung von PoE-Endgeraeten und Aruba-Switchen" description = "TESM -- Verwaltung und Ueberwachung von PoE-Endgeraeten und Aruba-Switchen"
requires-python = ">=3.11" requires-python = ">=3.11"
dependencies = [ dependencies = [
+128 -6
View File
@@ -807,9 +807,82 @@ mkdir -p "$(dirname "$ziel")"
printf %s '@FRACHT@' | base64 -d > "$ziel" printf %s '@FRACHT@' | base64 -d > "$ziel"
chmod 0644 "$ziel" chmod 0644 "$ziel"
echo "geschrieben: $ziel" echo "geschrieben: $ziel"
# Der Uebersetzer: systemd legt fremde Optionen roh und unter ihrer Nummer ab
# ("OPTION_225=687474..."), weil im DHCP-Paket weder Name noch Typ steht.
# Beides weiss nur dieser Server -- also schreibt er die Zuordnung mit.
mkdir -p /usr/local/lib/tesm
printf %s '@UEBERSETZER@' | base64 -d > /usr/local/lib/tesm/dhcp-options
chmod 0755 /usr/local/lib/tesm/dhcp-options
printf %s '@DIENST@' | base64 -d > /etc/systemd/system/tesm-dhcp-options.service
printf %s '@PFADEINHEIT@' | base64 -d > /etc/systemd/system/tesm-dhcp-options.path
chmod 0644 /etc/systemd/system/tesm-dhcp-options.service /etc/systemd/system/tesm-dhcp-options.path
systemctl daemon-reload
systemctl enable --now tesm-dhcp-options.path >/dev/null 2>&1 || true
systemctl enable tesm-dhcp-options.service >/dev/null 2>&1 || true
networkctl reload networkctl reload
networkctl reconfigure "$schnittstelle" networkctl reconfigure "$schnittstelle"
# Der Pfadwaechter loest bei der naechsten Lease aus; einmal von Hand, damit
# die Datei sofort dasteht.
sleep 2
systemctl start tesm-dhcp-options.service || true
echo "Lease: /run/systemd/netif/leases/$(cat /sys/class/net/"$schnittstelle"/ifindex)" echo "Lease: /run/systemd/netif/leases/$(cat /sys/class/net/"$schnittstelle"/ifindex)"
echo "Klartext: /run/tesm-dhcp-options.env"
cat /run/tesm-dhcp-options.env 2>/dev/null || true
"""
#: Uebersetzt die rohen Optionen der Lease in "name=wert".
#:
#: Warum es das braucht: in einem DHCP-Paket stehen nur Nummer, Laenge und
#: Bytes. Dass 225 "url" heisst und eine Zeichenkette ist, steht allein im
#: Katalog dieses Servers -- systemd kann es nicht wissen und legt deshalb
#: Hex unter der Nummer ab. Diese Zuordnung reist mit.
UEBERSETZER = """#!/bin/sh
# Von TESM erzeugt. Schreibt die eigenen DHCP-Optionen im Klartext nach
# /run/tesm-dhcp-options.env -- eine Zeile "name=wert" je Option.
set -e
ziel=/run/tesm-dhcp-options.env
schnittstelle=$(ip -o -4 route show default 2>/dev/null | awk '{print $5; exit}')
[ -n "$schnittstelle" ] || exit 0
lease=/run/systemd/netif/leases/$(cat /sys/class/net/"$schnittstelle"/ifindex 2>/dev/null)
[ -f "$lease" ] || exit 0
zwischen=$(mktemp)
for paar in @PAARE@; do
code=${paar%%:*}
name=${paar#*:}
roh=$(sed -n "s/^OPTION_$code=//p" "$lease" | head -n1)
[ -n "$roh" ] || continue
wert=$(/usr/bin/printf "$(echo "$roh" | sed 's/../@BS@x&/g')")
echo "$name=$wert" >> "$zwischen"
done
mv -f "$zwischen" "$ziel"
chmod 0644 "$ziel"
"""
#: Einmal beim Start und nach jeder Lease.
DIENST_EINHEIT = """[Unit]
Description=TESM -- DHCP-Optionen im Klartext
After=systemd-networkd.service
[Service]
Type=oneshot
ExecStart=/usr/local/lib/tesm/dhcp-options
RemainAfterExit=no
[Install]
WantedBy=multi-user.target
"""
PFAD_EINHEIT = """[Unit]
Description=TESM -- wacht ueber die DHCP-Lease
[Path]
PathModified=/run/systemd/netif/leases
Unit=tesm-dhcp-options.service
[Install]
WantedBy=multi-user.target
""" """
#: Ersetzt den eigenen Block in einer Datei, die dem Client gehoert. #: Ersetzt den eigenen Block in einer Datei, die dem Client gehoert.
@@ -902,12 +975,55 @@ def client_konfiguration(conn: sqlite3.Connection, stil: str) -> dict[str, Any]:
"pfad": pfad, "pfad": pfad,
"inhalt": inhalt, "inhalt": inhalt,
"nachher": nachher, "nachher": nachher,
"skript": _uebernahme_skript(stil, inhalt), "skript": _uebernahme_skript(stil, inhalt, optionen),
"auslesen": _auslesen(stil, optionen),
"optionen": optionen, "optionen": optionen,
} }
def _uebernahme_skript(stil: str, inhalt: str) -> str: def _auslesen(stil: str, benannt: list[dict[str, Any]]) -> dict[str, str]:
"""Wie der Wert auf dem Client wieder herauskommt.
Gehoert dazu, weil "angekommen" sonst niemand pruefen kann -- und weil ein
RMM-Agent genau diese Zeile braucht.
Zwei Wege bei systemd-networkd, und das hat einen Grund: in der Lease
steht der Wert **hexkodiert unter seiner Nummer**. Im DHCP-Paket reisen
nur Code, Laenge und Bytes; dass 225 "url" heisst und Text enthaelt, weiss
allein der Katalog dieses Servers. Deshalb legt der SSH-Push einen kleinen
Uebersetzer mit, der nach jeder Lease ``/run/tesm-dhcp-options.env``
schreibt -- "name=wert", lesbar. ``roh`` bleibt fuer den Fall, dass nur
die Datei von Hand kopiert wurde.
"""
if not benannt:
return {}
code = int(benannt[0]["code"])
name = str(benannt[0]["name"])
if stil == "networkd":
return {
"einfach": f"grep '^{name}=' /run/tesm-dhcp-options.env",
"roh": (
"lease=/run/systemd/netif/leases/$(cat /sys/class/net/$IF/ifindex); "
f"/usr/bin/printf \"$(sed -n 's/^OPTION_{code}=//p' \"$lease\" "
"| sed 's/../@BS@x&/g')\""
).replace("@BS@", "\\" * 2),
}
if stil == "dhcpcd":
# dhcpcd kennt den Namen, weil 'define' ihn ihm beigebracht hat.
return {"einfach": f"dhcpcd -U $IF | grep -i '^{name}='", "roh": ""}
return {
"einfach": f"grep -i 'option {name}' /var/lib/dhcp/dhclient.leases | tail -n1",
"roh": "",
}
def _b64(text: str) -> str:
import base64
return base64.b64encode(text.encode("utf-8")).decode("ascii")
def _uebernahme_skript(stil: str, inhalt: str, benannt: list[dict[str, Any]] | None = None) -> str:
"""Das Skript, das die Konfiguration auf dem Client eintraegt. """Das Skript, das die Konfiguration auf dem Client eintraegt.
Zwei Dinge macht es anders als ein blosses ``tee``, und beide sind mit Zwei Dinge macht es anders als ein blosses ``tee``, und beide sind mit
@@ -925,12 +1041,18 @@ def _uebernahme_skript(stil: str, inhalt: str) -> str:
Der Inhalt reist base64-kodiert und als **Argument**, nicht ueber stdin: Der Inhalt reist base64-kodiert und als **Argument**, nicht ueber stdin:
dort steht bei einem Client mit Passwort das Kennwort fuer ``sudo``. dort steht bei einem Client mit Passwort das Kennwort fuer ``sudo``.
""" """
import base64 fracht = _b64(inhalt)
benannt = benannt or []
fracht = base64.b64encode(inhalt.encode("utf-8")).decode("ascii")
if stil == "networkd": if stil == "networkd":
return NETWORKD_SKRIPT.replace("@FRACHT@", fracht) paare = " ".join(f"{eintrag['code']}:{eintrag['name']}" for eintrag in benannt)
uebersetzer = UEBERSETZER.replace("@PAARE@", paare).replace("@BS@", "\\\\") # sed gibt aus zwei Zeichen eines aus
return (
NETWORKD_SKRIPT.replace("@FRACHT@", fracht)
.replace("@UEBERSETZER@", _b64(uebersetzer))
.replace("@DIENST@", _b64(DIENST_EINHEIT))
.replace("@PFADEINHEIT@", _b64(PFAD_EINHEIT))
)
ziel = "/etc/dhcpcd.conf" if stil == "dhcpcd" else "/etc/dhcp/dhclient.conf" ziel = "/etc/dhcpcd.conf" if stil == "dhcpcd" else "/etc/dhcp/dhclient.conf"
dienst = ( dienst = (
@@ -445,6 +445,23 @@
</div> </div>
<pre class="code-block code-block--wrap" id="konfig-{{ stil }}">{{ konfig.inhalt }}</pre> <pre class="code-block code-block--wrap" id="konfig-{{ stil }}">{{ konfig.inhalt }}</pre>
<div class="fs-xs faint">Danach: <code>{{ konfig.nachher }}</code></div> <div class="fs-xs faint">Danach: <code>{{ konfig.nachher }}</code></div>
{% if konfig.auslesen.einfach %}
{# Ohne diese Zeile kann niemand pruefen, ob es angekommen ist --
und ein RMM-Agent braucht genau sie. #}
<div class="fs-xs faint">
Auslesen auf dem Client:
<code class="break-all">{{ konfig.auslesen.einfach }}</code>
</div>
{% if konfig.auslesen.roh %}
<div class="fs-xs faint">
systemd legt fremde Optionen unter ihrer Nummer und hexkodiert ab – den
Klartext schreibt ein kleiner Übersetzer, den der SSH-Push mitinstalliert
(<code>/run/tesm-dhcp-options.env</code>, nach jeder Lease erneuert).
Direkt aus der Lease:
<code class="break-all">{{ konfig.auslesen.roh }}</code>
</div>
{% endif %}
{% endif %}
{% if current_user.has('servicing_command.execute') %} {% if current_user.has('servicing_command.execute') %}
<form method="post" action="{{ url_for('dhcp.push_client_config') }}"> <form method="post" action="{{ url_for('dhcp.push_client_config') }}">