diff --git a/group_vars/all.yml b/group_vars/all.yml index 530c4b1..00855ed 100644 --- a/group_vars/all.yml +++ b/group_vars/all.yml @@ -17,4 +17,10 @@ user_mgmt_default: ssh_key: present: - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEay33koXmcBcrDuCQKkCBlw/gKiPtwLswATPqIR7udl fritz@fluorine.grimpen.net" + - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEyVVwh0cUPxZ/wwRsB8YRsQE/cxjEX6gomS7EPArXuX fritz@NaOH" + absent: [] + deelkar: + ssh_key: + present: + - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBz7TX/Nm+tE/8RZ2XLuboFWUmBR0oCD0yTaRm2NILm3 deelkar@artena" absent: [] diff --git a/host_vars/emma.ccchb.de b/host_vars/emma.ccchb.de index a1cb1c2..34b9b9c 100644 --- a/host_vars/emma.ccchb.de +++ b/host_vars/emma.ccchb.de @@ -27,6 +27,8 @@ haproxy_http: addr: '2a01:4f8:150:926f::11' - host: 'embassy.ccchb.de' addr: '2a01:4f8:150:926f::11' + - host: 'jabber.ccchb.de' + addr: '2a01:4f8:150:926f::13' haproxy_sni: - host: 'ccchb.de' @@ -45,6 +47,8 @@ haproxy_sni: addr: '2a01:4f8:150:926f::11' - host: 'embassy.ccchb.de' addr: '2a01:4f8:150:926f::11' + - host: 'jabber.ccchb.de' + addr: '2a01:4f8:150:926f::13' bhyve_ipv4: 10.0.0.0 bhyve_ipv6: 2a01:4f8:150:926f::4 @@ -165,3 +169,19 @@ bhyve_guests: volblocksize: 64k primarycache: metadata + - name: jabber + index: 7 + enabled: true + ram: 1G + cpus: 1 + image: debian-10.5.0-amd64-netinst.iso + password: foobar + order: + - DISKS + disks: + - name: disk + properties: + volsize: 32g + volblocksize: 64k + primarycache: metadata + diff --git a/host_vars/jabber.emma.ccchb.de.yml b/host_vars/jabber.emma.ccchb.de.yml new file mode 100644 index 0000000..e73c38e --- /dev/null +++ b/host_vars/jabber.emma.ccchb.de.yml @@ -0,0 +1,12 @@ +vm_index: 7 + +user_mgmt: + crest: + state: present + groups: sudo + genofire: + state: present + groups: sudo + fritz: + state: present + groups: sudo diff --git a/host_vars/wiki.emma.ccchb.de.yml b/host_vars/wiki.emma.ccchb.de.yml new file mode 100644 index 0000000..05f727a --- /dev/null +++ b/host_vars/wiki.emma.ccchb.de.yml @@ -0,0 +1,18 @@ +vm_index: 6 + +user_mgmt: + crest: + state: present + groups: sudo + genofire: + state: present + groups: sudo + fritz: + state: present + groups: sudo + +certbot_certs: + - [ "wiki.ccchb.de" ] + - [ "ccchb.de", "www.ccchb.de" ] + - [ "files.ccchb.de" ] + diff --git a/hosts/10_jabber b/hosts/10_jabber new file mode 100644 index 0000000..73eab1f --- /dev/null +++ b/hosts/10_jabber @@ -0,0 +1,2 @@ +[jabber] +jabber.emma.ccchb.de diff --git a/hosts/10_wiki b/hosts/10_wiki new file mode 100644 index 0000000..a05cdf0 --- /dev/null +++ b/hosts/10_wiki @@ -0,0 +1,2 @@ +[wiki] +wiki.emma.ccchb.de diff --git a/hosts/50_debian b/hosts/50_debian index e8490fa..f14dd0c 100644 --- a/hosts/50_debian +++ b/hosts/50_debian @@ -4,3 +4,5 @@ dn42.emma.ccchb.de [debian:children] nextcloud gitea +wiki +jabber diff --git a/roles/certbot/defaults/main.yml b/roles/certbot/defaults/main.yml new file mode 100644 index 0000000..60a15ba --- /dev/null +++ b/roles/certbot/defaults/main.yml @@ -0,0 +1,11 @@ +--- +certbot_admin_email: hostmaster@ccchb.de + +certbot_package: letsencrypt + +certbot_method: webroot +certbot_webroot: /var/www/html + +certbot_certs: [] + +certbot_renew: true diff --git a/roles/certbot/tasks/main.yml b/roles/certbot/tasks/main.yml new file mode 100644 index 0000000..506227c --- /dev/null +++ b/roles/certbot/tasks/main.yml @@ -0,0 +1,22 @@ +--- +- name: Enable certbot timer. + systemd: + name: certbot.timer + enabled: yes + +- name: Install certbot. + package: + name: "{{ certbot_package }}" + state: present + +- name: Check for presence of certificates. + stat: + path: "/etc/letsencrypt/live/{{ item | first | replace(\"*.\", \"\") }}/cert.pem" + register: certs_presence + loop: "{{ certbot_certs }}" + +- name: Obtain certificates. + include_tasks: 'obtain_{{ certbot_method }}.yml' + when: not item.stat.exists + loop: "{{ certs_presence.results }}" +... diff --git a/roles/certbot/tasks/obtain_standalone.yml b/roles/certbot/tasks/obtain_standalone.yml new file mode 100644 index 0000000..fd863ed --- /dev/null +++ b/roles/certbot/tasks/obtain_standalone.yml @@ -0,0 +1,4 @@ +--- +- name: "Obtain certificate for {{ item.item | join(',') }}" + command: "certbot certonly --agree-tos -m {{ certbot_admin_email | quote }} -d {{ item.item | join(',') }} --standalone" +... diff --git a/roles/certbot/tasks/obtain_webroot.yml b/roles/certbot/tasks/obtain_webroot.yml new file mode 100644 index 0000000..13ec4ff --- /dev/null +++ b/roles/certbot/tasks/obtain_webroot.yml @@ -0,0 +1,4 @@ +--- +- name: "Obtain certificate for {{ item.item | join(',') }}" + command: "certbot certonly --agree-tos -m {{ certbot_admin_email | quote }} -d {{ item.item | join(',') }} --webroot -w {{ certbot_webroot | quote }}" +... diff --git a/roles/certbot/templates/certbot.conf.j2 b/roles/certbot/templates/certbot.conf.j2 new file mode 100644 index 0000000..6c3cc87 --- /dev/null +++ b/roles/certbot/templates/certbot.conf.j2 @@ -0,0 +1,4 @@ +location /.well-known/acme-challenge/ { + alias {{ certbot_webroot }}/.well-known/acme-challenge/; + allow all; +} diff --git a/roles/mediawiki/defaults/main.yml b/roles/mediawiki/defaults/main.yml new file mode 100644 index 0000000..47b4883 --- /dev/null +++ b/roles/mediawiki/defaults/main.yml @@ -0,0 +1,46 @@ +--- +mediawiki_domain: wiki.ccchb.de + +mediawiki_webroot: /var/www/wiki.ccchb.de/webroot +mediawiki_path: /w + +mediawiki_extensions: + - CategoryTree + - ParserFunctions + - PdfHandler + - Renameuser + - Interwiki + - ConfirmEdit + - ConfirmEdit/QuestyCaptcha + - WikiEditor + - MobileFrontend + +mediawiki_skins: + - MonoBook + - Timeless + - Vector + - MinervaNeue + +mediawiki_sitename: "CCC Bremen" + +mediawiki_email: "webmaster@ccchb.de" + +mediawiki_install_nginx: true +mediawiki_php_socket: "unix:/run/php/php7.3-fpm.sock" + +mediawiki_nginx_conf: | + listen [::]:443 ssl http2; + listen 443 ssl http2; + + server_name {{ mediawiki_domain }}; + + root {{ mediawiki_webroot }}; + + ssl_certificate /etc/letsencrypt/live/{{ mediawiki_domain }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{ mediawiki_domain }}/privkey.pem; + ssl_trusted_certificate /etc/letsencrypt/live/{{ mediawiki_domain }}/chain.pem; + + client_max_body_size 100M; + + include snippets/certbot.conf; +... diff --git a/roles/mediawiki/tasks/main.yml b/roles/mediawiki/tasks/main.yml new file mode 100644 index 0000000..b73b458 --- /dev/null +++ b/roles/mediawiki/tasks/main.yml @@ -0,0 +1,21 @@ +--- +- name: Configure Mediawiki + template: + src: LocalSettings.php.j2 + dest: "{{ mediawiki_webroot }}/{{ mediawiki_path }}/LocalSettings.php" + owner: www-data + group: www-data + mode: '0600' + +- name: Install nginx site + template: + src: nginx.j2 + dest: /etc/nginx/sites-available/{{ mediawiki_domain }} + when: mediawiki_install_nginx + +- name: Activate site {{ mediawiki_install_nginx }} + file: + src: /etc/nginx/sites-available/{{ mediawiki_domain }} + dest: /etc/nginx/sites-enabled/{{ mediawiki_domain }} + when: mediawiki_install_nginx +... diff --git a/roles/mediawiki/templates/LocalSettings.php.j2 b/roles/mediawiki/templates/LocalSettings.php.j2 new file mode 100644 index 0000000..f2b4824 --- /dev/null +++ b/roles/mediawiki/templates/LocalSettings.php.j2 @@ -0,0 +1,115 @@ + 'Die Antwort auf die Frage nach dem Leben, dem Universum und allem?', 'answer' => '42' ); +$wgCaptchaQuestions[] = array( 'question' => 'Wie lautet der Kurzname dieses Vereins?', 'answer' => 'CCCHB' ); +$wgCaptchaQuestions[] = array( 'question' => 'Gib einfach das Wort Passion ein (kleingeschrieben):', 'answer' => 'passion' ); +$wgCaptchaQuestions[] = array( 'question' => 'Gib bitte Fünfhundertsiebenundsechzig als Zahl ein:', 'answer' => '567' ); +$wgCaptchaQuestions[] = array( 'question' => 'An welchem Wochentag treffen wir uns regelmäßig?', 'answer' => 'Dienstag' ); +$wgCaptchaQuestions[] = array( 'question' => 'Der erste Congress im CCH (2012)', 'answer' => '29c3' ); +$wgCaptchaQuestions[] = array( 'question' => 'Der letzte Congress im bcc (2011)', 'answer' => '28c3' ); +$wgCaptchaQuestions[] = array( 'question' => 'Wie kürzt man Erfahrungsaustauschkreis in 4 Buchstaben ab? (alles großgeschrieben)', 'answer' => 'ERFA' ); + +$wgShowExceptionDetails = true; +$wgShowDBErrorBacktrace = true; + diff --git a/roles/mediawiki/templates/nginx.j2 b/roles/mediawiki/templates/nginx.j2 new file mode 100644 index 0000000..0f0433b --- /dev/null +++ b/roles/mediawiki/templates/nginx.j2 @@ -0,0 +1,58 @@ +# {{ ansible_managed }} + +server { + {{ mediawiki_nginx_conf }} + + location ~ ^{{ mediawiki_path }}/(index|load|api|thumb|opensearch_desc|rest|img_auth)\.php$ { + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_pass {{ mediawiki_php_socket }}; + } + + # Images + location {{ mediawiki_path }}/images { + # Separate location for images/ so .php execution won't apply + } + location {{ mediawiki_path }}/images/deleted { + # Deny access to deleted images folder + deny all; + } + # MediaWiki assets (usually images) + location ~ ^{{ mediawiki_path }}/resources/(assets|lib|src) { + try_files $uri 404; + add_header Cache-Control "public"; + expires 7d; + } + # Assets, scripts and styles from skins and extensions + location ~ ^{{ mediawiki_path }}/(skins|extensions)/.+\.(css|js|gif|jpg|jpeg|png|svg|wasm)$ { + try_files $uri 404; + add_header Cache-Control "public"; + expires 7d; + } + # Favicon + location = /favicon.ico { + add_header Cache-Control "public"; + expires 7d; + } + + location {{ mediawiki_path }}/rest.php/ { + try_files $uri $uri/ {{ mediawiki_path }}/rest.php?$query_string; + } + + # Handling for the article path (pretty URLs) + location /wiki/ { + rewrite ^/wiki/(?.*)$ {{ mediawiki_path }}/index.php; + } + + # Allow robots.txt in case you have one + location = /robots.txt { + } + # Explicit access to the root website, redirect to main page (adapt as needed) + location = / { + return 301 /wiki/Hauptseite; + } + + location / { + return 404; + } +} diff --git a/roles/prosody/defaults/main.yml b/roles/prosody/defaults/main.yml new file mode 100644 index 0000000..ebd029b --- /dev/null +++ b/roles/prosody/defaults/main.yml @@ -0,0 +1,57 @@ +--- +prosody_domain: "jabber.ccchb.de" +prosody_ssl_cert: "/etc/letsencrypt/live/{{ prosody_domain }}/fullchain.pem" +prosody_ssl_key: "/etc/letsencrypt/live/{{ prosody_domain }}/privkey.pem" +prosody_allow_registration: false +prosody_modules: + - roster + - saslauth + - tls + - dialback + - disco + - private + - bookmarks + - vcard + - proxy65 + - legacyauth + - version + - uptime + - time + - ping + - pep + - register + - adhoc + - admin_adhoc + - posix + - bosh + - websocket + - groups + - announce + - watchregistrations + - blocking + - smacks + - carbons + - cloud_notify + - csi + - mam + - filter_chatstates + - throttle_presence + - http_upload + - turncredentials + - vcard_legacy + +prosody_nginx_install: true +prosody_nginx_conf: | + listen [::]:443 ssl http2; + listen 443 ssl http2; + + server_name {{ prosody_domain }}; + + root /var/www/html; + + ssl_certificate {{ prosody_ssl_cert }}; + ssl_certificate_key {{ prosody_ssl_key }}; + ssl_trusted_certificate /etc/letsencrypt/live/{{ prosody_domain }}/chain.pem; + + include snippets/certbot.conf; +... diff --git a/roles/prosody/handlers/main.yml b/roles/prosody/handlers/main.yml new file mode 100644 index 0000000..4e0a6ca --- /dev/null +++ b/roles/prosody/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: reload nginx + systemd: + name: nginx + state: reloaded diff --git a/roles/prosody/tasks/main.yml b/roles/prosody/tasks/main.yml new file mode 100644 index 0000000..fffe4b6 --- /dev/null +++ b/roles/prosody/tasks/main.yml @@ -0,0 +1,21 @@ +--- +- name: Install prosody http site + template: + src: nginx.j2 + dest: "/etc/nginx/sites-available/{{ prosody_domain }}" + when: prosody_nginx_install + +- name: Enable prosody http site + notify: reload nginx + file: + src: /etc/nginx/sites-available/{{ prosody_domain }} + dest: /etc/nginx/sites-enabled/{{ prosody_domain }} + state: link + when: prosody_nginx_install + +- name: Configure prosody + template: + src: prosody.cfg.lua.j2 + dest: /etc/prosody/prosody_test.cfg.lua + +... diff --git a/roles/prosody/templates/nginx.j2 b/roles/prosody/templates/nginx.j2 new file mode 100644 index 0000000..bf6f639 --- /dev/null +++ b/roles/prosody/templates/nginx.j2 @@ -0,0 +1,18 @@ +# {{ ansible_managed }} + +server { + {{ prosody_nginx_conf }} + + location /http-bind { + proxy_pass http://127.0.0.1:5280/http-bind; + } + + location /xmpp-websocket { + proxy_pass http://127.0.0.1:5280/xmpp-websocket; + } + + location /upload { + proxy_set_header Host {{ prosody_domain }}; + proxy_pass http://127.0.0.1:5280/upload; + } +} diff --git a/roles/prosody/templates/prosody.cfg.lua.j2 b/roles/prosody/templates/prosody.cfg.lua.j2 new file mode 100644 index 0000000..6ac7996 --- /dev/null +++ b/roles/prosody/templates/prosody.cfg.lua.j2 @@ -0,0 +1,160 @@ +-- Prosody XMPP Server Configuration +-- {{ ansible_managed }} + +---------- Server-wide settings ---------- +-- Settings in this section apply to the whole server and are the default settings +-- for any virtual hosts + +-- This is a (by default, empty) list of accounts that are admins +-- for the server. Note that you must create the accounts separately +-- (see http://prosody.im/doc/creating_accounts for info) +-- Example: admins = { "user1@example.com", "user2@example.net" } +admins = { "deelkar@jabber.ccchb.de", "freak@jabber.ccchb.de", "jali@jabber.ccchb.de" } + +-- Enable use of libevent for better performance under high load +-- For more information see: http://prosody.im/doc/libevent +use_libevent = false; + +plugin_paths = { "/opt/prosody-modules" } + +-- This is the list of modules Prosody will load on startup. +-- It looks for mod_modulename.lua in the plugins folder, so make sure that exists too. +-- Documentation on modules can be found at: http://prosody.im/doc/modules +modules_enabled = { + {% for module in prosody_modules %} + "{{ module }}"; + {% endfor %} +}; + +-- These modules are auto-loaded, should you +-- (for some mad reason) want to disable +-- them then uncomment them below +modules_disabled = { + -- "presence"; -- Route user/contact status information + -- "message"; -- Route messages + -- "iq"; -- Route info queries + -- "offline"; -- Store offline messages +}; + +-- Disable account creation by default, for security +-- For more information see http://prosody.im/doc/creating_accounts +allow_registration = {{ prosody_allow_registration }}; + +-- These are the SSL/TLS-related settings. If you don't want +-- to use SSL/TLS, you may comment or remove this +-- *** DUMMY CERT *** DO NOT CHANGE *** SET CERT IN HOST SECTION *** +ssl = { + protocol = "sslv23"; + key = "{{ prosody_ssl_key }}"; + certificate = "{{ prosody_ssl_cert }}"; + dhparam = "/etc/prosody/certs/dh-2048.pem"; + options = { "no_sslv2", "no_sslv3", "no_ticket", "no_compression", "cipher_server_preference", "single_dh_use", "single_ecdh_use" }; + ciphers = "ECDH:DH:HIGH+kEDH:HIGH+kEECDH:HIGH:!CAMELLIA128:!3DES:!MD5:!RC4:!aNULL:!NULL:!EXPORT:!LOW:!MEDIUM"; +} +legacy_ssl_ports = { 5223 } +http_external_url = "https://{{ prosody_domain }}/" + +-- Only allow encrypted streams? Encryption is already used when +-- available. These options will cause Prosody to deny connections that +-- are not encrypted. Note that some servers do not support s2s +-- encryption or have it disabled, including gmail.com and Google Apps +-- domains. + +--c2s_require_encryption = false +--s2s_require_encryption = false + +-- Select the authentication backend to use. The 'internal' providers +-- use Prosody's configured data storage to store the authentication data. +-- To allow Prosody to offer secure authentication mechanisms to clients, the +-- default provider stores passwords in plaintext. If you do not trust your +-- server please see http://prosody.im/doc/modules/mod_auth_internal_hashed +-- for information about using the hashed backend. + +authentication = "internal_hashed" + +-- Select the storage backend to use. By default Prosody uses flat files +-- in its configured data directory, but it also supports more backends +-- through modules. An "sql" backend is included by default, but requires +-- additional dependencies. See http://prosody.im/doc/storage for more info. + +--storage = "sql" -- Default is "internal" + +-- For the "sql" backend, you can uncomment *one* of the below to configure: +--sql = { driver = "SQLite3", database = "prosody.sqlite" } -- Default. 'database' is the filename. +--sql = { driver = "MySQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" } +--sql = { driver = "PostgreSQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" } + + +-- STUN/TURN +--turncredentials_host = "jabber.emma.ccchb.de" +turncredentials_host = "einstein.cskreie.de" +turncredentials_secret = "gabbagabbahey" + + +-- HTTP-UPLOAD +http_upload_file_size_limit = 10485760 -- 10M +http_max_content_size = 20971520 -- 20M +http_upload_quota = 104857600 -- 100M +http_upload_expire_after = 2592000 -- 30d + +-- Logging configuration +-- For advanced logging see http://prosody.im/doc/logging +-- Hint: If you create a new log file or rename them, don't forget +-- to update the logrotate config at /etc/logrotate.d/prosody +log = { + -- Log all error messages to prosody.err + error = "/var/log/prosody/prosody.err"; + -- Log everything of level "info" and higher (that is, all except "debug" messages) + -- to prosody.log + -- info = "/var/log/prosody/prosody.log"; -- Change 'info' to 'debug' for more verbose logging + -- debug = "/var/log/prosody/prosody.log"; -- Change 'info' to 'debug' for more verbose logging + --"*syslog"; -- Uncomment this for logging to syslog +} + +-- Pidfile, used by prosodyctl and the init.d script +pidfile = "/var/run/prosody/prosody.pid"; + +----------- Virtual hosts ----------- +-- You need to add a VirtualHost entry for each domain you wish Prosody to serve. +-- Settings under each VirtualHost entry apply *only* to that host. + +VirtualHost "localhost" + +VirtualHost "{{ prosody_domain }}" + enabled = true -- Remove this line to enable this host + + -- Assign this host a certificate for TLS, otherwise it would use the one + -- set in the global section (if any). + -- Note that old-style SSL on port 5223 only supports one certificate, and will always + -- use the global one. + ssl = { + protocol = "sslv23"; + key = "{{ prosody_ssl_key }}"; + certificate = "{{ prosody_ssl_cert }}"; + dhparam = "/etc/prosody/certs/dh-2048.pem"; + options = { "no_sslv2", "no_sslv3", "no_ticket", "no_compression", "cipher_server_preference", "single_dh_use", "single_ecdh_use" }; + ciphers = "ECDH:DH:HIGH+kEDH:HIGH+kEECDH:HIGH:!CAMELLIA128:!3DES:!MD5:!RC4:!aNULL:!NULL:!EXPORT:!LOW:!MEDIUM"; + } + +------ Components ------ +-- You can specify components to add hosts that provide special services, +-- like multi-user conferences, and transports. +-- For more information on components, see http://prosody.im/doc/components + +---Set up a MUC (multi-user chat) room server on conference.example.com: +Component "muc.{{ prosody_domain }}" "muc" +modules_enabled = { + "vcard_muc", "muc_mam", +} +-- Set up a SOCKS5 bytestream proxy for server-proxied file transfers: +--Component "proxy.example.com" "proxy65" + +---Set up an external component (default component port is 5347) +-- +-- External components allow adding various services, such as gateways/ +-- transports to other networks like ICQ, MSN and Yahoo. For more info +-- see: http://prosody.im/doc/components#adding_an_external_component +-- +--Component "gateway.example.com" +-- component_secret = "password" + diff --git a/site.yml b/site.yml index 3e374e1..845bad0 100644 --- a/site.yml +++ b/site.yml @@ -6,3 +6,4 @@ - import_playbook: bhyve.yml - import_playbook: mail.yml - import_playbook: restic.yml +- import_playbook: wiki.yml diff --git a/wiki.yml b/wiki.yml new file mode 100644 index 0000000..a42dc86 --- /dev/null +++ b/wiki.yml @@ -0,0 +1,7 @@ +--- +- hosts: + - wiki + become: yes + roles: + - mediawiki + - certbot