364 lines
16 KiB
YAML
364 lines
16 KiB
YAML
---
|
|
# guacd + FreeRDP als reine Protokoll-Engine fuer RDP (Konzept 3/4.3).
|
|
# Laeuft ausschliesslich lokal gebunden, kein Netzwerkzugriff von aussen.
|
|
#
|
|
# Installationsweg haengt vom Ziel-OS ab:
|
|
#
|
|
# - Ubuntu (22.04/24.04): hat ein fertiges apt-Paket ("guacd",
|
|
# "libguac-client-rdp0", ueber die "universe"-Komponente). Zwar mit
|
|
# veralteter Version (1.3.0), aber offiziell von Ubuntu gepflegt und
|
|
# bekommt automatisch Sicherheitsupdates ueber unattended-upgrades (da
|
|
# dynamisch gegen libfreerdp2 gelinkt). Daher hier bevorzugt.
|
|
#
|
|
# - Debian: hat KEIN guacd-apt-Paket (weder bookworm noch trixie -- nur
|
|
# Ubuntu paketiert es ueber "universe"). Daher wird guacd hier gemaess der
|
|
# offiziellen Apache-Guacamole-Doku ("Installing Guacamole natively") aus
|
|
# dem Quellcode gebaut. Nur das RDP-Plugin wird aktiviert
|
|
# (FreeRDP-Dev-Header vorhanden, kein VNC/Telnet), passend zum reinen
|
|
# RDP-Einsatzzweck dieses Jumphosts.
|
|
#
|
|
# FreeRDP-Paketname und Build-Quelle haengen dabei von der Debian-Version ab:
|
|
#
|
|
# - Debian 12 (bookworm): liefert nur FreeRDP2 (freerdp2-dev). Dafuer wird
|
|
# der offizielle guacamole-server-1.5.5-Release-Tarball gebaut (stabil,
|
|
# Checksum-verifiziert).
|
|
#
|
|
# - Debian 13 (trixie): liefert nur FreeRDP3 (freerdp3-dev), und zwar
|
|
# bereits Version 3.15 -- NEUER als das, wogegen der letzte offizielle
|
|
# guacamole-server-Release (1.6.0) geschrieben wurde. Sowohl der
|
|
# 1.6.0-Tarball als auch der aktuelle "main"-Branch (git, Stand: Aug 2026)
|
|
# bauen NICHT gegen trixies FreeRDP3 (u.a. entfernter "->input"-Member
|
|
# auf rdpFreerdp, s. https://issues.apache.org/jira/browse/GUACAMOLE-2146
|
|
# und diverse Debian-13-Install-Guides, die genau das bestaetigen).
|
|
# Es gibt noch KEIN Release oder git-main-Stand mit funktionierender
|
|
# FreeRDP-3.15-Unterstuetzung. Der git-main-Pfad bleibt hier dennoch als
|
|
# Option bestehen (fuer den Fall, dass ein zukuenftiger Commit das
|
|
# behebt), ist aber AUSDRUECKLICH KEIN von Apache Guacamole empfohlener
|
|
# Weg (Doku: "please not a non-release version from git") und aktuell
|
|
# NICHT funktionsfaehig getestet. Fuer produktive Debian-13-Hosts also
|
|
# bis auf Weiteres eher Ubuntu oder Debian 12 als Zielsystem waehlen.
|
|
# Sobald ein Fix verfuegbar ist: guacd_git_ref auf den entsprechenden
|
|
# Commit/Branch setzen, oder bei einem echten Release guacd_version auf
|
|
# den Tag setzen (erzwingt den stabilen Release-Tarball-Pfad).
|
|
|
|
- name: Installationsweg (apt vs. Quellcode) und ggf. FreeRDP-Paket/Version anhand der Distribution bestimmen
|
|
ansible.builtin.set_fact:
|
|
_guacd_install_mode: "{{ 'apt' if ansible_distribution == 'Ubuntu' else 'source' }}"
|
|
_guacd_build_needed: false
|
|
_guacd_src_dir: ""
|
|
_guacd_freerdp_pkg: >-
|
|
{{ guacd_freerdp_dev_package
|
|
if (guacd_freerdp_dev_package | default('', true) | length > 0)
|
|
else ('freerdp3-dev'
|
|
if (ansible_distribution == 'Debian' and ansible_distribution_major_version | int >= 13)
|
|
else 'freerdp2-dev') }}
|
|
_guacd_build_mode: >-
|
|
{{ 'release'
|
|
if (guacd_version | default('', true) | length > 0)
|
|
else ('git'
|
|
if (ansible_distribution == 'Debian' and ansible_distribution_major_version | int >= 13)
|
|
else 'release') }}
|
|
_guacd_version: "{{ guacd_version if (guacd_version | default('', true) | length > 0) else '1.5.5' }}"
|
|
_guacd_git_ref: "{{ guacd_git_ref | default('main') }}"
|
|
|
|
# --- apt-Pfad (Ubuntu) ------------------------------------------------------
|
|
|
|
- name: 'guacd und FreeRDP-Plugin per apt installieren (Ubuntu: guacd liegt in "universe")'
|
|
ansible.builtin.apt:
|
|
name:
|
|
- guacd
|
|
- libguac-client-rdp0
|
|
state: present
|
|
update_cache: true
|
|
when: _guacd_install_mode == 'apt'
|
|
|
|
# --- Quellcode-Pfad (Debian) -------------------------------------------------
|
|
|
|
- name: Build-Abhaengigkeiten fuer guacd installieren
|
|
ansible.builtin.apt:
|
|
name:
|
|
- build-essential
|
|
- libcairo2-dev
|
|
- libjpeg62-turbo-dev
|
|
- libpng-dev
|
|
- libtool-bin
|
|
- uuid-dev
|
|
- libpango1.0-dev
|
|
- libssl-dev
|
|
- "{{ _guacd_freerdp_pkg }}"
|
|
- pkg-config
|
|
- wget
|
|
- ca-certificates
|
|
- git
|
|
- autoconf
|
|
- automake
|
|
- libtool
|
|
state: present
|
|
update_cache: true
|
|
when: _guacd_install_mode == 'source'
|
|
|
|
- name: Vorhandene guacd-Quellcode-Installation pruefen
|
|
ansible.builtin.stat:
|
|
path: /usr/local/sbin/guacd
|
|
register: _guacd_bin
|
|
when: _guacd_install_mode == 'source'
|
|
|
|
- name: Installierte guacd-Version ermitteln (Release-Pfad)
|
|
ansible.builtin.command: /usr/local/sbin/guacd -v
|
|
register: _guacd_installed_version
|
|
changed_when: false
|
|
when: _guacd_install_mode == 'source' and _guacd_bin.stat.exists and _guacd_build_mode == 'release'
|
|
|
|
- name: guacamole-server Quellcode-Tarball herunterladen
|
|
ansible.builtin.get_url:
|
|
url: "https://archive.apache.org/dist/guacamole/{{ _guacd_version }}/source/guacamole-server-{{ _guacd_version }}.tar.gz"
|
|
dest: "/usr/local/src/guacamole-server-{{ _guacd_version }}.tar.gz"
|
|
checksum: "sha256:https://archive.apache.org/dist/guacamole/{{ _guacd_version }}/source/guacamole-server-{{ _guacd_version }}.tar.gz.sha256"
|
|
mode: "0644"
|
|
when: >-
|
|
_guacd_install_mode == 'source' and _guacd_build_mode == 'release' and
|
|
((not _guacd_bin.stat.exists) or (_guacd_version not in (_guacd_installed_version.stdout | default(''))))
|
|
|
|
- name: guacamole-server Tarball entpacken
|
|
ansible.builtin.unarchive:
|
|
src: "/usr/local/src/guacamole-server-{{ _guacd_version }}.tar.gz"
|
|
dest: /usr/local/src
|
|
remote_src: true
|
|
creates: "/usr/local/src/guacamole-server-{{ _guacd_version }}/configure"
|
|
when: >-
|
|
_guacd_install_mode == 'source' and _guacd_build_mode == 'release' and
|
|
((not _guacd_bin.stat.exists) or (_guacd_version not in (_guacd_installed_version.stdout | default(''))))
|
|
|
|
- name: Feststellen, ob (Neu-)Build noetig ist (Release-Pfad)
|
|
ansible.builtin.set_fact:
|
|
_guacd_build_needed: "{{ (not _guacd_bin.stat.exists) or (_guacd_version not in (_guacd_installed_version.stdout | default(''))) }}"
|
|
_guacd_src_dir: "/usr/local/src/guacamole-server-{{ _guacd_version }}"
|
|
when: _guacd_install_mode == 'source' and _guacd_build_mode == 'release'
|
|
|
|
- name: guacamole-server-Quellcode (git {{ _guacd_git_ref }}) holen/aktualisieren
|
|
ansible.builtin.git:
|
|
repo: https://github.com/apache/guacamole-server.git
|
|
dest: /usr/local/src/guacamole-server-git
|
|
version: "{{ _guacd_git_ref }}"
|
|
force: true
|
|
register: _guacd_git
|
|
when: _guacd_install_mode == 'source' and _guacd_build_mode == 'git'
|
|
|
|
- name: Feststellen, ob (Neu-)Build noetig ist (git-Pfad)
|
|
ansible.builtin.set_fact:
|
|
_guacd_build_needed: "{{ (not _guacd_bin.stat.exists) or _guacd_git.changed }}"
|
|
_guacd_src_dir: /usr/local/src/guacamole-server-git
|
|
when: _guacd_install_mode == 'source' and _guacd_build_mode == 'git'
|
|
|
|
- name: Build-System aus configure.ac generieren (autoreconf, nur git-Pfad)
|
|
ansible.builtin.command:
|
|
cmd: autoreconf -fi
|
|
chdir: "{{ _guacd_src_dir }}"
|
|
when: _guacd_install_mode == 'source' and _guacd_build_mode == 'git' and _guacd_build_needed
|
|
|
|
- name: guacamole-server konfigurieren (nur RDP-Plugin, kein VNC/Telnet/guacenc)
|
|
# --sysconfdir=/etc ist Pflicht (Befund 3, Umsetzungsauftrag_Sonnet5.md
|
|
# Teil A.2): ohne diesen Schalter sucht guacd sein Konfigverzeichnis unter
|
|
# /usr/local/etc/guacamole, waehrend die Tasks weiter unten unveraendert
|
|
# nach /etc/guacamole schreiben. Solange guacd_port/bind_host auf den
|
|
# (unwirksamen) Defaults stehen faellt das nicht auf; sobald einer davon
|
|
# abweicht, lauscht guacd auf dem falschen Port/Host und die App bekommt
|
|
# "[Errno 111] Connection refused".
|
|
ansible.builtin.command:
|
|
cmd: >-
|
|
./configure --with-systemd-dir=/etc/systemd/system
|
|
--sysconfdir=/etc
|
|
--disable-guacenc --disable-guaclog
|
|
chdir: "{{ _guacd_src_dir }}"
|
|
when: _guacd_install_mode == 'source' and _guacd_build_needed
|
|
|
|
- name: guacamole-server kompilieren
|
|
ansible.builtin.command:
|
|
cmd: "make -j{{ ansible_processor_vcpus | default(2) }}"
|
|
chdir: "{{ _guacd_src_dir }}"
|
|
when: _guacd_install_mode == 'source' and _guacd_build_needed
|
|
changed_when: true
|
|
|
|
- name: guacamole-server installieren (make install)
|
|
ansible.builtin.command:
|
|
cmd: make install
|
|
chdir: "{{ _guacd_src_dir }}"
|
|
when: _guacd_install_mode == 'source' and _guacd_build_needed
|
|
notify:
|
|
- reload systemd
|
|
- restart guacd
|
|
|
|
- name: Shared-Library-Cache aktualisieren (ldconfig)
|
|
ansible.builtin.command: ldconfig
|
|
when: _guacd_install_mode == 'source' and _guacd_build_needed
|
|
changed_when: false
|
|
|
|
- name: Pruefen, dass das RDP-Plugin tatsaechlich gebaut und installiert wurde
|
|
# Der Build ueberspringt das RDP-Plugin STILLSCHWEIGEND, wenn die
|
|
# FreeRDP-Header (_guacd_freerdp_pkg) beim configure-Lauf fehlten oder
|
|
# nicht gefunden wurden -- "make"/"make install" laufen trotzdem
|
|
# fehlerfrei durch. Ohne diese Pruefung faellt das erst zur Laufzeit auf,
|
|
# als "Support for protocol "rdp" is not installed" von guacd, mitten in
|
|
# einem Verbindungsversuch (Umsetzungsauftrag_Sonnet5.md Teil A.2, "Zu
|
|
# tun"-Liste). Beide moeglichen Installationspfade des Plugins abdecken:
|
|
# /usr/local/lib direkt (aeltere guacamole-server-Versionen) und
|
|
# /usr/local/lib/guacamole (aktuelle Versionen, eigenes Plugin-Verzeichnis).
|
|
ansible.builtin.find:
|
|
paths:
|
|
- /usr/local/lib
|
|
- /usr/local/lib/guacamole
|
|
patterns: "libguac-client-rdp.so*"
|
|
recurse: false
|
|
register: _guacd_rdp_plugin
|
|
when: _guacd_install_mode == 'source'
|
|
|
|
- name: Abbrechen, wenn das RDP-Plugin fehlt
|
|
ansible.builtin.fail:
|
|
msg: >-
|
|
guacd wurde gebaut, aber libguac-client-rdp.so* wurde weder unter
|
|
/usr/local/lib noch /usr/local/lib/guacamole gefunden. Vermutliche
|
|
Ursache: die FreeRDP-Entwicklungspakete ({{ _guacd_freerdp_pkg }})
|
|
waren beim './configure'-Lauf nicht vollstaendig installiert, das
|
|
RDP-Plugin wurde daraufhin stillschweigend uebersprungen. Pruefen:
|
|
'apt policy {{ _guacd_freerdp_pkg }}' auf dem Zielsystem, danach den
|
|
Build-Ordner ({{ _guacd_src_dir }}) loeschen und die Rolle erneut
|
|
ausfuehren, damit configure/make sauber neu laufen.
|
|
when: _guacd_install_mode == 'source' and (_guacd_rdp_plugin.matched | default(0)) == 0
|
|
|
|
# --- Gemeinsame Konfiguration (beide Pfade) ---------------------------------
|
|
|
|
- name: Dedizierten guacd-User sicherstellen (apt-Paket legt ihn i.d.R. bereits an)
|
|
# Befund B6: als Zweitgruppe "{{ jumphost_app_group }}" (append, nicht
|
|
# ersetzen) bekommt guacd das Traversal-Bit auf {{ jumphost_data_dir }}
|
|
# (siehe Rolle sqlite_init) und kann damit ueberhaupt erst bis zu
|
|
# rdp-drives/ vordringen. Ohne diese Zeile schlaegt jede RDP-Sitzung mit
|
|
# aktivierter Laufwerksumleitung mit EACCES fehl.
|
|
ansible.builtin.user:
|
|
name: guacd
|
|
system: true
|
|
shell: /usr/sbin/nologin
|
|
create_home: false
|
|
groups: "{{ jumphost_app_group }}"
|
|
append: true
|
|
notify: restart guacd # neue Gruppenmitgliedschaft wirkt erst nach einem Neustart des Prozesses
|
|
|
|
- name: Konfigurationsverzeichnis /etc/guacamole anlegen
|
|
# Befund 4 (Umsetzungsauftrag_Sonnet5.md Teil A.2): lineinfile mit
|
|
# create:true legt nur die FEHLENDE DATEI an, nicht das Verzeichnis --
|
|
# ohne diesen Task bricht die Rolle bei den beiden folgenden lineinfile-
|
|
# Tasks ab, wenn /etc/guacamole noch nicht existiert (frischer Host, oder
|
|
# apt-Pfad ohne vorherigen Paketaufruf).
|
|
ansible.builtin.file:
|
|
path: /etc/guacamole
|
|
state: directory
|
|
owner: root
|
|
group: root
|
|
mode: "0755"
|
|
|
|
- name: guacd nur an localhost binden
|
|
ansible.builtin.lineinfile:
|
|
path: /etc/guacamole/guacd.conf
|
|
regexp: '^bind_host'
|
|
line: "bind_host = 127.0.0.1"
|
|
create: true
|
|
notify: restart guacd
|
|
|
|
- name: guacd-Port setzen
|
|
ansible.builtin.lineinfile:
|
|
path: /etc/guacamole/guacd.conf
|
|
regexp: '^bind_port'
|
|
line: "bind_port = {{ guacd_port }}"
|
|
create: true
|
|
notify: restart guacd
|
|
|
|
- name: RDP-Laufwerksumleitungs-Verzeichnisse anlegen (Filetransfer, Konzept 4.3)
|
|
ansible.builtin.file:
|
|
path: "/var/lib/jumphost/rdp-drives"
|
|
state: directory
|
|
owner: guacd
|
|
group: guacd
|
|
mode: "0750"
|
|
|
|
- name: Override-Verzeichnis fuer guacd-Unit anlegen
|
|
ansible.builtin.file:
|
|
path: /etc/systemd/system/guacd.service.d
|
|
state: directory
|
|
owner: root
|
|
group: root
|
|
mode: "0755"
|
|
|
|
- name: Gehaertete systemd-Unit fuer guacd ausrollen (Override)
|
|
ansible.builtin.template:
|
|
src: guacd.service.j2
|
|
dest: /etc/systemd/system/guacd.service.d/override.conf
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
notify:
|
|
- reload systemd
|
|
- restart guacd
|
|
|
|
# --- Start-/Erreichbarkeitspruefung (Befunde 1, 2, 7 aus Umsetzungsauftrag_Sonnet5.md Teil A.2) ---
|
|
|
|
- name: Anstehende Handler (z.B. "restart guacd" nach Config-/Unit-Aenderung) sofort anwenden
|
|
# Befund 7: Handler werden von Ansible standardmaessig erst am ENDE DES
|
|
# GESAMTEN PLAYS geflusht, nicht am Ende dieser Rolle. Ohne dieses meta
|
|
# wuerde "restart guacd" erst NACH dem ersten Start von jumphost_app
|
|
# laufen (site.yml ordnet die Rollen zwar guacd -> jumphost_app, aber der
|
|
# Handler-Flush ist rollenuebergreifend ans Play-Ende gebunden) -- der
|
|
# allererste App-Start faende dann einen noch alten/toten guacd-Prozess vor.
|
|
ansible.builtin.meta: flush_handlers
|
|
|
|
- name: guacd aktivieren und starten (unabhaengig davon, ob gerade ein Handler ausgeloest wurde)
|
|
# Befund 1: die Rolle hatte bislang KEINEN expliziten service-Task, nur
|
|
# den Handler "restart guacd" (laeuft ausschliesslich bei einer
|
|
# Aenderung). Ein zweiter Ansible-Lauf ohne Aenderung meldete bisher
|
|
# ueberall "ok", auch wenn guacd zwischenzeitlich (Absturz, manuelles
|
|
# "systemctl stop", OOM-Kill) tot war -- das ist nach dem Debian-12-Wechsel
|
|
# die wahrscheinlichste verbleibende Ursache fuer
|
|
# "[Errno 111] Connection refused" auf einem sonst funktionierenden Build.
|
|
ansible.builtin.service:
|
|
name: guacd
|
|
state: started
|
|
enabled: true
|
|
|
|
- name: Warten, bis guacd tatsaechlich auf dem konfigurierten Port lauscht
|
|
# Befund 2: keine Startpruefung. Ohne wait_for bleibt ein fehlgeschlagener
|
|
# Start unbemerkt, bis der erste Benutzer eine RDP-Sitzung versucht --
|
|
# genau das Symptom aus dem gemeldeten Vorfall (Teil A.0).
|
|
ansible.builtin.wait_for:
|
|
host: 127.0.0.1
|
|
port: "{{ guacd_port }}"
|
|
timeout: 15
|
|
state: started
|
|
register: _guacd_port_check
|
|
ignore_errors: true
|
|
|
|
- name: Abbrechen mit Klartext, wenn guacd nicht auf dem konfigurierten Port lauscht
|
|
ansible.builtin.fail:
|
|
msg: >-
|
|
guacd lauscht nach dem Start NICHT auf 127.0.0.1:{{ guacd_port }}
|
|
(Timeout nach 15s). Auf dem Zielsystem pruefen: 'systemctl status
|
|
guacd', 'journalctl -u guacd -n 50', 'ss -ltnp | grep {{ guacd_port }}'.
|
|
Haeufigste Ursachen laut Umsetzungsauftrag_Sonnet5.md Teil A.2: das
|
|
systemd-Hardening-Drop-in (ProtectSystem=strict) blockiert ein von
|
|
guacd benoetigtes Laufzeit-/Schreibverzeichnis, oder
|
|
/etc/guacamole/guacd.conf wird wegen eines fehlenden
|
|
--sysconfdir=/etc beim configure-Lauf gar nicht erst gelesen.
|
|
when: _guacd_port_check is failed
|
|
|
|
- name: Smoke-Test - lauschenden Port gegen die App-Konfiguration abgleichen
|
|
# Letzter Punkt aus der "Zu tun"-Liste (Teil A.2): der oben erfolgreich
|
|
# geprüfte Port ist bereits exakt derselbe Wert wie guacd_port, aus dem
|
|
# sowohl diese Rolle als auch app/config.py::settings.guacd_port (ueber
|
|
# die Rolle jumphost_app) gespeist werden -- ein Auseinanderlaufen ist
|
|
# durch die gemeinsame Variable strukturell ausgeschlossen. Trotzdem
|
|
# explizit sichtbar machen, damit ein kuenftiger Review sofort sieht, WO
|
|
# dieser Abgleich stattfindet, falls App- und guacd-Port je entkoppelt
|
|
# werden sollten.
|
|
ansible.builtin.debug:
|
|
msg: >-
|
|
guacd-Smoke-Test erfolgreich: guacd lauscht auf
|
|
127.0.0.1:{{ guacd_port }}, identisch mit dem von der App erwarteten
|
|
Port (Variable guacd_port, siehe app/config.py::settings.guacd_port).
|