From cfa3f6ced2a256171d799adede32386e47ec1eb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ebbe=20Ba=C3=9F?= Date: Tue, 14 Jul 2026 08:29:24 +0200 Subject: [PATCH] Added first ubuntu server playbooks --- linux/ubuntu-server/deploy_basics.yml | 27 + .../deploy_hardened_nextcloud.yml | 485 ++++++++++++++++++ 2 files changed, 512 insertions(+) create mode 100644 linux/ubuntu-server/deploy_basics.yml create mode 100644 linux/ubuntu-server/deploy_hardened_nextcloud.yml diff --git a/linux/ubuntu-server/deploy_basics.yml b/linux/ubuntu-server/deploy_basics.yml new file mode 100644 index 0000000..874c6d9 --- /dev/null +++ b/linux/ubuntu-server/deploy_basics.yml @@ -0,0 +1,27 @@ +- name: Install basic features + hosts: ansible_master + become: true + + tasks: + - name: Update apt + ansible.builtin.apt: + update_cache: true + cache_valid_time: 3600 + + - name: Install net-tools + ansible.builtin.apt: + name: net-tools + state: present + + - name: Install neofetch or fastfetch + block: + - name: Install neofetch + ansible.builtin.apt: + name: neofetch + state: present + + rescue: + - name: If neofetch is depricated install fastfetch + ansible.builtin.apt: + name: fastfetch + state: present \ No newline at end of file diff --git a/linux/ubuntu-server/deploy_hardened_nextcloud.yml b/linux/ubuntu-server/deploy_hardened_nextcloud.yml new file mode 100644 index 0000000..d616270 --- /dev/null +++ b/linux/ubuntu-server/deploy_hardened_nextcloud.yml @@ -0,0 +1,485 @@ +--- +# Nextcloud Installation Playbook +# Target: Ubuntu 24.04 +# Stack: Apache2 + PHP 8.3 + MariaDB + Nextcloud 34.0.0 +# HTTPS via self-signed certificate +# +# Usage: +# ansible-playbook nextcloud.yml -i inventory.ini + +# ================================================================ +# PLAY 0 — Bootstrap: install acl BEFORE any become_user is used. +# ================================================================ +- name: Bootstrap – ensure acl is installed + hosts: nextcloud + become: true + gather_facts: false + + tasks: + - name: Update apt cache + ansible.builtin.apt: + update_cache: true + cache_valid_time: 3600 + + - name: Install acl (required for Ansible become_user on Linux) + ansible.builtin.apt: + name: acl + state: present + +# ================================================================ +# PLAY 1 — Main Nextcloud installation +# ================================================================ +- name: Install and configure Nextcloud + hosts: nextcloud + become: true + + vars: + nextcloud_version: "34.0.0" + nextcloud_domain: "192.168.124.80" + nextcloud_data_dir: "/etc/nextcloud/data" + nextcloud_install_dir: "/etc/nextcloud" + + db_name: "nextcloud" + db_user: "nextcloud" + db_password: "Start2026!" # ÄNDERN + admin_user: "admin" + admin_password: "Start2026!" # ÄNDERN + php_version: "8.3" + + # TLS certificate paths (self-signed, generated by this playbook) + ssl_cert: "/etc/ssl/certs/nextcloud-selfsigned.crt" + ssl_key: "/etc/ssl/private/nextcloud-selfsigned.key" + + # Maintenance window (UTC): tasks run between start and start+4h + # 1 = 01:00 UTC → adjust to your timezone offset as needed + maintenance_window_start: 1 + + # --------------------------------------------------------------- + # 1. System packages + # --------------------------------------------------------------- + tasks: + - name: Install system packages + ansible.builtin.apt: + name: + - apt-transport-https + - ca-certificates + - curl + - gnupg + - lsb-release + - python3-pymysql + - openssl + - unzip + - bzip2 + state: present + + # --------------------------------------------------------------- + # 2. Apache + # --------------------------------------------------------------- + - name: Install Apache2 + ansible.builtin.apt: + name: apache2 + state: present + + - name: Enable required Apache modules + community.general.apache2_module: + name: "{{ item }}" + state: present + loop: + - rewrite + - headers + - env + - dir + - mime + - ssl + notify: Restart Apache + + - name: Enable Apache service + ansible.builtin.systemd: + name: apache2 + enabled: true + state: started + + # --------------------------------------------------------------- + # 3. Self-signed TLS certificate + # --------------------------------------------------------------- + - name: Generate self-signed TLS certificate (10-year validity) + ansible.builtin.command: + cmd: > + openssl req -x509 -nodes -days 3650 + -newkey rsa:4096 + -keyout {{ ssl_key }} + -out {{ ssl_cert }} + -subj "/CN={{ nextcloud_domain }}/O=Nextcloud/C=DE" + -addext "subjectAltName=DNS:{{ nextcloud_domain }}" + creates: "{{ ssl_cert }}" + + - name: Restrict private key permissions + ansible.builtin.file: + path: "{{ ssl_key }}" + owner: root + group: root + mode: "0600" + + # --------------------------------------------------------------- + # 4. PHP 8.3 + extensions + # --------------------------------------------------------------- + - name: Install PHP {{ php_version }} and extensions + ansible.builtin.apt: + name: + - "php{{ php_version }}" + - "php{{ php_version }}-cli" + - "php{{ php_version }}-common" + - "php{{ php_version }}-curl" + - "php{{ php_version }}-gd" + - "php{{ php_version }}-gmp" + - "php{{ php_version }}-imagick" + - "php{{ php_version }}-intl" + - "php{{ php_version }}-mbstring" + - "php{{ php_version }}-mysql" + - "php{{ php_version }}-opcache" + - "php{{ php_version }}-readline" + - "php{{ php_version }}-redis" + - "php{{ php_version }}-xml" + - "php{{ php_version }}-zip" + - "php{{ php_version }}-bcmath" + - "php{{ php_version }}-apcu" + state: present + notify: Restart Apache + + - name: Configure PHP for Nextcloud (php.ini tweaks) + ansible.builtin.lineinfile: + path: "/etc/php/{{ php_version }}/apache2/php.ini" + regexp: "{{ item.regexp }}" + line: "{{ item.line }}" + backup: true + loop: + - { regexp: '^memory_limit', line: 'memory_limit = 2048M' } + - { regexp: '^upload_max_filesize', line: 'upload_max_filesize = 16G' } + - { regexp: '^post_max_size', line: 'post_max_size = 16G' } + - { regexp: '^max_execution_time', line: 'max_execution_time = 300' } + - { regexp: '^max_input_time', line: 'max_input_time = 300' } + - { regexp: '^output_buffering', line: 'output_buffering = Off' } + notify: Restart Apache + + - name: Enable OPcache settings + ansible.builtin.blockinfile: + path: "/etc/php/{{ php_version }}/apache2/conf.d/10-opcache.ini" + block: | + opcache.enable=1 + opcache.interned_strings_buffer=32 + opcache.max_accelerated_files=10000 + opcache.memory_consumption=128 + opcache.save_comments=1 + opcache.revalidate_freq=1 + marker: "; {mark} ANSIBLE MANAGED BLOCK" + notify: Restart Apache + + # --------------------------------------------------------------- + # 5. MariaDB + # --------------------------------------------------------------- + - name: Install MariaDB server + ansible.builtin.apt: + name: + - mariadb-server + - mariadb-client + state: present + + - name: Enable and start MariaDB + ansible.builtin.systemd: + name: mariadb + enabled: true + state: started + + - name: Create Nextcloud database + community.mysql.mysql_db: + name: "{{ db_name }}" + collation: utf8mb4_general_ci + encoding: utf8mb4 + state: present + login_unix_socket: /var/run/mysqld/mysqld.sock + + - name: Create Nextcloud database user + community.mysql.mysql_user: + name: "{{ db_user }}" + password: "{{ db_password }}" + priv: "{{ db_name }}.*:ALL" + host: localhost + state: present + login_unix_socket: /var/run/mysqld/mysqld.sock + + # --------------------------------------------------------------- + # 6. Download & extract Nextcloud + # --------------------------------------------------------------- + - name: Check if Nextcloud is already present + ansible.builtin.stat: + path: "{{ nextcloud_install_dir }}/index.php" + register: nextcloud_stat + + - name: Download Nextcloud {{ nextcloud_version }} + ansible.builtin.get_url: + url: "https://download.nextcloud.com/server/releases/nextcloud-{{ nextcloud_version }}.zip" + dest: "/tmp/nextcloud-{{ nextcloud_version }}.zip" + mode: "0644" + when: not nextcloud_stat.stat.exists + + - name: Extract Nextcloud archive + ansible.builtin.unarchive: + src: "/tmp/nextcloud-{{ nextcloud_version }}.zip" + dest: /etc/ + remote_src: true + owner: www-data + group: www-data + when: not nextcloud_stat.stat.exists + + - name: Create Nextcloud data directory + ansible.builtin.file: + path: "{{ nextcloud_data_dir }}" + state: directory + owner: www-data + group: www-data + mode: "0750" + + - name: Set correct ownership on Nextcloud install directory + ansible.builtin.file: + path: "{{ nextcloud_install_dir }}" + recurse: true + owner: www-data + group: www-data + + # --------------------------------------------------------------- + # 7. Apache virtual host — HTTP redirects to HTTPS, HTTPS serves NC + # --------------------------------------------------------------- + - name: Deploy Nextcloud Apache virtual host (HTTP redirect + HTTPS) + ansible.builtin.copy: + dest: /etc/apache2/sites-available/nextcloud.conf + mode: "0644" + content: | + # ── HTTP: redirect all traffic to HTTPS ────────────────────── + + ServerName {{ nextcloud_domain }} + RewriteEngine On + RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L] + + + # ── HTTPS: serve Nextcloud ──────────────────────────────────── + + ServerName {{ nextcloud_domain }} + DocumentRoot {{ nextcloud_install_dir }} + + SSLEngine on + SSLCertificateFile {{ ssl_cert }} + SSLCertificateKeyFile {{ ssl_key }} + + # Recommended TLS hardening + SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 + SSLCipherSuite ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:!aNULL:!MD5:!DSS + SSLHonorCipherOrder on + + + Header always set Strict-Transport-Security "max-age=15552000; includeSubDomains" + + + + Require all granted + AllowOverride All + Options FollowSymLinks MultiViews + + + Dav off + + + + # Security headers + Header always set Strict-Transport-Security "max-age=15552000; includeSubDomains" + Header always set Referrer-Policy "no-referrer" + Header always set X-Content-Type-Options "nosniff" + Header always set X-Frame-Options "SAMEORIGIN" + Header always set X-Permitted-Cross-Domain-Policies "none" + Header always set X-Robots-Tag "noindex, nofollow" + Header always set X-XSS-Protection "1; mode=block" + + ErrorLog ${APACHE_LOG_DIR}/nextcloud_error.log + CustomLog ${APACHE_LOG_DIR}/nextcloud_access.log combined + + notify: Restart Apache + + - name: Disable default Apache site + ansible.builtin.command: a2dissite 000-default + args: + removes: /etc/apache2/sites-enabled/000-default.conf + notify: Restart Apache + + - name: Enable Nextcloud Apache site + ansible.builtin.command: a2ensite nextcloud + args: + creates: /etc/apache2/sites-enabled/nextcloud.conf + notify: Restart Apache + + # --------------------------------------------------------------- + # 8. Nextcloud occ installer + # --------------------------------------------------------------- + - name: Check if Nextcloud is already installed + ansible.builtin.stat: + path: "{{ nextcloud_install_dir }}/config/config.php" + register: nc_config + + - name: Run Nextcloud installation via occ + ansible.builtin.shell: | + su -s /bin/bash www-data -c \ + 'php {{ nextcloud_install_dir }}/occ maintenance:install \ + --database "mysql" \ + --database-name "{{ db_name }}" \ + --database-user "{{ db_user }}" \ + --database-pass "{{ db_password }}" \ + --admin-user "{{ admin_user }}" \ + --admin-pass "{{ admin_password }}" \ + --data-dir "{{ nextcloud_data_dir }}"' + args: + creates: "{{ nextcloud_install_dir }}/config/config.php" + register: occ_install + + - name: Add trusted domain to Nextcloud config + ansible.builtin.shell: | + su -s /bin/bash www-data -c \ + 'php {{ nextcloud_install_dir }}/occ config:system:set \ + trusted_domains 0 --value="{{ nextcloud_domain }}"' + when: occ_install.changed + + - name: Set overwrite.cli.url to HTTPS + ansible.builtin.shell: | + su -s /bin/bash www-data -c \ + 'php {{ nextcloud_install_dir }}/occ config:system:set \ + overwrite.cli.url --value="https://{{ nextcloud_domain }}"' + when: occ_install.changed + + - name: Force HTTPS protocol in Nextcloud + ansible.builtin.shell: | + su -s /bin/bash www-data -c \ + 'php {{ nextcloud_install_dir }}/occ config:system:set \ + overwriteprotocol --value="https"' + when: occ_install.changed + + - name: Set default phone region + ansible.builtin.shell: | + su -s /bin/bash www-data -c \ + 'php {{ nextcloud_install_dir }}/occ \ + config:system:set default_phone_region --value=DE' + when: occ_install.changed + + - name: Set maintenance window start hour (UTC) + ansible.builtin.shell: | + su -s /bin/bash www-data -c \ + 'php {{ nextcloud_install_dir }}/occ config:system:set \ + maintenance_window_start --type=integer --value="{{ maintenance_window_start }}"' + when: occ_install.changed + + # --------------------------------------------------------------- + # 9. Cron job for background tasks + # --------------------------------------------------------------- + - name: Configure Nextcloud background job to use cron + ansible.builtin.shell: | + su -s /bin/bash www-data -c \ + 'php {{ nextcloud_install_dir }}/occ background:cron' + + - name: Add cron job for Nextcloud + ansible.builtin.cron: + name: "Nextcloud background tasks" + user: www-data + minute: "*/5" + job: "php -f {{ nextcloud_install_dir }}/cron.php > /dev/null 2>&1" + + # --------------------------------------------------------------- + # Handlers + # --------------------------------------------------------------- + handlers: + - name: Restart Apache + ansible.builtin.systemd: + name: apache2 + state: restarted + +# --------------------------------------------------------------- +# Play 3 - Nextcloud: Hardening +# --------------------------------------------------------------- + +- name: Harden Nextcloud install + hosts: nextcloud + become: true + + vars: + nextcloud_version: "34.0.0" + nextcloud_domain: "cloud.test.local" + nextcloud_data_dir: "/etc/nextcloud/data" + nextcloud_install_dir: "/etc/nextcloud" + + fail2ban_dir: "/etc/fail2ban" + + php_version: "8.3" + + tasks: + - name: Set correct open_basedir restriction + ansible.builtin.lineinfile: + path: "/etc/php/{{ php_version }}/apache2/php.ini" + regexp: '^;?open_basedir' + line: "open_basedir = {{ nextcloud_install_dir }}:{{ nextcloud_data_dir }}:/tmp:/dev/urandom" + notify: Restart Apache + + # --------------------------------------------------------------- + # Setup fail2ban for Nextcloud + # --------------------------------------------------------------- + - name: Install fail2ban + ansible.builtin.apt: + name: + - fail2ban + state: present + + - name: Alter Nextcloud log settings for fail2ban + ansible.builtin.lineinfile: + path: "{{ nextcloud_install_dir }}/config/config.php" + regexp: "{{ item.regexp }}" + line: " {{ item.line }}" + insertbefore: '^\);' + backup: true + loop: + - { regexp: "^\\s*'log_type'\\s*=>", line: "'log_type' => 'file'," } + - { regexp: "^\\s*'logfile'\\s*=>", line: "'logfile' => '{{ nextcloud_data_dir }}/nextcloud.log'," } + - { regexp: "^\\s*'loglevel'\\s*=>", line: "'loglevel' => 3," } + + - name: Create fail2ban filter for Nextcloud + ansible.builtin.copy: + content: | + {% raw %} + [Definition] + _groupsre = (?:(?:,?\s*"\w+":(?:"[^"]+"|\w+))*) + failregex = ^\{%(_groupsre)s,?\s*"remoteAddr":""%(_groupsre)s,?\s*"message":"Login failed: + ^\{%(_groupsre)s,?\s*"remoteAddr":""%(_groupsre)s,?\s*"message":"Two-factor challenge failed: + ^\{%(_groupsre)s,?\s*"remoteAddr":""%(_groupsre)s,?\s*"message":"Trusted domain error. + datepattern = ,?\s*"time"\s*:\s*"%%Y-%%m-%%d[T ]%%H:%%M:%%S(%%z)?" + {% endraw %} + dest: "{{ fail2ban_dir }}/filter.d/nextcloud.conf" + mode: "0644" + + - name: Create fail2ban jail for Nextcloud + ansible.builtin.copy: + dest: "{{ fail2ban_dir }}/jail.d/nextcloud.local" + mode: "0644" + content: | + [nextcloud] + backend = auto + enabled = true + port = 80,443 + protocol = tcp + filter = nextcloud + maxretry = 3 + bantime = 86400 + findtime = 43200 + logpath = {{ nextcloud_data_dir }}/nextcloud.log + notify: Restart Apache + + # --------------------------------------------------------------- + # Handlers + # --------------------------------------------------------------- + handlers: + - name: Restart Apache + ansible.builtin.systemd: + name: apache2 + state: restarted \ No newline at end of file