second commit
This commit is contained in:
35
ansible/inventory/group_vars/all.yml
Normal file
35
ansible/inventory/group_vars/all.yml
Normal file
@ -0,0 +1,35 @@
|
||||
---
|
||||
# Gemeinsame Variablen. Umgebungsspezifische Werte in
|
||||
# jumphost_with_nginx.yml / jumphost_no_nginx.yml ueberschreiben
|
||||
# (oder direkt hier anpassen, wenn nur eine Umgebung existiert).
|
||||
|
||||
jumphost_app_user: jumphost
|
||||
jumphost_app_group: jumphost
|
||||
jumphost_home: /opt/jumphost
|
||||
jumphost_data_dir: /var/lib/jumphost
|
||||
jumphost_venv: "{{ jumphost_home }}/venv"
|
||||
jumphost_repo_src: "{{ playbook_dir }}/../.." # Projekt-Root (enthaelt app/, static/, templates/)
|
||||
|
||||
jumphost_listen_uds: /run/jumphost/app.sock
|
||||
jumphost_app_port: 8443 # nur relevant wenn enable_nginx_proxy: false
|
||||
|
||||
guacd_port: 4822
|
||||
|
||||
# --- nginx / TLS ------------------------------------------------------------
|
||||
enable_nginx_proxy: true
|
||||
tls_mode: internal_pki # internal_pki | external_reverse_proxy | acme_public
|
||||
# Nur fuer tls_mode: external_reverse_proxy relevant:
|
||||
external_reverse_proxy_cidr: "10.10.5.0/24"
|
||||
internal_hop_plaintext_accepted: false # bewusste Ausnahme, siehe Konzept 7.2a
|
||||
# Nur fuer tls_mode: acme_public relevant:
|
||||
acme_domain: "jumphost.example.com"
|
||||
acme_email: "admin@example.com"
|
||||
|
||||
# --- Firewall ----------------------------------------------------------------
|
||||
ssh_admin_access_cidr: "10.10.1.0/24" # Management-Netz fuer SSH-Zugriff AUF den Jumphost selbst
|
||||
target_networks: # Netze der Zielsysteme (fuer ausgehende Regeln)
|
||||
- "10.20.0.0/16"
|
||||
|
||||
# --- Backup --------------------------------------------------------------------
|
||||
backup_dir: /var/backups/jumphost
|
||||
backup_retention_days: 30
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
# Beispiel-Override fuer eine Umgebung OHNE nginx (siehe Konzept 7.2/9):
|
||||
# Die Python-App terminiert TLS direkt.
|
||||
enable_nginx_proxy: false
|
||||
jumphost_app_port: 8443
|
||||
@ -0,0 +1,4 @@
|
||||
---
|
||||
# Beispiel-Override fuer eine Umgebung MIT nginx (siehe Konzept 7.2/9).
|
||||
enable_nginx_proxy: true
|
||||
tls_mode: internal_pki
|
||||
6
ansible/inventory/group_vars/vault.yml.example
Normal file
6
ansible/inventory/group_vars/vault.yml.example
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
# Mit `ansible-vault encrypt group_vars/vault.yml` verschluesseln, NIEMALS
|
||||
# im Klartext committen (siehe Konzept 6.4/7.3).
|
||||
vault_jumphost_kek: "CHANGE_ME_32_BYTES_HEX_0123456789abcdef0123456789abcdef"
|
||||
vault_jumphost_session_secret: "CHANGE_ME_32_BYTES_HEX_0123456789abcdef0123456789abcdef"
|
||||
vault_jumphost_initial_admin_password: "CHANGE_ME_STRONG_PASSWORD"
|
||||
6
ansible/inventory/production.ini.example
Normal file
6
ansible/inventory/production.ini.example
Normal file
@ -0,0 +1,6 @@
|
||||
; Beispiel-Inventory. Kopieren nach production.ini und anpassen.
|
||||
[jumphosts]
|
||||
jumphost01.example.internal ansible_user=deploy
|
||||
|
||||
[jumphosts:vars]
|
||||
ansible_python_interpreter=/usr/bin/python3
|
||||
4
ansible/roles/backup/handlers/main.yml
Normal file
4
ansible/roles/backup/handlers/main.yml
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
- name: reload systemd
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
47
ansible/roles/backup/tasks/main.yml
Normal file
47
ansible/roles/backup/tasks/main.yml
Normal file
@ -0,0 +1,47 @@
|
||||
---
|
||||
- name: Backup-Verzeichnis anlegen
|
||||
ansible.builtin.file:
|
||||
path: "{{ backup_dir }}"
|
||||
state: directory
|
||||
owner: "{{ jumphost_app_user }}"
|
||||
group: "{{ jumphost_app_group }}"
|
||||
mode: "0700"
|
||||
|
||||
- name: age installieren (Backup-Verschluesselung)
|
||||
ansible.builtin.apt:
|
||||
name: age
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Backup-Skript ausrollen
|
||||
ansible.builtin.template:
|
||||
src: backup.sh.j2
|
||||
dest: "{{ jumphost_home }}/scripts/backup.sh"
|
||||
owner: "{{ jumphost_app_user }}"
|
||||
group: "{{ jumphost_app_group }}"
|
||||
mode: "0750"
|
||||
|
||||
- name: systemd-Service fuer Backup ausrollen
|
||||
ansible.builtin.template:
|
||||
src: jumphost-backup.service.j2
|
||||
dest: /etc/systemd/system/jumphost-backup.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: reload systemd
|
||||
|
||||
- name: systemd-Timer fuer Backup ausrollen
|
||||
ansible.builtin.template:
|
||||
src: jumphost-backup.timer.j2
|
||||
dest: /etc/systemd/system/jumphost-backup.timer
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: reload systemd
|
||||
|
||||
- name: Backup-Timer aktivieren
|
||||
ansible.builtin.systemd:
|
||||
name: jumphost-backup.timer
|
||||
daemon_reload: true
|
||||
enabled: true
|
||||
state: started
|
||||
17
ansible/roles/backup/templates/backup.sh.j2
Normal file
17
ansible/roles/backup/templates/backup.sh.j2
Normal file
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
# Konsistentes, verschluesseltes SQLite-Backup (Konzept 6.8).
|
||||
# KEK-Backup erfolgt bewusst GETRENNT (siehe Konzept 6.8) -- dieses Skript
|
||||
# sichert ausschliesslich die Datenbank, kein Schluesselmaterial.
|
||||
set -euo pipefail
|
||||
|
||||
DB_PATH="{{ jumphost_data_dir }}/jumphost.db"
|
||||
BACKUP_DIR="{{ backup_dir }}"
|
||||
TS="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
OUT_PLAIN="${BACKUP_DIR}/jumphost_${TS}.db"
|
||||
OUT_ENC="${OUT_PLAIN}.age"
|
||||
|
||||
sqlite3 "$DB_PATH" "VACUUM INTO '${OUT_PLAIN}'"
|
||||
age -r "{{ backup_age_public_key | default('AGE_PUBLIC_KEY_PLACEHOLDER') }}" -o "${OUT_ENC}" "${OUT_PLAIN}"
|
||||
shred -u "${OUT_PLAIN}"
|
||||
|
||||
find "$BACKUP_DIR" -name 'jumphost_*.db.age' -mtime +{{ backup_retention_days }} -delete
|
||||
16
ansible/roles/backup/templates/jumphost-backup.service.j2
Normal file
16
ansible/roles/backup/templates/jumphost-backup.service.j2
Normal file
@ -0,0 +1,16 @@
|
||||
[Unit]
|
||||
Description=Jumphost verschluesseltes DB-Backup
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User={{ jumphost_app_user }}
|
||||
Group={{ jumphost_app_group }}
|
||||
ExecStart=/usr/bin/env bash {{ jumphost_home }}/scripts/backup.sh
|
||||
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
ReadWritePaths={{ backup_dir }} {{ jumphost_data_dir }}
|
||||
CapabilityBoundingSet=
|
||||
UMask=0077
|
||||
10
ansible/roles/backup/templates/jumphost-backup.timer.j2
Normal file
10
ansible/roles/backup/templates/jumphost-backup.timer.j2
Normal file
@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=Taeglicher Jumphost-Backup-Timer
|
||||
|
||||
[Timer]
|
||||
OnCalendar=daily
|
||||
RandomizedDelaySec=1800
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
5
ansible/roles/fail2ban/handlers/main.yml
Normal file
5
ansible/roles/fail2ban/handlers/main.yml
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: restart fail2ban
|
||||
ansible.builtin.service:
|
||||
name: fail2ban
|
||||
state: restarted
|
||||
38
ansible/roles/fail2ban/tasks/main.yml
Normal file
38
ansible/roles/fail2ban/tasks/main.yml
Normal file
@ -0,0 +1,38 @@
|
||||
---
|
||||
# Zweite Verteidigungslinie gegen Brute-Force zusaetzlich zum
|
||||
# Anwendungs-Rate-Limiting (Konzept 6.2). Ueberwacht die Uvicorn-/nginx-
|
||||
# Access-Logs auf gehaeufte 401-Antworten vom Login-Endpunkt.
|
||||
|
||||
- name: fail2ban installieren
|
||||
ansible.builtin.apt:
|
||||
name: fail2ban
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Filter fuer Jumphost-Login-Fehlversuche
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/fail2ban/filter.d/jumphost-login.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
[Definition]
|
||||
failregex = ^.*"POST /auth/login(/totp)? HTTP/.*" 401 .*client_ip=<HOST>.*$
|
||||
^.*"POST /auth/login(/totp)? HTTP/.*" 401 .*<HOST>.*$
|
||||
ignoreregex =
|
||||
|
||||
- name: Jail fuer Jumphost-Login aktivieren
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/fail2ban/jail.d/jumphost.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
[jumphost-login]
|
||||
enabled = true
|
||||
filter = jumphost-login
|
||||
logpath = /var/log/jumphost/access.log
|
||||
maxretry = 8
|
||||
findtime = 300
|
||||
bantime = 1800
|
||||
notify: restart fail2ban
|
||||
5
ansible/roles/firewall_nftables/handlers/main.yml
Normal file
5
ansible/roles/firewall_nftables/handlers/main.yml
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: reload nftables
|
||||
ansible.builtin.service:
|
||||
name: nftables
|
||||
state: restarted
|
||||
25
ansible/roles/firewall_nftables/tasks/main.yml
Normal file
25
ansible/roles/firewall_nftables/tasks/main.yml
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
# Default-Deny-Firewall (Konzept 6.7). Eingehend nur admin-SSH (aus dem
|
||||
# Management-Netz) und der oeffentliche App-/nginx-Port; ausgehend nur zu den
|
||||
# definierten Zielsystem-Netzen sowie DNS/NTP.
|
||||
|
||||
- name: nftables installieren
|
||||
ansible.builtin.apt:
|
||||
name: nftables
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: nftables-Regelsatz ausrollen
|
||||
ansible.builtin.template:
|
||||
src: jumphost.nft.j2
|
||||
dest: /etc/nftables.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0640"
|
||||
notify: reload nftables
|
||||
|
||||
- name: nftables aktivieren und starten
|
||||
ansible.builtin.service:
|
||||
name: nftables
|
||||
state: started
|
||||
enabled: true
|
||||
46
ansible/roles/firewall_nftables/templates/jumphost.nft.j2
Normal file
46
ansible/roles/firewall_nftables/templates/jumphost.nft.j2
Normal file
@ -0,0 +1,46 @@
|
||||
#!/usr/sbin/nft -f
|
||||
flush ruleset
|
||||
|
||||
table inet filter {
|
||||
chain input {
|
||||
type filter hook input priority 0; policy drop;
|
||||
|
||||
iif lo accept
|
||||
ct state established,related accept
|
||||
ct state invalid drop
|
||||
|
||||
icmp type echo-request limit rate 5/second accept
|
||||
ip6 nexthdr icmpv6 icmpv6 type echo-request limit rate 5/second accept
|
||||
|
||||
tcp dport 22 ip saddr {{ ssh_admin_access_cidr }} accept
|
||||
|
||||
{% if enable_nginx_proxy %}
|
||||
tcp dport 443 accept
|
||||
{% else %}
|
||||
tcp dport {{ jumphost_app_port }} accept
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
chain forward {
|
||||
type filter hook forward priority 0; policy drop;
|
||||
}
|
||||
|
||||
chain output {
|
||||
type filter hook output priority 0; policy drop;
|
||||
|
||||
oif lo accept
|
||||
ct state established,related accept
|
||||
|
||||
udp dport 53 accept
|
||||
tcp dport 53 accept
|
||||
udp dport 123 accept
|
||||
|
||||
{% for net in target_networks %}
|
||||
ip daddr {{ net }} accept
|
||||
{% endfor %}
|
||||
|
||||
# ACME/interne PKI (tls_certificates-Rolle) und OS-Paketquellen
|
||||
tcp dport 443 accept
|
||||
tcp dport 80 accept
|
||||
}
|
||||
}
|
||||
25
ansible/roles/frontend_assets/tasks/main.yml
Normal file
25
ansible/roles/frontend_assets/tasks/main.yml
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
# Vendored Frontend-Assets (xterm.js, guacamole-common-js). Bewusst OHNE
|
||||
# Laufzeit-CDN-Bezug (Konzept 4.1/6.6) -- der Build laeuft hier einmalig auf
|
||||
# dem Zielsystem (oder alternativ auf einem Build-Host, siehe Variante unten).
|
||||
|
||||
- name: Node.js/npm fuer den Asset-Build installieren
|
||||
ansible.builtin.apt:
|
||||
name: npm
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Frontend-Assets bauen (scripts/fetch_frontend_assets.sh)
|
||||
ansible.builtin.command:
|
||||
cmd: "bash {{ jumphost_repo_src }}/scripts/fetch_frontend_assets.sh"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
run_once: true
|
||||
register: _asset_build
|
||||
changed_when: "'Fertig' in _asset_build.stdout"
|
||||
|
||||
# Hinweis: In restriktiveren Umgebungen (Jumphost selbst ohne Internetzugang
|
||||
# fuer npm) diesen Task durch eine Kopie von vorgefertigten, im internen
|
||||
# Artefakt-Repository abgelegten vendor/-Dateien ersetzen -- die Zielstruktur
|
||||
# (static/js/vendor/*.js) bleibt identisch, siehe python_runtime-Rolle, die
|
||||
# static/ anschliessend synchronisiert.
|
||||
10
ansible/roles/guacd/handlers/main.yml
Normal file
10
ansible/roles/guacd/handlers/main.yml
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
- name: reload systemd
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: restart guacd
|
||||
ansible.builtin.service:
|
||||
name: guacd
|
||||
state: restarted
|
||||
enabled: true
|
||||
64
ansible/roles/guacd/tasks/main.yml
Normal file
64
ansible/roles/guacd/tasks/main.yml
Normal file
@ -0,0 +1,64 @@
|
||||
---
|
||||
# guacd + FreeRDP als reine Protokoll-Engine fuer RDP (Konzept 3/4.3).
|
||||
# Laeuft ausschliesslich lokal gebunden, kein Netzwerkzugriff von aussen.
|
||||
|
||||
- name: guacd und FreeRDP-Plugin installieren
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- guacd
|
||||
- libguac-client-rdp0
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Dedizierten guacd-User sicherstellen (Paket legt i.d.R. bereits einen an)
|
||||
ansible.builtin.user:
|
||||
name: guacd
|
||||
system: true
|
||||
shell: /usr/sbin/nologin
|
||||
create_home: false
|
||||
ignore_errors: true
|
||||
|
||||
- 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"
|
||||
vars:
|
||||
_dummy: true
|
||||
notify:
|
||||
- reload systemd
|
||||
- restart guacd
|
||||
20
ansible/roles/guacd/templates/guacd.service.j2
Normal file
20
ansible/roles/guacd/templates/guacd.service.j2
Normal file
@ -0,0 +1,20 @@
|
||||
; Hardening-Override fuer die vom Debian/Ubuntu-Paket mitgelieferte
|
||||
; guacd.service (Konzept 8.2). Nur lokale Kommunikation zur App (Unix-Domain
|
||||
; oder localhost) sowie ausgehend RDP zu den Zielsystemen.
|
||||
[Service]
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectControlGroups=true
|
||||
RestrictNamespaces=true
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
||||
RestrictSUIDSGID=true
|
||||
MemoryDenyWriteExecute=true
|
||||
LockPersonality=true
|
||||
CapabilityBoundingSet=
|
||||
ReadWritePaths=/var/lib/jumphost/rdp-drives
|
||||
UMask=0077
|
||||
9
ansible/roles/jumphost_app/handlers/main.yml
Normal file
9
ansible/roles/jumphost_app/handlers/main.yml
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
- name: reload systemd
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: restart jumphost-app service
|
||||
ansible.builtin.service:
|
||||
name: jumphost-app
|
||||
state: restarted
|
||||
76
ansible/roles/jumphost_app/tasks/main.yml
Normal file
76
ansible/roles/jumphost_app/tasks/main.yml
Normal file
@ -0,0 +1,76 @@
|
||||
---
|
||||
# Deployt die eigentliche Jumphost-Anwendung als gehaertete systemd-Unit.
|
||||
# KEK und Session-Secret werden ueber systemd-creds verschluesselt abgelegt
|
||||
# (Konzept 6.4) -- niemals als Klartext-Env-Variable im Unit-File.
|
||||
|
||||
- name: Konfigurationsverzeichnis anlegen
|
||||
ansible.builtin.file:
|
||||
path: /etc/jumphost
|
||||
state: directory
|
||||
owner: root
|
||||
group: "{{ jumphost_app_group }}"
|
||||
mode: "0750"
|
||||
|
||||
- name: Pruefen ob systemd-creds verfuegbar ist (systemd >= 250 empfohlen)
|
||||
ansible.builtin.command: systemd-creds --version
|
||||
register: _creds_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Warnung ausgeben, falls systemd-creds fehlt
|
||||
ansible.builtin.debug:
|
||||
msg: >
|
||||
WARNUNG: systemd-creds nicht verfuegbar. Fallback auf Env-Variablen
|
||||
(JUMPHOST_KEK/JUMPHOST_SESSION_SECRET) in einer 0600-EnvironmentFile --
|
||||
weniger sicher als LoadCredentialEncrypted=, siehe Konzept 6.4.
|
||||
when: _creds_check.rc != 0
|
||||
|
||||
- name: KEK verschluesselt ablegen (systemd-creds)
|
||||
ansible.builtin.shell: |
|
||||
set -o pipefail
|
||||
echo -n '{{ vault_jumphost_kek }}' | systemd-creds encrypt --name=jumphost_kek - /etc/jumphost/jumphost_kek.cred
|
||||
args:
|
||||
creates: /etc/jumphost/jumphost_kek.cred
|
||||
executable: /bin/bash
|
||||
when: _creds_check.rc == 0
|
||||
no_log: true
|
||||
|
||||
- name: Session-Secret verschluesselt ablegen (systemd-creds)
|
||||
ansible.builtin.shell: |
|
||||
set -o pipefail
|
||||
echo -n '{{ vault_jumphost_session_secret }}' | systemd-creds encrypt --name=jumphost_session_secret - /etc/jumphost/jumphost_session_secret.cred
|
||||
args:
|
||||
creates: /etc/jumphost/jumphost_session_secret.cred
|
||||
executable: /bin/bash
|
||||
when: _creds_check.rc == 0
|
||||
no_log: true
|
||||
|
||||
- name: Fallback-EnvironmentFile (nur falls systemd-creds fehlt)
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/jumphost/env
|
||||
owner: root
|
||||
group: "{{ jumphost_app_group }}"
|
||||
mode: "0640"
|
||||
content: |
|
||||
JUMPHOST_KEK={{ vault_jumphost_kek }}
|
||||
JUMPHOST_SESSION_SECRET={{ vault_jumphost_session_secret }}
|
||||
when: _creds_check.rc != 0
|
||||
no_log: true
|
||||
|
||||
- name: systemd-Unit fuer die Jumphost-App ausrollen
|
||||
ansible.builtin.template:
|
||||
src: jumphost-app.service.j2
|
||||
dest: /etc/systemd/system/jumphost-app.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify:
|
||||
- reload systemd
|
||||
- restart jumphost-app service
|
||||
|
||||
- name: Jumphost-App aktivieren und starten
|
||||
ansible.builtin.systemd:
|
||||
name: jumphost-app
|
||||
daemon_reload: true
|
||||
enabled: true
|
||||
state: started
|
||||
58
ansible/roles/jumphost_app/templates/jumphost-app.service.j2
Normal file
58
ansible/roles/jumphost_app/templates/jumphost-app.service.j2
Normal file
@ -0,0 +1,58 @@
|
||||
[Unit]
|
||||
Description=Jumphost Gateway Application
|
||||
After=network.target guacd.service
|
||||
Wants=guacd.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User={{ jumphost_app_user }}
|
||||
Group={{ jumphost_app_group }}
|
||||
WorkingDirectory={{ jumphost_home }}
|
||||
Environment=JUMPHOST_ENV=production
|
||||
Environment=JUMPHOST_DATA_DIR={{ jumphost_data_dir }}
|
||||
Environment=JUMPHOST_LISTEN_UDS={{ jumphost_listen_uds }}
|
||||
Environment=JUMPHOST_GUACD_HOST=127.0.0.1
|
||||
Environment=JUMPHOST_GUACD_PORT={{ guacd_port }}
|
||||
{% if not enable_nginx_proxy %}
|
||||
Environment=JUMPHOST_DIRECT_TLS=1
|
||||
{% endif %}
|
||||
|
||||
{% if _creds_check.rc == 0 %}
|
||||
LoadCredentialEncrypted=jumphost_kek:/etc/jumphost/jumphost_kek.cred
|
||||
LoadCredentialEncrypted=jumphost_session_secret:/etc/jumphost/jumphost_session_secret.cred
|
||||
{% else %}
|
||||
EnvironmentFile=/etc/jumphost/env
|
||||
{% endif %}
|
||||
|
||||
{% if enable_nginx_proxy %}
|
||||
ExecStart={{ jumphost_venv }}/bin/uvicorn app.main:app --uds {{ jumphost_listen_uds }}
|
||||
{% else %}
|
||||
ExecStart={{ jumphost_venv }}/bin/uvicorn app.main:app --host 0.0.0.0 --port {{ jumphost_app_port }} \
|
||||
--ssl-certfile /etc/jumphost/tls/server.crt --ssl-keyfile /etc/jumphost/tls/server.key
|
||||
{% endif %}
|
||||
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
# --- Hardening (Konzept 6.7/8.1) ---------------------------------------------
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectControlGroups=true
|
||||
RestrictNamespaces=true
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
||||
RestrictSUIDSGID=true
|
||||
MemoryDenyWriteExecute=true
|
||||
LockPersonality=true
|
||||
SystemCallFilter=@system-service
|
||||
SystemCallErrorNumber=EPERM
|
||||
CapabilityBoundingSet=
|
||||
ReadWritePaths={{ jumphost_data_dir }} /run/jumphost /var/log/jumphost
|
||||
UMask=0077
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
5
ansible/roles/nginx_proxy/handlers/main.yml
Normal file
5
ansible/roles/nginx_proxy/handlers/main.yml
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: reload nginx
|
||||
ansible.builtin.service:
|
||||
name: nginx
|
||||
state: reloaded
|
||||
45
ansible/roles/nginx_proxy/tasks/main.yml
Normal file
45
ansible/roles/nginx_proxy/tasks/main.yml
Normal file
@ -0,0 +1,45 @@
|
||||
---
|
||||
# nginx als vorgelagerter Reverse Proxy (Konzept 7.2/7.2a/9). Nur inkludiert
|
||||
# wenn enable_nginx_proxy: true.
|
||||
|
||||
- name: nginx installieren
|
||||
ansible.builtin.apt:
|
||||
name: nginx
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Default-vHost entfernen
|
||||
ansible.builtin.file:
|
||||
path: /etc/nginx/sites-enabled/default
|
||||
state: absent
|
||||
|
||||
- name: Proxy-Header-Snippet ausrollen (import)
|
||||
ansible.builtin.import_tasks: snippets.yml
|
||||
|
||||
- name: Jumphost-vHost-Konfiguration ausrollen
|
||||
ansible.builtin.template:
|
||||
src: jumphost.conf.j2
|
||||
dest: /etc/nginx/sites-available/jumphost.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: reload nginx
|
||||
|
||||
- name: vHost aktivieren
|
||||
ansible.builtin.file:
|
||||
src: /etc/nginx/sites-available/jumphost.conf
|
||||
dest: /etc/nginx/sites-enabled/jumphost.conf
|
||||
state: link
|
||||
notify: reload nginx
|
||||
|
||||
- name: Zugriff des nginx-Users auf den App-Unix-Socket sicherstellen
|
||||
ansible.builtin.user:
|
||||
name: www-data
|
||||
groups: "{{ jumphost_app_group }}"
|
||||
append: true
|
||||
|
||||
- name: nginx aktivieren und starten
|
||||
ansible.builtin.service:
|
||||
name: nginx
|
||||
state: started
|
||||
enabled: true
|
||||
20
ansible/roles/nginx_proxy/tasks/snippets.yml
Normal file
20
ansible/roles/nginx_proxy/tasks/snippets.yml
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
# Wird von tasks/main.yml importiert (siehe include_tasks unten ergaenzen,
|
||||
# falls Snippet-Datei separat gepflegt werden soll). Der Einfachheit halber
|
||||
# hier als eigenstaendiger Task-Block gehalten.
|
||||
- name: Snippet-Verzeichnis anlegen
|
||||
ansible.builtin.file:
|
||||
path: /etc/nginx/snippets
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Proxy-Header-Snippet ausrollen
|
||||
ansible.builtin.template:
|
||||
src: jumphost_proxy_headers.conf.j2
|
||||
dest: /etc/nginx/snippets/jumphost_proxy_headers.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: reload nginx
|
||||
63
ansible/roles/nginx_proxy/templates/jumphost.conf.j2
Normal file
63
ansible/roles/nginx_proxy/templates/jumphost.conf.j2
Normal file
@ -0,0 +1,63 @@
|
||||
# Jumphost Reverse-Proxy-Konfiguration (Konzept 6.6/7.2a/9).
|
||||
# tls_mode = {{ tls_mode }}
|
||||
|
||||
{% if tls_mode == 'external_reverse_proxy' %}
|
||||
# Nur der vorgelagerte externe Reverse Proxy darf X-Forwarded-For/-Proto
|
||||
# setzen -- sonst waere das Audit-Log-Feld client_ip faelschbar (Konzept 7.2a).
|
||||
set_real_ip_from {{ external_reverse_proxy_cidr }};
|
||||
real_ip_header X-Forwarded-For;
|
||||
real_ip_recursive on;
|
||||
{% endif %}
|
||||
|
||||
limit_req_zone $binary_remote_addr zone=jumphost_login:10m rate=10r/m;
|
||||
|
||||
server {
|
||||
{% if tls_mode == 'external_reverse_proxy' and internal_hop_plaintext_accepted %}
|
||||
# Bewusste, dokumentierte Ausnahme (Konzept 7.2a): Klartext-HTTP auf der
|
||||
# Strecke externer Proxy -> Jumphost, nur zulaessig in einem eigenen,
|
||||
# zugriffskontrollierten Netzsegment.
|
||||
listen 443;
|
||||
{% else %}
|
||||
listen 443 ssl;
|
||||
ssl_certificate /etc/jumphost/tls/server.crt;
|
||||
ssl_certificate_key /etc/jumphost/tls/server.key;
|
||||
ssl_protocols TLSv1.3;
|
||||
ssl_session_cache shared:jumphost_ssl:10m;
|
||||
{% endif %}
|
||||
|
||||
server_name {{ acme_domain | default('_') }};
|
||||
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
|
||||
add_header X-Frame-Options "DENY" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "no-referrer" always;
|
||||
|
||||
client_max_body_size 210m; # etwas ueber MAX_UPLOAD_BYTES der App (Konzept 6.6)
|
||||
|
||||
location /auth/login {
|
||||
limit_req zone=jumphost_login burst=5 nodelay;
|
||||
proxy_pass http://unix:{{ jumphost_listen_uds }}:;
|
||||
include /etc/nginx/snippets/jumphost_proxy_headers.conf;
|
||||
}
|
||||
|
||||
location /ws/ {
|
||||
proxy_pass http://unix:{{ jumphost_listen_uds }}:;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_read_timeout 3600s;
|
||||
include /etc/nginx/snippets/jumphost_proxy_headers.conf;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://unix:{{ jumphost_listen_uds }}:;
|
||||
include /etc/nginx/snippets/jumphost_proxy_headers.conf;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name {{ acme_domain | default('_') }};
|
||||
location /.well-known/acme-challenge/ { root /var/www/html; }
|
||||
location / { return 301 https://$host$request_uri; }
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
{% if tls_mode == 'external_reverse_proxy' %}
|
||||
# Vom externen Proxy gesetzte Header werden dank set_real_ip_from oben
|
||||
# vertrauensvoll uebernommen statt hier ueberschrieben zu werden.
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
|
||||
{% else %}
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
{% endif %}
|
||||
71
ansible/roles/os_hardening/CIS_STIG_MAPPING.md
Normal file
71
ansible/roles/os_hardening/CIS_STIG_MAPPING.md
Normal file
@ -0,0 +1,71 @@
|
||||
# CIS/STIG-Mapping der `os_hardening`-Rolle
|
||||
|
||||
Diese Rolle deckt eine bewusst ausgewählte, für einen dedizierten Jumphost
|
||||
(Debian/Ubuntu-Familie) relevante Teilmenge der CIS Debian/Ubuntu Linux
|
||||
Benchmarks sowie einzelner DISA-STIG-typischer Controls ab. Sie ist **kein**
|
||||
vollständiger, zertifizierter Benchmark-Lauf — dafür fehlt ein automatisiertes
|
||||
Compliance-Scanning-Tool. Empfehlung: nach dem Rollout zusätzlich mit
|
||||
`oscap xccdf eval` (OpenSCAP) gegen das jeweils zutreffende Profil prüfen und
|
||||
diese Tabelle bei Abweichungen als Startpunkt für die Nacharbeit nutzen.
|
||||
|
||||
Referenz-Control-IDs folgen der CIS-Nummerierung *sinngemäß* (Benchmark-Version
|
||||
kann je nach Debian/Ubuntu-Release leicht abweichen); wo passend ist zusätzlich
|
||||
vermerkt, wenn ein Control STIG-typisch, aber nicht CIS-nummeriert ist.
|
||||
|
||||
| Bereich | CIS-Control (sinngemäß) | Umsetzung | Status |
|
||||
|---|---|---|---|
|
||||
| Unsichere Legacy-Dienste | 2.1.x | `tasks/packages.yml` | Implementiert |
|
||||
| Automatische Sicherheitsupdates | 1.9 | `tasks/packages.yml` | Implementiert |
|
||||
| Deaktivierung seltener Dateisystemtreiber | 1.1.1.1–1.1.1.8 | `tasks/kernel_modules.yml` | Implementiert |
|
||||
| Deaktivierung seltener Netzwerkprotokolle | 3.4.1–3.4.4 | `tasks/kernel_modules.yml` | Implementiert |
|
||||
| USB-Speichermedien deaktivieren | 1.1.23 | `tasks/kernel_modules.yml` | Implementiert |
|
||||
| Netzwerk-/Kernel-sysctl-Härtung | 3.x, 1.5.x | `tasks/sysctl.yml` | Implementiert |
|
||||
| Core Dumps deaktivieren | 1.5.1 | `tasks/sysctl.yml` | Implementiert |
|
||||
| `/tmp`, `/dev/shm` mit noexec/nosuid/nodev | 1.1.2.x | `tasks/mounts.yml` | **Best-Effort** — nur wirksam, wenn bereits eigene Mountpoints existieren (siehe unten) |
|
||||
| Passwortqualität (Länge/Komplexität) | 5.4.1 | `tasks/pam_password_policy.yml` | Implementiert (lokale OS-Konten) |
|
||||
| Account-Lockout (pam_faillock) | 5.3.1 | `tasks/pam_password_policy.yml` | Implementiert, siehe Einschränkung unten |
|
||||
| Passwort-Historie | 5.4.2 | `tasks/pam_password_policy.yml` | Implementiert |
|
||||
| Passwort-Ablauf (`login.defs`) | 5.4.1.1–5.4.1.4 | `tasks/pam_password_policy.yml` | Implementiert |
|
||||
| Erweiterte auditd-Regeln (Identität, Zeit, Netzwerk, Logins, Kernelmodule) | 4.1.3–4.1.14 | `tasks/auditd.yml` | Implementiert |
|
||||
| auditd-Log-Handling (keep_logs, space_left_action) | 4.1.2.3, 4.1.2.4 | `tasks/auditd.yml` | Implementiert |
|
||||
| auditd unveränderlich (`-e 2`) | 4.1.1.4 (STIG) | `tasks/auditd.yml` | Implementiert, **standardmäßig deaktiviert** (Opt-in `os_hardening_auditd_immutable`, da Reboot zum Ändern nötig) |
|
||||
| Datei-Integritäts-Monitoring (AIDE) | 1.4.1, 1.4.2 | `tasks/aide.yml` | Implementiert |
|
||||
| Rootkit-Scanner (rkhunter, ergänzend) | STIG-typisch, nicht CIS-nummeriert | `tasks/rootkit_scan.yml` | Implementiert |
|
||||
| Anmelde-Banner | STIG-typisch (Legal Notice) | `tasks/banners.yml`, `tasks/sshd.yml` | Implementiert |
|
||||
| cron/at auf autorisierte Nutzer beschränken | 2.4.1.x | `tasks/cron_at.yml` | Implementiert |
|
||||
| Berechtigungen kritischer Dateien (`/etc/shadow` etc.) | 6.1.x | `tasks/file_permissions.yml` | Implementiert |
|
||||
| sudo-Logging (`log_input`, `log_output`, `use_pty`) | STIG-typisch | `tasks/file_permissions.yml` | Implementiert |
|
||||
| `su` auf Gruppe `sudo` beschränken | 5.6 | `tasks/file_permissions.yml` | Implementiert |
|
||||
| SSH-Daemon-Härtung (Ciphers/KEX/MACs, kein Root-Login, etc.) | 5.2.x | `tasks/sshd.yml` | Implementiert |
|
||||
| IPv6 deaktivieren | 3.1.1 | `tasks/sysctl.yml` | Implementiert, **Opt-in** (`os_hardening_disable_ipv6`, Default aus) |
|
||||
|
||||
## Bewusst nicht automatisiert (mit Begründung)
|
||||
|
||||
| Bereich | CIS-Control (sinngemäß) | Warum nicht automatisiert |
|
||||
|---|---|---|
|
||||
| Getrenntes Partitionslayout (`/tmp`, `/var`, `/var/log`, `/var/log/audit`, `/home` als eigene Partitionen) | 1.1.1–1.1.1.30 | Erfordert eine Neupartitionierung der Festplatte — nur zum Zeitpunkt der OS-Installation sinnvoll setzbar, nicht nachträglich per Ansible auf ein laufendes System ohne Datenverlustrisiko. **Empfehlung:** beim Erstellen des Basis-Images/der VM-Vorlage bereits mit diesem Layout provisionieren. |
|
||||
| Bootloader-Passwort (GRUB) | 1.4.1 (ältere CIS-Nummerierung) | Erfordert physischen/Konsolen-Zugriff zum Testen und ist bei Cloud-/Hypervisor-Images oft irrelevant oder sogar kontraproduktiv (verhindert automatisierten Neustart). Bewusst dem jeweiligen Betreiber überlassen. |
|
||||
| Volltext-Festplattenverschlüsselung | Nicht CIS-nummeriert, STIG-typisch | Muss bei der OS-Installation eingerichtet werden (LUKS o.ä.), nicht nachträglich per Ansible. In Cloud-Umgebungen häufig durch Provider-seitige Verschlüsselung (z.B. verschlüsselte Volumes) abgedeckt — separat prüfen. |
|
||||
| Physische Sicherheit / BIOS-UEFI-Passwort | Nicht CIS-nummeriert | Außerhalb der Reichweite von Ansible; organisatorische/physische Maßnahme. |
|
||||
| Zentrales Log-Forwarding an SIEM/Syslog-Server | Nicht CIS-nummeriert, aber im Jumphost-Konzept Kap. 6.9 gefordert | Erfordert Kenntnis der Ziel-SIEM-Infrastruktur (Empfänger-Host, Protokoll, TLS-Zertifikate) — als eigener Konfigurationspunkt vorgesehen, aber nicht Teil dieser Rolle (siehe Konzept Kap. 6.9, Erweiterungspunkt). |
|
||||
| Netzwerksegmentierung / Firewalling *zwischen* Zielsystemen | Außerhalb des Jumphost-Scopes | Betrifft die Netzwerkinfrastruktur rund um den Jumphost, nicht den Jumphost selbst — Bestandteil des übergeordneten Netzwerkkonzepts. |
|
||||
|
||||
## Bekannte Einschränkungen
|
||||
|
||||
- **`pam_faillock`-Einbindung** (`tasks/pam_password_policy.yml`) fügt einen
|
||||
Block direkt in `/etc/pam.d/common-auth` ein. Auf Systemen, die
|
||||
`pam-auth-update --force` als Teil eines anderen Automatisierungsschritts
|
||||
laufen lassen, kann dieser Block überschrieben werden. Für produktive
|
||||
Systeme mit häufigen PAM-Änderungen wird empfohlen, stattdessen ein
|
||||
eigenes `pam-auth-update`-Profil unter `/usr/share/pam-configs/` zu
|
||||
pflegen (sauberer, upgrade-fest) — hier aus Gründen der Nachvollziehbarkeit
|
||||
als direkter Block-Insert gehalten.
|
||||
- **`/tmp`/`/dev/shm`-Remount** wirkt nur, wenn diese Pfade bereits eigene
|
||||
Mountpoints sind. Ist das nicht der Fall, gibt die Rolle eine Debug-Meldung
|
||||
aus, ändert aber nichts automatisch am Partitionslayout (siehe Tabelle oben).
|
||||
- Diese Rolle wurde in dieser Session **nicht** gegen ein reales
|
||||
Debian/Ubuntu-System ausgeführt (kein Root-/Zielsystem verfügbar) — nur
|
||||
`ansible-playbook --syntax-check` sowie YAML-/Jinja2-Parsing wurden
|
||||
verifiziert (siehe README, Abschnitt „Was bewusst noch offen ist"). Ein
|
||||
`--check`-Lauf (Dry-Run) und ein realer Rollout-Test in einer
|
||||
Staging-Umgebung stehen vor dem Produktivbetrieb noch aus.
|
||||
47
ansible/roles/os_hardening/defaults/main.yml
Normal file
47
ansible/roles/os_hardening/defaults/main.yml
Normal file
@ -0,0 +1,47 @@
|
||||
---
|
||||
# Schalter fuer die vertiefte CIS/STIG-nahe Haertung dieser Rolle.
|
||||
# Siehe ansible/roles/os_hardening/CIS_STIG_MAPPING.md fuer die Zuordnung
|
||||
# der einzelnen Tasks zu konkreten Benchmark-Controls sowie fuer alles, was
|
||||
# hier bewusst NICHT automatisiert wird (mit Begruendung).
|
||||
|
||||
# AIDE (Dateiintegritaets-Monitoring) initialisieren + taeglichen Check-Timer
|
||||
# einrichten. Erzeugt beim ersten Lauf spuerbare I/O-Last (voller Datei-Scan).
|
||||
os_hardening_aide_enabled: true
|
||||
|
||||
# rkhunter als zusaetzlicher, ergaenzender Rootkit-/Anomalie-Scanner.
|
||||
os_hardening_rkhunter_enabled: true
|
||||
|
||||
# /tmp und /dev/shm nach Moeglichkeit mit noexec,nosuid,nodev neu einhaengen
|
||||
# (nur wenn sie BEREITS eigene Mountpoints sind -- siehe Mapping-Dokument
|
||||
# Punkt "Partitionslayout" fuer den nicht automatisierbaren Teil).
|
||||
os_hardening_restrict_tmp_mounts: true
|
||||
|
||||
# Der Anmelde-Banner-Text (STIG-typischer Warnhinweis). An lokale
|
||||
# Rechtslage/Policy anpassen.
|
||||
os_hardening_login_banner: |
|
||||
******************************************************************
|
||||
* Autorisierter Zugriff ausschliesslich fuer befugte Personen. *
|
||||
* Alle Aktivitaeten auf diesem System werden protokolliert und *
|
||||
* koennen im Rahmen von Sicherheitsuntersuchungen ausgewertet *
|
||||
* werden. Mit der Anmeldung stimmen Sie dieser Ueberwachung zu. *
|
||||
******************************************************************
|
||||
|
||||
# IPv6 komplett deaktivieren. Standardmaessig AUS (false), da dies in vielen
|
||||
# Umgebungen ungewollte Nebenwirkungen hat (z.B. wenn Zielsysteme oder das
|
||||
# Management-Netz IPv6 nutzen) -- bewusst ein Opt-in, kein stiller Default.
|
||||
os_hardening_disable_ipv6: false
|
||||
|
||||
# auditd-Konfiguration als UNVERAENDERLICH markieren (-e 2 am Ende der
|
||||
# Regeln). Das ist die STIG-konforme Einstellung, erfordert danach aber
|
||||
# einen Reboot, um die Audit-Regeln ueberhaupt noch aendern zu koennen --
|
||||
# daher standardmaessig AUS, damit iterative Playbook-Laeufe waehrend des
|
||||
# Aufbaus nicht versehentlich aussperren. Vor der finalen Abnahme auf true setzen.
|
||||
os_hardening_auditd_immutable: false
|
||||
|
||||
# Passwortrichtlinie fuer LOKALE OS-Konten auf dem Jumphost selbst (NICHT zu
|
||||
# verwechseln mit der Argon2id/TOTP-Pflicht der Jumphost-WEBANWENDUNG, die
|
||||
# unabhaengig davon in app/security/passwords.py + totp.py durchgesetzt wird).
|
||||
os_hardening_password_min_length: 14
|
||||
os_hardening_password_remember: 5
|
||||
os_hardening_faillock_deny: 5
|
||||
os_hardening_faillock_unlock_time: 900
|
||||
32
ansible/roles/os_hardening/handlers/main.yml
Normal file
32
ansible/roles/os_hardening/handlers/main.yml
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
- name: restart auditd
|
||||
ansible.builtin.service:
|
||||
name: auditd
|
||||
state: restarted
|
||||
|
||||
- name: restart sshd
|
||||
ansible.builtin.service:
|
||||
name: ssh
|
||||
state: restarted
|
||||
|
||||
- name: apply sysctl
|
||||
ansible.builtin.command: sysctl --system
|
||||
changed_when: true
|
||||
|
||||
- name: update initramfs
|
||||
ansible.builtin.command: update-initramfs -u
|
||||
changed_when: true
|
||||
|
||||
- name: remount tmp
|
||||
ansible.builtin.command: mount -o remount /tmp
|
||||
changed_when: true
|
||||
failed_when: false # best effort, siehe Kommentar in tasks/mounts.yml
|
||||
|
||||
- name: remount shm
|
||||
ansible.builtin.command: mount -o remount /dev/shm
|
||||
changed_when: true
|
||||
failed_when: false
|
||||
|
||||
- name: reload systemd (os_hardening)
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
86
ansible/roles/os_hardening/tasks/aide.yml
Normal file
86
ansible/roles/os_hardening/tasks/aide.yml
Normal file
@ -0,0 +1,86 @@
|
||||
---
|
||||
# CIS 1.4.x: AIDE (Advanced Intrusion Detection Environment) als
|
||||
# Datei-Integritaets-Monitoring. Ergaenzt das Anwendungs-Audit-Log
|
||||
# (manipulationssichere Hash-Chain, Konzept 6.1) um eine unabhaengige,
|
||||
# dateisystemweite Kontrolle -- erkennt z.B. Aenderungen an Systembinaries
|
||||
# oder eingeschleuste Dateien ausserhalb des Anwendungscodes.
|
||||
|
||||
- name: AIDE installieren
|
||||
ansible.builtin.apt:
|
||||
name: aide
|
||||
state: present
|
||||
update_cache: true
|
||||
when: os_hardening_aide_enabled
|
||||
|
||||
- name: Pruefen, ob bereits eine AIDE-Datenbank existiert
|
||||
ansible.builtin.stat:
|
||||
path: /var/lib/aide/aide.db
|
||||
register: _aide_db
|
||||
when: os_hardening_aide_enabled
|
||||
|
||||
- name: Initiale AIDE-Datenbank aufbauen (kann einige Minuten dauern)
|
||||
ansible.builtin.command: aideinit -y -f
|
||||
when: os_hardening_aide_enabled and not _aide_db.stat.exists
|
||||
async: 1800
|
||||
poll: 30
|
||||
|
||||
- name: AIDE-Wrapper-Skript fuer den taeglichen Check ausrollen
|
||||
ansible.builtin.copy:
|
||||
dest: /usr/local/sbin/jumphost-aide-check.sh
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0750"
|
||||
content: |
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
LOGFILE=/var/log/jumphost/aide-check.log
|
||||
/usr/bin/aide.wrapper --check >> "$LOGFILE" 2>&1 || {
|
||||
logger -p authpriv.warning "AIDE hat Dateisystem-Abweichungen gemeldet, siehe $LOGFILE"
|
||||
}
|
||||
when: os_hardening_aide_enabled
|
||||
|
||||
- name: systemd-Service fuer taeglichen AIDE-Check
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/system/jumphost-aide-check.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
[Unit]
|
||||
Description=Taeglicher AIDE-Dateiintegritaets-Check
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/sbin/jumphost-aide-check.sh
|
||||
Nice=10
|
||||
IOSchedulingClass=idle
|
||||
when: os_hardening_aide_enabled
|
||||
notify: reload systemd (os_hardening)
|
||||
|
||||
- name: systemd-Timer fuer taeglichen AIDE-Check
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/system/jumphost-aide-check.timer
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
[Unit]
|
||||
Description=Taeglicher AIDE-Dateiintegritaets-Check (Timer)
|
||||
|
||||
[Timer]
|
||||
OnCalendar=daily
|
||||
RandomizedDelaySec=3600
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
when: os_hardening_aide_enabled
|
||||
notify: reload systemd (os_hardening)
|
||||
|
||||
- name: AIDE-Check-Timer aktivieren
|
||||
ansible.builtin.systemd:
|
||||
name: jumphost-aide-check.timer
|
||||
daemon_reload: true
|
||||
enabled: true
|
||||
state: started
|
||||
when: os_hardening_aide_enabled
|
||||
98
ansible/roles/os_hardening/tasks/auditd.yml
Normal file
98
ansible/roles/os_hardening/tasks/auditd.yml
Normal file
@ -0,0 +1,98 @@
|
||||
---
|
||||
# CIS 4.1.x / DISA-STIG-aehnliche Audit-Regeln. Ueber die bereits vorhandene
|
||||
# Ueberwachung des Jumphost-Datenverzeichnisses hinaus wird hier ein
|
||||
# Standard-Ruleset fuer sicherheitsrelevante OS-Ereignisse ergaenzt:
|
||||
# Identitaets-/Rechteaenderungen, privilegierte Kommandos, Zeit-/
|
||||
# Netzwerkkonfigurationsaenderungen, Login-Ereignisse, Modulladen.
|
||||
|
||||
- name: auditd + audispd-plugins installieren
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- auditd
|
||||
- audispd-plugins
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Bestehende auditd-Regeln fuer das Jumphost-Datenverzeichnis
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/audit/rules.d/10-jumphost-app.rules
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0640"
|
||||
content: |
|
||||
-w {{ jumphost_data_dir }}/jumphost.db -p wa -k jumphost_db
|
||||
-w {{ jumphost_home }} -p wa -k jumphost_app_files
|
||||
-w /etc/jumphost -p wa -k jumphost_config
|
||||
notify: restart auditd
|
||||
|
||||
- name: Erweiterte CIS/STIG-Audit-Regeln fuer das Basissystem
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/audit/rules.d/20-cis-baseline.rules
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0640"
|
||||
content: |
|
||||
# Identitaets-/Rechteaenderungen (CIS 4.1.4)
|
||||
-w /etc/passwd -p wa -k identity
|
||||
-w /etc/group -p wa -k identity
|
||||
-w /etc/shadow -p wa -k identity
|
||||
-w /etc/gshadow -p wa -k identity
|
||||
-w /etc/sudoers -p wa -k identity
|
||||
-w /etc/sudoers.d/ -p wa -k identity
|
||||
|
||||
# Sudo-Nutzung protokollieren (ergaenzt Defaults logfile in
|
||||
# sudo_logging.yml um eine auditd-seitige, manipulationsresistentere Spur)
|
||||
-a always,exit -F arch=b64 -S execve -F euid=0 -F auid!=0 -F auid!=-1 -k privileged_sudo
|
||||
|
||||
# Zeitaenderungen (CIS 4.1.3)
|
||||
-a always,exit -F arch=b64 -S adjtimex,settimeofday,clock_settime -k time_change
|
||||
-w /etc/localtime -p wa -k time_change
|
||||
|
||||
# Netzwerkkonfiguration (CIS 4.1.7)
|
||||
-w /etc/hosts -p wa -k network_config
|
||||
-w /etc/network/ -p wa -k network_config
|
||||
-w /etc/nftables.conf -p wa -k network_config
|
||||
|
||||
# Login/Logout-Ereignisse (CIS 4.1.5)
|
||||
-w /var/log/faillog -p wa -k logins
|
||||
-w /var/log/lastlog -p wa -k logins
|
||||
-w /var/run/utmp -p wa -k session
|
||||
-w /var/log/wtmp -p wa -k session
|
||||
-w /var/log/btmp -p wa -k session
|
||||
|
||||
# Kernel-Modul-Laden/-Entladen (CIS 4.1.13)
|
||||
-a always,exit -F arch=b64 -S init_module,delete_module -k kernel_modules
|
||||
|
||||
# SSH-Konfigurationsaenderungen des Jumphosts selbst
|
||||
-w /etc/ssh/sshd_config -p wa -k sshd_config
|
||||
-w /etc/ssh/sshd_config.d/ -p wa -k sshd_config
|
||||
|
||||
# Loeschungen durch Nutzer (CIS 4.1.14, exemplarisch fuer den eigenen UID-Bereich)
|
||||
-a always,exit -F arch=b64 -S unlink,unlinkat,rename,renameat -F auid>=1000 -F auid!=-1 -k file_deletion
|
||||
notify: restart auditd
|
||||
|
||||
- name: auditd-Regeln als unveraenderlich markieren (STIG, optional)
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/audit/rules.d/99-immutable.rules
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0640"
|
||||
content: |
|
||||
# ACHTUNG: nach Aktivierung sind Aenderungen an den Audit-Regeln erst
|
||||
# nach einem Reboot wieder moeglich (auditctl -e 2 sperrt bis Neustart).
|
||||
-e 2
|
||||
when: os_hardening_auditd_immutable
|
||||
notify: restart auditd
|
||||
|
||||
- name: auditd-Log-Rotation auf "keep_logs" setzen statt Ueberschreiben (CIS 4.1.2.3)
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/audit/auditd.conf
|
||||
regexp: '^max_log_file_action\s*='
|
||||
line: "max_log_file_action = keep_logs"
|
||||
notify: restart auditd
|
||||
|
||||
- name: auditd bei vollem Log-Speicher anhalten statt Ereignisse zu verwerfen (CIS 4.1.2.4)
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/audit/auditd.conf
|
||||
regexp: '^space_left_action\s*='
|
||||
line: "space_left_action = email"
|
||||
22
ansible/roles/os_hardening/tasks/banners.yml
Normal file
22
ansible/roles/os_hardening/tasks/banners.yml
Normal file
@ -0,0 +1,22 @@
|
||||
---
|
||||
# STIG-typische Anmelde-Warnhinweise (rechtlich in vielen Organisationen
|
||||
# vorgeschrieben, bevor Zugriff auf ein System gewaehrt wird).
|
||||
|
||||
- name: Anmelde-Banner setzen (/etc/issue, /etc/issue.net, /etc/motd)
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ item }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: "{{ os_hardening_login_banner }}"
|
||||
loop:
|
||||
- /etc/issue
|
||||
- /etc/issue.net
|
||||
- /etc/motd
|
||||
# Kein Serviceneustart noetig: /etc/issue* und /etc/motd werden bei
|
||||
# jedem neuen Login/jeder neuen Verbindung frisch gelesen.
|
||||
|
||||
# Die "Banner /etc/issue.net"-Direktive fuer sshd wird zentral in sshd.yml
|
||||
# gesetzt (dieselbe Datei /etc/ssh/sshd_config.d/99-jumphost-hardening.conf
|
||||
# wird dort komplett -- inkl. Banner-Zeile -- verwaltet, um zwei Tasks mit
|
||||
# widerspruechlichem "wer besitzt diese Datei" zu vermeiden).
|
||||
36
ansible/roles/os_hardening/tasks/cron_at.yml
Normal file
36
ansible/roles/os_hardening/tasks/cron_at.yml
Normal file
@ -0,0 +1,36 @@
|
||||
---
|
||||
# CIS 2.4.1.x: cron/at auf autorisierte Nutzer beschraenken.
|
||||
|
||||
- name: cron.deny/at.deny entfernen (deny-Listen sind fehleranfaelliger als allow-Listen)
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
loop:
|
||||
- /etc/cron.deny
|
||||
- /etc/at.deny
|
||||
|
||||
- name: cron.allow / at.allow auf root und den Jumphost-Service-User beschraenken
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ item }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
content: |
|
||||
root
|
||||
loop:
|
||||
- /etc/cron.allow
|
||||
- /etc/at.allow
|
||||
|
||||
- name: Berechtigungen der cron-Verzeichnisse absichern (CIS 2.4.1.7-2.4.1.11)
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0700"
|
||||
loop:
|
||||
- /etc/cron.d
|
||||
- /etc/cron.daily
|
||||
- /etc/cron.hourly
|
||||
- /etc/cron.monthly
|
||||
- /etc/cron.weekly
|
||||
ignore_errors: true # nicht jedes Basis-Image legt alle Verzeichnisse an
|
||||
38
ansible/roles/os_hardening/tasks/file_permissions.yml
Normal file
38
ansible/roles/os_hardening/tasks/file_permissions.yml
Normal file
@ -0,0 +1,38 @@
|
||||
---
|
||||
# CIS 6.1.x: Berechtigungen sicherheitskritischer Systemdateien; zusaetzlich
|
||||
# manipulationssicheres sudo-Logging (ergaenzt die auditd-Regel
|
||||
# "privileged_sudo" aus auditd.yml um ein menschenlesbares Log).
|
||||
|
||||
- name: Berechtigungen sicherheitskritischer Dateien absichern
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.path }}"
|
||||
owner: root
|
||||
group: "{{ item.group }}"
|
||||
mode: "{{ item.mode }}"
|
||||
loop:
|
||||
- { path: /etc/passwd, group: root, mode: "0644" }
|
||||
- { path: /etc/group, group: root, mode: "0644" }
|
||||
- { path: /etc/shadow, group: shadow, mode: "0640" }
|
||||
- { path: /etc/gshadow, group: shadow, mode: "0640" }
|
||||
- { path: /etc/ssh/sshd_config, group: root, mode: "0600" }
|
||||
ignore_errors: true # z.B. wenn die shadow-Gruppe distributionsabhaengig anders heisst
|
||||
|
||||
- name: Eigenstaendiges sudo-Logfile aktivieren
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/sudoers.d/99-jumphost-logging
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0440"
|
||||
validate: "visudo -cf %s"
|
||||
content: |
|
||||
Defaults logfile="/var/log/sudo.log"
|
||||
Defaults log_input, log_output
|
||||
Defaults use_pty
|
||||
Defaults passwd_tries=3
|
||||
|
||||
- name: su-Kommando auf die Gruppe "sudo" beschraenken (CIS 5.6)
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/pam.d/su
|
||||
regexp: '^#?\s*auth\s+required\s+pam_wheel\.so'
|
||||
line: "auth required pam_wheel.so use_uid group=sudo"
|
||||
insertafter: '^# Uncomment this'
|
||||
41
ansible/roles/os_hardening/tasks/kernel_modules.yml
Normal file
41
ansible/roles/os_hardening/tasks/kernel_modules.yml
Normal file
@ -0,0 +1,41 @@
|
||||
---
|
||||
# CIS 1.1.1.x: selten benoetigte Dateisysteme und exotische Netzwerk-
|
||||
# protokolle per modprobe-Blacklist deaktivieren. Ein dedizierter Jumphost
|
||||
# braucht keines davon; jedes deaktivierte Modul ist Angriffsflaeche weniger
|
||||
# (u.a. relevant fuer angeschlossene Wechseldatentraeger und historische
|
||||
# Kernel-CVEs in selten gepflegten Dateisystemtreibern).
|
||||
|
||||
- name: Kernelmodule fuer seltene Dateisysteme/Protokolle blacklisten
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/modprobe.d/jumphost-blacklist.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
# CIS 1.1.1.1 - 1.1.1.8 (sinngemaess): unbenoetigte Dateisysteme
|
||||
install cramfs /bin/false
|
||||
install freevxfs /bin/false
|
||||
install jffs2 /bin/false
|
||||
install hfs /bin/false
|
||||
install hfsplus /bin/false
|
||||
install udf /bin/false
|
||||
install squashfs /bin/false
|
||||
# CIS 3.4.x (sinngemaess): unbenoetigte/seltene Netzwerkprotokolle
|
||||
install dccp /bin/false
|
||||
install sctp /bin/false
|
||||
install rds /bin/false
|
||||
install tipc /bin/false
|
||||
notify: update initramfs
|
||||
|
||||
- name: USB-Speichermedien deaktivieren (CIS 1.1.23, sinngemaess)
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/modprobe.d/jumphost-usb-storage.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
# Ein Jumphost sollte keine lokal angeschlossenen USB-Speichermedien
|
||||
# einbinden muessen -- Dateitransfer laeuft ausschliesslich ueber die
|
||||
# Anwendung (SFTP/RDP-Laufwerksumleitung), siehe Konzept 4.3.
|
||||
install usb-storage /bin/false
|
||||
notify: update initramfs
|
||||
47
ansible/roles/os_hardening/tasks/main.yml
Normal file
47
ansible/roles/os_hardening/tasks/main.yml
Normal file
@ -0,0 +1,47 @@
|
||||
---
|
||||
# Betriebssystem-Haertung fuer den Jumphost-Server selbst (Konzept 6.7),
|
||||
# vertieft auf CIS/STIG-nahes Niveau. Siehe CIS_STIG_MAPPING.md in diesem
|
||||
# Rollenverzeichnis fuer die Zuordnung der einzelnen Tasks zu konkreten
|
||||
# Benchmark-Controls sowie fuer alles, was bewusst NICHT automatisiert wird
|
||||
# (mit Begruendung, z.B. Partitionslayout, Bootloader-Passwort, physische
|
||||
# Sicherheit).
|
||||
#
|
||||
# Kein Ersatz fuer ein vollstaendiges CIS/STIG-Auditwerkzeug (z.B. OpenSCAP)
|
||||
# -- jeder produktive Rollout sollte zusaetzlich mit `oscap xccdf eval`
|
||||
# gegen das jeweilige Benchmark-Profil verifiziert werden (siehe README).
|
||||
|
||||
- name: Pakete & automatische Updates
|
||||
ansible.builtin.import_tasks: packages.yml
|
||||
|
||||
- name: Kernelmodule (Dateisysteme/Protokolle) einschraenken
|
||||
ansible.builtin.import_tasks: kernel_modules.yml
|
||||
|
||||
- name: sysctl- und Core-Dump-Haertung
|
||||
ansible.builtin.import_tasks: sysctl.yml
|
||||
|
||||
- name: Mount-Optionen (/tmp, /dev/shm)
|
||||
ansible.builtin.import_tasks: mounts.yml
|
||||
|
||||
- name: PAM-/Passwort-Policy fuer lokale OS-Konten
|
||||
ansible.builtin.import_tasks: pam_password_policy.yml
|
||||
|
||||
- name: Erweiterte auditd-Regeln
|
||||
ansible.builtin.import_tasks: auditd.yml
|
||||
|
||||
- name: AIDE-Dateiintegritaets-Monitoring
|
||||
ansible.builtin.import_tasks: aide.yml
|
||||
|
||||
- name: rkhunter-Rootkit-Scanner
|
||||
ansible.builtin.import_tasks: rootkit_scan.yml
|
||||
|
||||
- name: Anmelde-Banner
|
||||
ansible.builtin.import_tasks: banners.yml
|
||||
|
||||
- name: cron/at auf autorisierte Nutzer beschraenken
|
||||
ansible.builtin.import_tasks: cron_at.yml
|
||||
|
||||
- name: Dateirechte & sudo-Logging
|
||||
ansible.builtin.import_tasks: file_permissions.yml
|
||||
|
||||
- name: SSH-Daemon des Jumphosts haerten
|
||||
ansible.builtin.import_tasks: sshd.yml
|
||||
48
ansible/roles/os_hardening/tasks/mounts.yml
Normal file
48
ansible/roles/os_hardening/tasks/mounts.yml
Normal file
@ -0,0 +1,48 @@
|
||||
---
|
||||
# CIS 1.1.2.x (nodev/nosuid/noexec auf /tmp, /dev/shm). Best-Effort: nur
|
||||
# wirksam, wenn diese Pfade BEREITS eigene Mountpoints sind. Ob das der Fall
|
||||
# ist, haengt vom Partitionslayout des Basis-Images ab -- siehe
|
||||
# CIS_STIG_MAPPING.md fuer den Hinweis, dass ein vollstaendig CIS-konformes
|
||||
# Partitionslayout (separate /tmp, /var, /var/log, /var/log/audit, /home)
|
||||
# eine bewusste Entscheidung bei der OS-Installation ist und nicht nachtraeglich
|
||||
# per Ansible auf ein bestehendes System aufgepraegt werden kann, ohne die
|
||||
# Platte neu zu partitionieren.
|
||||
|
||||
- name: Pruefen, ob /tmp ein eigener Mountpoint ist
|
||||
ansible.builtin.command: findmnt --noheadings --output SOURCE /tmp
|
||||
register: _tmp_mount
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: /tmp mit noexec,nosuid,nodev in /etc/fstab absichern (falls eigener Mountpoint)
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/fstab
|
||||
regexp: '^\S+\s+/tmp\s+'
|
||||
line: "{{ _tmp_mount.stdout }} /tmp tmpfs defaults,noexec,nosuid,nodev 0 0"
|
||||
backup: true
|
||||
when: os_hardening_restrict_tmp_mounts and _tmp_mount.rc == 0 and _tmp_mount.stdout != ''
|
||||
notify: remount tmp
|
||||
|
||||
- name: Pruefen, ob /dev/shm ein eigener Mountpoint ist
|
||||
ansible.builtin.command: findmnt --noheadings --output SOURCE /dev/shm
|
||||
register: _shm_mount
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: /dev/shm mit noexec,nosuid,nodev in /etc/fstab absichern (falls eigener Mountpoint)
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/fstab
|
||||
regexp: '^\S+\s+/dev/shm\s+'
|
||||
line: "{{ _shm_mount.stdout }} /dev/shm tmpfs defaults,noexec,nosuid,nodev 0 0"
|
||||
backup: true
|
||||
when: os_hardening_restrict_tmp_mounts and _shm_mount.rc == 0 and _shm_mount.stdout != ''
|
||||
notify: remount shm
|
||||
|
||||
- name: Hinweis, falls /tmp kein eigener Mountpoint ist
|
||||
ansible.builtin.debug:
|
||||
msg: >
|
||||
/tmp ist kein eigener Mountpoint auf diesem System -- noexec/nosuid/nodev
|
||||
koennen so nicht erzwungen werden. Fuer volle CIS-Konformitaet muesste
|
||||
/tmp bei der OS-Installation als eigene Partition/eigenes tmpfs angelegt
|
||||
werden (siehe CIS_STIG_MAPPING.md).
|
||||
when: os_hardening_restrict_tmp_mounts and (_tmp_mount.rc != 0 or _tmp_mount.stdout == '')
|
||||
33
ansible/roles/os_hardening/tasks/packages.yml
Normal file
33
ansible/roles/os_hardening/tasks/packages.yml
Normal file
@ -0,0 +1,33 @@
|
||||
---
|
||||
# CIS 2.x (Services) / STIG-aequivalent: unsichere Legacy-Dienste/-Clients
|
||||
# entfernen, automatische Sicherheitsupdates aktivieren.
|
||||
|
||||
- name: Unnoetige/unsichere Pakete entfernen
|
||||
ansible.builtin.apt:
|
||||
name: "{{ item }}"
|
||||
state: absent
|
||||
purge: true
|
||||
loop:
|
||||
- telnet
|
||||
- rsh-client
|
||||
- talk
|
||||
- nis # ypbind etc. -- veraltete, unverschluesselte Netzwerkdienste
|
||||
- tftpd-hpa
|
||||
- xinetd
|
||||
ignore_errors: true
|
||||
|
||||
- name: Automatische Sicherheitsupdates installieren
|
||||
ansible.builtin.apt:
|
||||
name: unattended-upgrades
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Unattended-upgrades aktivieren
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/apt/apt.conf.d/20auto-upgrades
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
APT::Periodic::Update-Package-Lists "1";
|
||||
APT::Periodic::Unattended-Upgrade "1";
|
||||
83
ansible/roles/os_hardening/tasks/pam_password_policy.yml
Normal file
83
ansible/roles/os_hardening/tasks/pam_password_policy.yml
Normal file
@ -0,0 +1,83 @@
|
||||
---
|
||||
# CIS 5.3.x / 5.4.x: Passwortqualitaet, Account-Lockout und Ablaufregeln fuer
|
||||
# LOKALE OS-Konten auf dem Jumphost selbst (Admin-SSH-Zugang zum Jumphost-
|
||||
# Server, siehe ssh_admin_access_cidr in group_vars/all.yml).
|
||||
#
|
||||
# WICHTIG -- Abgrenzung: Dies ist NICHT identisch mit der Argon2id/TOTP-
|
||||
# Pflicht der Jumphost-WEBANWENDUNG (siehe app/security/passwords.py,
|
||||
# app/security/totp.py, app/auth/routes.py). Es handelt sich um zwei
|
||||
# getrennte Konten-/Auth-Systeme: die App verwaltet ihre eigenen Nutzer in
|
||||
# SQLite, waehrend hier die BS-Konten der Administratoren gehaertet werden,
|
||||
# die sich per SSH auf den Jumphost-Server selbst einloggen (z.B. fuer
|
||||
# Wartung, Deployment, Log-Einsicht).
|
||||
|
||||
- name: libpam-pwquality installieren
|
||||
ansible.builtin.apt:
|
||||
name: libpam-pwquality
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Passwortqualitaets-Policy setzen (CIS 5.4.1)
|
||||
# Direkt in pwquality.conf statt eines conf.d-Snippets, da nicht jede
|
||||
# Distributionsversion von libpam-pwquality ein conf.d-Verzeichnis
|
||||
# unterstuetzt -- pwquality.conf selbst wird ueberall gelesen.
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/security/pwquality.conf
|
||||
regexp: "^#?\\s*{{ item.key }}\\s*="
|
||||
line: "{{ item.key }} = {{ item.value }}"
|
||||
create: true
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
loop:
|
||||
- { key: "minlen", value: "{{ os_hardening_password_min_length }}" }
|
||||
- { key: "dcredit", value: "-1" }
|
||||
- { key: "ucredit", value: "-1" }
|
||||
- { key: "ocredit", value: "-1" }
|
||||
- { key: "lcredit", value: "-1" }
|
||||
- { key: "retry", value: "3" }
|
||||
# Kein Service-Neustart noetig: PAM liest die Datei bei jeder neuen
|
||||
# Authentifizierung, kein laufender Daemon haelt sie offen.
|
||||
|
||||
- name: Passwortqualitaet auch fuer root erzwingen
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/security/pwquality.conf
|
||||
regexp: "^#?\\s*enforce_for_root"
|
||||
line: "enforce_for_root"
|
||||
create: true
|
||||
|
||||
- name: pam_faillock fuer Login-Lockout aktivieren (CIS 5.3.1)
|
||||
ansible.builtin.blockinfile:
|
||||
path: /etc/pam.d/common-auth
|
||||
marker: "# {mark} ANSIBLE MANAGED BLOCK (jumphost pam_faillock)"
|
||||
insertbefore: "^auth\\s+\\[success=1"
|
||||
block: |
|
||||
auth required pam_faillock.so preauth silent deny={{ os_hardening_faillock_deny }} unlock_time={{ os_hardening_faillock_unlock_time }}
|
||||
auth [success=1 default=ignore] pam_unix.so nullok
|
||||
auth [default=die] pam_faillock.so authfail deny={{ os_hardening_faillock_deny }} unlock_time={{ os_hardening_faillock_unlock_time }}
|
||||
auth sufficient pam_faillock.so authsucc deny={{ os_hardening_faillock_deny }} unlock_time={{ os_hardening_faillock_unlock_time }}
|
||||
# Hinweis: pam-auth-update-verwaltete Systeme (Debian/Ubuntu-Standard)
|
||||
# ueberschreiben common-auth ggf. bei "pam-auth-update --force". Fuer
|
||||
# produktive Systeme ist die Nutzung eines eigenen pam-auth-update-Profils
|
||||
# (/usr/share/pam-configs/jumphost-faillock) die sauberere, upgrade-feste
|
||||
# Alternative -- hier aus Uebersichtlichkeitsgruenden als direkter Block-
|
||||
# Insert gehalten und im Mapping-Dokument als bekannte Einschraenkung vermerkt.
|
||||
|
||||
- name: Passwort-Ablaufregeln in /etc/login.defs setzen (CIS 5.4.1.1-5.4.1.4)
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/login.defs
|
||||
regexp: "^{{ item.key }}\\s"
|
||||
line: "{{ item.key }} {{ item.value }}"
|
||||
loop:
|
||||
- { key: "PASS_MAX_DAYS", value: "90" }
|
||||
- { key: "PASS_MIN_DAYS", value: "7" }
|
||||
- { key: "PASS_WARN_AGE", value: "14" }
|
||||
- { key: "UMASK", value: "027" }
|
||||
- { key: "ENCRYPT_METHOD", value: "SHA512" }
|
||||
|
||||
- name: Passwort-Historie (pam_pwhistory) aktivieren (CIS 5.4.2)
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/pam.d/common-password
|
||||
regexp: '^password\s+requisite\s+pam_pwhistory\.so'
|
||||
insertafter: '^password\s+requisite\s+pam_pwquality\.so'
|
||||
line: "password requisite pam_pwhistory.so remember={{ os_hardening_password_remember }} use_authtok"
|
||||
24
ansible/roles/os_hardening/tasks/rootkit_scan.yml
Normal file
24
ansible/roles/os_hardening/tasks/rootkit_scan.yml
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
# Ergaenzender Rootkit-/Anomalie-Scanner (rkhunter). Kein Ersatz fuer AIDE
|
||||
# (dateibasierte Integritaet) oder auditd (Ereignisprotokoll), sondern eine
|
||||
# dritte, unabhaengige Kontrollschicht mit eigener Signaturheuristik.
|
||||
|
||||
- name: rkhunter installieren
|
||||
ansible.builtin.apt:
|
||||
name: rkhunter
|
||||
state: present
|
||||
update_cache: true
|
||||
when: os_hardening_rkhunter_enabled
|
||||
|
||||
- name: rkhunter-Property-Datenbank initialisieren
|
||||
ansible.builtin.command: rkhunter --propupd
|
||||
when: os_hardening_rkhunter_enabled
|
||||
changed_when: true
|
||||
|
||||
- name: Woechentlichen rkhunter-Check per systemd-Timer aktivieren (Debian-Paket-Default nutzen)
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/default/rkhunter
|
||||
regexp: '^CRON_DAILY_RUN='
|
||||
line: 'CRON_DAILY_RUN="true"'
|
||||
create: true
|
||||
when: os_hardening_rkhunter_enabled
|
||||
32
ansible/roles/os_hardening/tasks/sshd.yml
Normal file
32
ansible/roles/os_hardening/tasks/sshd.yml
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
# SSH-Daemon des Jumphosts SELBST (administrativer Zugriff auf den Server) --
|
||||
# nicht zu verwechseln mit der SSH-Proxy-Funktion der Anwendung (app/ssh_proxy),
|
||||
# die eigene, unabhaengige Verbindungen zu den Zielsystemen aufbaut.
|
||||
# CIS 5.2.x.
|
||||
|
||||
- name: SSH-Daemon des Jumphosts haerten (vollstaendige Konfiguration inkl. Banner)
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/ssh/sshd_config.d/99-jumphost-hardening.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
PermitRootLogin no
|
||||
PasswordAuthentication no
|
||||
KbdInteractiveAuthentication no
|
||||
PermitEmptyPasswords no
|
||||
X11Forwarding no
|
||||
AllowTcpForwarding no
|
||||
AllowAgentForwarding no
|
||||
PermitTunnel no
|
||||
MaxAuthTries 3
|
||||
MaxSessions 4
|
||||
LoginGraceTime 20
|
||||
ClientAliveInterval 300
|
||||
ClientAliveCountMax 2
|
||||
Banner /etc/issue.net
|
||||
LogLevel VERBOSE
|
||||
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
|
||||
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512
|
||||
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
|
||||
notify: restart sshd
|
||||
58
ansible/roles/os_hardening/tasks/sysctl.yml
Normal file
58
ansible/roles/os_hardening/tasks/sysctl.yml
Normal file
@ -0,0 +1,58 @@
|
||||
---
|
||||
# CIS 3.x (Netzwerk) / 1.5.x (Kernel-Haertung). Bewusst ueber
|
||||
# ansible.builtin.copy + "sysctl --system" statt des ansible.posix.sysctl-
|
||||
# Moduls (siehe Kommentar in Kap. 7/README) -- keine Zusatz-Collection als
|
||||
# Voraussetzung fuer den Betrieb dieses Playbooks.
|
||||
|
||||
- name: Kernel-/Netzwerk-Haertung (sysctl)
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/sysctl.d/99-jumphost-hardening.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
# --- Netzwerk (CIS 3.x) ---
|
||||
net.ipv4.conf.all.rp_filter = 1
|
||||
net.ipv4.conf.default.rp_filter = 1
|
||||
net.ipv4.conf.all.accept_redirects = 0
|
||||
net.ipv4.conf.default.accept_redirects = 0
|
||||
net.ipv4.conf.all.secure_redirects = 0
|
||||
net.ipv4.conf.default.secure_redirects = 0
|
||||
net.ipv4.conf.all.send_redirects = 0
|
||||
net.ipv4.conf.default.send_redirects = 0
|
||||
net.ipv4.conf.all.accept_source_route = 0
|
||||
net.ipv4.conf.default.accept_source_route = 0
|
||||
net.ipv4.conf.all.log_martians = 1
|
||||
net.ipv4.conf.default.log_martians = 1
|
||||
net.ipv4.icmp_echo_ignore_broadcasts = 1
|
||||
net.ipv4.icmp_ignore_bogus_error_responses = 1
|
||||
net.ipv4.tcp_syncookies = 1
|
||||
net.ipv4.ip_forward = 0
|
||||
net.ipv6.conf.all.accept_redirects = 0
|
||||
net.ipv6.conf.default.accept_redirects = 0
|
||||
net.ipv6.conf.all.accept_source_route = 0
|
||||
net.ipv6.conf.default.accept_source_route = 0
|
||||
{% if os_hardening_disable_ipv6 %}
|
||||
net.ipv6.conf.all.disable_ipv6 = 1
|
||||
net.ipv6.conf.default.disable_ipv6 = 1
|
||||
{% endif %}
|
||||
# --- Kernel-Haertung (CIS 1.5.x) ---
|
||||
kernel.kptr_restrict = 2
|
||||
kernel.dmesg_restrict = 1
|
||||
kernel.randomize_va_space = 2
|
||||
kernel.yama.ptrace_scope = 1
|
||||
fs.suid_dumpable = 0
|
||||
fs.protected_hardlinks = 1
|
||||
fs.protected_symlinks = 1
|
||||
fs.protected_fifos = 2
|
||||
fs.protected_regular = 2
|
||||
notify: apply sysctl
|
||||
|
||||
- name: Core Dumps zusaetzlich auf PAM-Ebene deaktivieren (CIS 1.5.1)
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/security/limits.d/99-jumphost-no-coredumps.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
* hard core 0
|
||||
8
ansible/roles/python_runtime/handlers/main.yml
Normal file
8
ansible/roles/python_runtime/handlers/main.yml
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
- name: restart jumphost-app
|
||||
ansible.builtin.service:
|
||||
name: jumphost-app
|
||||
state: restarted
|
||||
# Handler laeuft ggf. bevor der Service durch jumphost_app angelegt wurde;
|
||||
# daher failed_when: false beim allerersten Deploy.
|
||||
failed_when: false
|
||||
93
ansible/roles/python_runtime/tasks/main.yml
Normal file
93
ansible/roles/python_runtime/tasks/main.yml
Normal file
@ -0,0 +1,93 @@
|
||||
---
|
||||
- name: Systembenutzer fuer die Jumphost-App anlegen
|
||||
ansible.builtin.group:
|
||||
name: "{{ jumphost_app_group }}"
|
||||
system: true
|
||||
|
||||
- name: Systembenutzer anlegen (kein Login-Shell, kein Home mit Zugriff fuer andere)
|
||||
ansible.builtin.user:
|
||||
name: "{{ jumphost_app_user }}"
|
||||
group: "{{ jumphost_app_group }}"
|
||||
system: true
|
||||
shell: /usr/sbin/nologin
|
||||
home: "{{ jumphost_home }}"
|
||||
create_home: false
|
||||
|
||||
- name: Python 3 + venv-Paket installieren
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- python3
|
||||
- python3-venv
|
||||
- python3-pip
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Verzeichnisse anlegen
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ jumphost_app_user }}"
|
||||
group: "{{ jumphost_app_group }}"
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ jumphost_home }}"
|
||||
- "{{ jumphost_data_dir }}"
|
||||
- "{{ jumphost_data_dir }}/recordings"
|
||||
- /run/jumphost
|
||||
- /var/log/jumphost
|
||||
|
||||
- name: Alten Anwendungscode entfernen (sauberes Redeploy)
|
||||
ansible.builtin.file:
|
||||
path: "{{ jumphost_home }}/{{ item }}"
|
||||
state: absent
|
||||
loop:
|
||||
- app
|
||||
- static
|
||||
- templates
|
||||
|
||||
- name: Anwendungscode kopieren
|
||||
# Bewusst ueber ansible.builtin.copy statt ansible.posix.synchronize, damit
|
||||
# das Playbook ohne zusaetzliche Collection auskommt und auch ohne rsync
|
||||
# auf Control-Node/Zielsystem funktioniert (Konzept-Anspruch: minimale
|
||||
# externe Abhaengigkeiten fuer den Deploy-Pfad selbst).
|
||||
ansible.builtin.copy:
|
||||
src: "{{ jumphost_repo_src }}/{{ item }}/"
|
||||
dest: "{{ jumphost_home }}/{{ item }}/"
|
||||
owner: "{{ jumphost_app_user }}"
|
||||
group: "{{ jumphost_app_group }}"
|
||||
loop:
|
||||
- app
|
||||
- static
|
||||
- templates
|
||||
notify: restart jumphost-app
|
||||
|
||||
- name: requirements.txt kopieren
|
||||
ansible.builtin.copy:
|
||||
src: "{{ jumphost_repo_src }}/requirements.txt"
|
||||
dest: "{{ jumphost_home }}/requirements.txt"
|
||||
owner: "{{ jumphost_app_user }}"
|
||||
group: "{{ jumphost_app_group }}"
|
||||
notify: restart jumphost-app
|
||||
|
||||
- name: Dateirechte auf Anwendungscode setzen
|
||||
ansible.builtin.file:
|
||||
path: "{{ jumphost_home }}"
|
||||
state: directory
|
||||
owner: "{{ jumphost_app_user }}"
|
||||
group: "{{ jumphost_app_group }}"
|
||||
recurse: true
|
||||
|
||||
- name: Virtualenv anlegen
|
||||
ansible.builtin.command:
|
||||
cmd: "python3 -m venv {{ jumphost_venv }}"
|
||||
creates: "{{ jumphost_venv }}/bin/python"
|
||||
become: true
|
||||
become_user: "{{ jumphost_app_user }}"
|
||||
|
||||
- name: Python-Abhaengigkeiten installieren
|
||||
ansible.builtin.pip:
|
||||
requirements: "{{ jumphost_home }}/requirements.txt"
|
||||
virtualenv: "{{ jumphost_venv }}"
|
||||
become: true
|
||||
become_user: "{{ jumphost_app_user }}"
|
||||
notify: restart jumphost-app
|
||||
25
ansible/roles/sqlite_init/tasks/main.yml
Normal file
25
ansible/roles/sqlite_init/tasks/main.yml
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
# Die Migrationen laufen bereits automatisch beim App-Start (app/db.py:init_db).
|
||||
# Diese Rolle stellt lediglich sicher, dass Verzeichnis/Rechte VOR dem ersten
|
||||
# Start korrekt sind und legt optional den initialen Admin-User an.
|
||||
|
||||
- name: Datenverzeichnis-Rechte sicherstellen
|
||||
ansible.builtin.file:
|
||||
path: "{{ jumphost_data_dir }}"
|
||||
state: directory
|
||||
owner: "{{ jumphost_app_user }}"
|
||||
group: "{{ jumphost_app_group }}"
|
||||
mode: "0700"
|
||||
|
||||
- name: Pruefen, ob bereits eine DB existiert
|
||||
ansible.builtin.stat:
|
||||
path: "{{ jumphost_data_dir }}/jumphost.db"
|
||||
register: _db_stat
|
||||
|
||||
- name: Hinweis fuer manuellen Erstadmin-Anlegeschritt ausgeben
|
||||
ansible.builtin.debug:
|
||||
msg: >
|
||||
Keine bestehende Datenbank gefunden. Nach dem ersten Start des Dienstes
|
||||
(Rolle jumphost_app) einmalig ausfuehren:
|
||||
{{ jumphost_venv }}/bin/python {{ jumphost_home }}/scripts/create_admin.py --username admin
|
||||
when: not _db_stat.stat.exists
|
||||
85
ansible/roles/tls_certificates/tasks/main.yml
Normal file
85
ansible/roles/tls_certificates/tasks/main.yml
Normal file
@ -0,0 +1,85 @@
|
||||
---
|
||||
# Drei TLS-Modi (Konzept 7.2a), gesteuert ueber tls_mode. Ergebnis ist in
|
||||
# allen Faellen identisch: /etc/jumphost/tls/server.{crt,key} bzw. bei
|
||||
# external_reverse_proxy zusaetzlich das interne Re-Encryption-Zertifikat
|
||||
# fuer nginx unter demselben Pfad (nginx_proxy-Rolle nutzt diese Dateien).
|
||||
|
||||
- name: TLS-Verzeichnis anlegen
|
||||
ansible.builtin.file:
|
||||
path: /etc/jumphost/tls
|
||||
state: directory
|
||||
owner: root
|
||||
group: "{{ jumphost_app_group if not enable_nginx_proxy else 'root' }}"
|
||||
mode: "0750"
|
||||
|
||||
- name: "Modus internal_pki / external_reverse_proxy: Zertifikat per ACME gegen interne CA beziehen"
|
||||
block:
|
||||
- name: step-ca ACME-Client (step-cli) installieren
|
||||
ansible.builtin.apt:
|
||||
name: step-cli
|
||||
state: present
|
||||
ignore_errors: true # Paket ist nicht in allen Distros verfuegbar, siehe Fallback unten
|
||||
|
||||
- name: Hinweis auf manuellen/alternativen Cert-Bezug
|
||||
ansible.builtin.debug:
|
||||
msg: >
|
||||
Falls step-cli nicht verfuegbar ist: Zertifikat/Key manuell aus der
|
||||
Unternehmens-PKI beziehen und ALS ansible-vault-verschluesselte
|
||||
Dateien unter files/tls/{{ inventory_hostname }}.crt / .key ablegen;
|
||||
diese Rolle kopiert sie dann per copy-Task (siehe unten, auskommentiert).
|
||||
when: tls_mode in ['internal_pki', 'external_reverse_proxy']
|
||||
|
||||
# Alternative zu ACME: vorab per Unternehmens-PKI ausgestellte Dateien einspielen.
|
||||
# - name: Vorab ausgestelltes internes Zertifikat einspielen
|
||||
# ansible.builtin.copy:
|
||||
# src: "files/tls/{{ inventory_hostname }}.crt"
|
||||
# dest: /etc/jumphost/tls/server.crt
|
||||
# when: tls_mode in ['internal_pki', 'external_reverse_proxy']
|
||||
|
||||
- name: "Modus acme_public: Certbot fuer oeffentliches Let's-Encrypt-Zertifikat"
|
||||
block:
|
||||
- name: certbot installieren
|
||||
ansible.builtin.apt:
|
||||
name: certbot
|
||||
state: present
|
||||
|
||||
- name: Zertifikat beziehen (nginx-Plugin, HTTP-01)
|
||||
ansible.builtin.command:
|
||||
cmd: >
|
||||
certbot certonly --nginx --non-interactive --agree-tos
|
||||
-m {{ acme_email }} -d {{ acme_domain }}
|
||||
creates: "/etc/letsencrypt/live/{{ acme_domain }}/fullchain.pem"
|
||||
when: enable_nginx_proxy
|
||||
|
||||
- name: Symlinks fuer einheitlichen Pfad anlegen
|
||||
ansible.builtin.file:
|
||||
src: "/etc/letsencrypt/live/{{ acme_domain }}/{{ item.src }}"
|
||||
dest: "/etc/jumphost/tls/{{ item.dest }}"
|
||||
state: link
|
||||
loop:
|
||||
- { src: fullchain.pem, dest: server.crt }
|
||||
- { src: privkey.pem, dest: server.key }
|
||||
|
||||
- name: Certbot-Renew-Timer aktivieren
|
||||
ansible.builtin.service:
|
||||
name: certbot.timer
|
||||
state: started
|
||||
enabled: true
|
||||
when: tls_mode == 'acme_public'
|
||||
|
||||
- name: Warnung bei Klartext-internem Hop protokollieren (bewusste Ausnahme, Konzept 7.2a)
|
||||
ansible.builtin.debug:
|
||||
msg: >
|
||||
ACHTUNG: internal_hop_plaintext_accepted=true gesetzt -- die Strecke
|
||||
zwischen externem Reverse Proxy und diesem Jumphost laeuft unverschluesselt.
|
||||
Dies ist NICHT die Standardempfehlung und erfordert eine dokumentierte
|
||||
Risikoakzeptanz (siehe Konzept 7.2a).
|
||||
when: tls_mode == 'external_reverse_proxy' and internal_hop_plaintext_accepted | default(false)
|
||||
|
||||
- name: Zertifikatsdateien-Rechte absichern
|
||||
ansible.builtin.file:
|
||||
path: /etc/jumphost/tls
|
||||
owner: root
|
||||
group: "{{ jumphost_app_group if not enable_nginx_proxy else 'root' }}"
|
||||
mode: "0750"
|
||||
recurse: true
|
||||
23
ansible/site.yml
Normal file
23
ansible/site.yml
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
# Jumphost-Deployment. Siehe Konzeptdokument Kap. 7 fuer Hintergrund.
|
||||
#
|
||||
# Steuerung ueber group_vars:
|
||||
# enable_nginx_proxy: true|false (Kap. 7.2)
|
||||
# tls_mode: internal_pki | external_reverse_proxy | acme_public (Kap. 7.2a)
|
||||
|
||||
- hosts: jumphosts
|
||||
become: true
|
||||
vars_files:
|
||||
- inventory/group_vars/all.yml
|
||||
roles:
|
||||
- os_hardening
|
||||
- firewall_nftables
|
||||
- fail2ban
|
||||
- frontend_assets # baut static/js/vendor/* lokal VOR dem Sync in python_runtime
|
||||
- python_runtime
|
||||
- sqlite_init
|
||||
- guacd
|
||||
- jumphost_app
|
||||
- tls_certificates
|
||||
- { role: nginx_proxy, when: enable_nginx_proxy | default(false) }
|
||||
- backup
|
||||
Reference in New Issue
Block a user