diff options
author | heqnx <root@heqnx.com> | 2025-10-02 13:08:58 +0300 |
---|---|---|
committer | heqnx <root@heqnx.com> | 2025-10-02 13:08:58 +0300 |
commit | c00c75d310e2afea3b521e8a4b90e800ab4f9ff2 (patch) | |
tree | 7875697bfeadfba2276e2e1d2066eca551bf3e17 | |
parent | ac5767248e7b20b376127ac8e3cfdd1a7aaf4193 (diff) | |
download | ansible-cockpit-main.tar.gz ansible-cockpit-main.zip |
-rw-r--r-- | .gitignore | 5 | ||||
-rw-r--r-- | .gitlab-ci.yml | 30 | ||||
-rw-r--r-- | files/cockpit.socket.override.conf | 3 | ||||
-rw-r--r-- | inventory.yaml.example | 10 | ||||
-rw-r--r-- | main.yaml | 9 | ||||
-rw-r--r-- | requirements.txt | 10 | ||||
-rw-r--r-- | tasks/cockpit_configure.yaml | 105 | ||||
-rw-r--r-- | tasks/harden.yaml | 151 | ||||
-rw-r--r-- | tasks/preflight.yaml | 16 | ||||
-rw-r--r-- | templates/cockpit.conf.j2 | 9 | ||||
-rw-r--r-- | templates/nginx.conf.j2 | 83 | ||||
-rw-r--r-- | vars/main.yaml | 8 |
12 files changed, 439 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc6a4ee --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +inventory.yaml +*venv* +*.pub +*rsa* +*ed25519* diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..236b28c --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,30 @@ +# source repo .gitlab-ci.yml +# requires a project access token (Project > Settings > Access tokens): +# - TOKEN with read_repository, write_repository; Role: Maintainer +# +# disable protected branch for Maintainer (Project > Settings > Repository): +# - main branch allowed to merge, allowed to push and merge maintainer; allowed to force push on +# +# requires the following gitlab variables (Project > Settings > CI/CD > Variables): +# - REPO_NAME: gitlab.com/<user>/<repo_name> (Protected, Masked, Expanded) +# - SOURCE_REPO: https://cgit.example.com/<repo_name> (Protected, Masked, Expanded) +# - TOKEN: <project access token> (Protected, Masked, Expanded) +# +stages: + - mirror + +mirror-from-cgit: + stage: mirror + image: ubuntu:20.04 + before_script: + - apt-get update -y && apt-get install -y git + - git config --global user.name "CI" + - git config --global user.email "ci@gitlab.com" + script: + - git clone --mirror "$SOURCE_REPO" temp_repo + - cd temp_repo + - TARGET_REPO="https://oauth2:$TOKEN@$REPO_NAME" + - git remote set-url origin "$TARGET_REPO" + - git push --prune --mirror + only: + - schedules diff --git a/files/cockpit.socket.override.conf b/files/cockpit.socket.override.conf new file mode 100644 index 0000000..2ecefef --- /dev/null +++ b/files/cockpit.socket.override.conf @@ -0,0 +1,3 @@ +[Socket] +ListenStream= +ListenStream=127.0.0.1:9090 diff --git a/inventory.yaml.example b/inventory.yaml.example new file mode 100644 index 0000000..17f526f --- /dev/null +++ b/inventory.yaml.example @@ -0,0 +1,10 @@ +all: + hosts: + server01: + ansible_host: 10.11.12.13 + ansible_user: root + ansible_ssh_private_key_file: id_rsa + children: + servers: + hosts: + server01: {} diff --git a/main.yaml b/main.yaml new file mode 100644 index 0000000..b254abf --- /dev/null +++ b/main.yaml @@ -0,0 +1,9 @@ +- name: setup server01 + hosts: server01 + gather_facts: true + vars_files: + - vars/main.yaml + tasks: + - import_tasks: tasks/preflight.yaml + - import_tasks: tasks/harden.yaml + - import_tasks: tasks/cockpit_configure.yaml diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..cfc541b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +ansible==10.7.0 +ansible-core==2.17.12 +cffi==1.17.1 +cryptography==45.0.4 +Jinja2==3.1.6 +MarkupSafe==3.0.2 +packaging==25.0 +pycparser==2.22 +PyYAML==6.0.2 +resolvelib==1.0.1 diff --git a/tasks/cockpit_configure.yaml b/tasks/cockpit_configure.yaml new file mode 100644 index 0000000..dc93a3d --- /dev/null +++ b/tasks/cockpit_configure.yaml @@ -0,0 +1,105 @@ +- name: remove /etc/nginx/sites-enabled directory + file: + path: /etc/nginx/sites-enabled + state: absent + +- name: remove /etc/nginx/sites-available directory + file: + path: /etc/nginx/sites-available + state: absent + +- name: remove /var/www/html directory + file: + path: /var/www/html + state: absent + ignore_errors: true + +- name: ensure /var/www/html directory exists + file: + path: /var/www/html + state: directory + mode: '0755' + owner: www-data + group: www-data + +- name: set directory permissions to 755 + ansible.builtin.file: + path: /var/www/html/ + recurse: yes + state: directory + mode: '0755' + owner: www-data + group: www-data + +- name: ensure /etc/cockpit directory exists + file: + path: /etc/cockpit + state: directory + mode: '0755' + owner: root + group: root + +- name: create /etc/systemd/system/cockpit.socket.d directory + file: + path: /etc/systemd/system/cockpit.socket.d + state: directory + mode: '0755' + owner: root + group: root + +- name: ensure /etc/NetworkManager/conf.d/ directory exists + file: + path: /etc/NetworkManager/conf.d/ + state: directory + mode: '0755' + owner: root + group: root + +- name: set up systemd cockpit socket override.conf + copy: + src: files/cockpit.socket.override.conf + dest: /etc/systemd/system/cockpit.socket.d/override.conf + mode: '0644' + +- name: generate /etc/cockpit/cockpit.conf + template: + src: templates/cockpit.conf.j2 + dest: /etc/cockpit/cockpit.conf + owner: root + group: root + mode: '0644' + +- name: generate /etc/nginx/nginx.conf from template + template: + src: templates/nginx.conf.j2 + dest: /etc/nginx/nginx.conf + owner: root + group: root + mode: '0644' + +- name: fix packagekit offline with dummy network interface + copy: + dest: /etc/NetworkManager/conf.d/10-globally-managed-devices.conf + content: | + [keyfile] + unmanaged-devices=none + +- name: create dummy network intereface + command: nmcli con add type dummy con-name fake ifname fake0 ip4 1.2.3.4/24 gw4 1.2.3.1 + +- name: reload systemd daemon + command: systemctl daemon-reload + +- name: restart cockpit service + systemd: + name: cockpit.service + state: restarted + enabled: true + when: ansible_service_mgr == 'systemd' + +- name: restart nginx service + systemd: + name: nginx + state: restarted + enabled: true + when: ansible_service_mgr == 'systemd' diff --git a/tasks/harden.yaml b/tasks/harden.yaml new file mode 100644 index 0000000..2cdb15b --- /dev/null +++ b/tasks/harden.yaml @@ -0,0 +1,151 @@ +- name: update apt packages + apt: + update_cache: true + +- name: install apt packages + apt: + name: "{{ apt_packages }}" + state: present + update_cache: true + environment: + DEBIAN_FRONTEND: noninteractive + +- name: clear /etc/issue and /etc/motd + copy: + content: "" + dest: "{{ item }}" + loop: + - /etc/issue + - /etc/motd + +- name: check if /etc/update-motd.d directory exists + stat: + path: /etc/update-motd.d + register: motd_dir + +- name: find files in /etc/update-motd.d + find: + paths: /etc/update-motd.d + file_type: file + register: motd_files + when: motd_dir.stat.exists + +- name: remove execute permissions from all files in /etc/update-motd.d + file: + path: "{{ item.path }}" + mode: u-x,g-x,o-x + loop: "{{ motd_files.files }}" + when: motd_dir.stat.exists + +- name: enforce root-only cron/at + file: + path: "{{ item }}" + state: touch + owner: root + group: root + mode: '0600' + loop: + - /etc/cron.allow + - /etc/at.allow + +- name: remove deny files for cron and at + file: + path: "{{ item }}" + state: absent + loop: + - /etc/cron.deny + - /etc/at.deny + +- name: backup sshd_config + copy: + src: /etc/ssh/sshd_config + dest: "/etc/ssh/sshd_config.bak_{{ ansible_date_time.iso8601_basic }}" + remote_src: true + +- name: harden sshd_config + copy: + dest: /etc/ssh/sshd_config + content: | + Port 22 + Banner /etc/issue + UsePAM yes + Protocol 2 + Subsystem sftp /usr/lib/openssh/sftp-server + LogLevel verbose + PrintMotd no + #AcceptEnv LANG LC_* + MaxSessions 5 + StrictModes yes + Compression no + MaxAuthTries 3 + IgnoreRhosts yes + PrintLastLog yes + AddressFamily inet + X11Forwarding no + PermitRootLogin yes + AllowTcpForwarding yes + ClientAliveInterval 1200 + AllowAgentForwarding no + PermitEmptyPasswords no + ClientAliveCountMax 0 + GSSAPIAuthentication no + KerberosAuthentication no + IgnoreUserKnownHosts yes + PermitUserEnvironment no + ChallengeResponseAuthentication no + MACs hmac-sha2-512,hmac-sha2-256 + Ciphers aes128-ctr,aes192-ctr,aes256-ctr + +- name: regenerate SSH host keys + shell: | + rm -f /etc/ssh/ssh_host_*key* + ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N "" + ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N "" + args: + creates: /etc/ssh/ssh_host_ed25519_key + +- name: restart ssh + systemd: + name: ssh + state: restarted + enabled: true + when: ansible_service_mgr == 'systemd' + +- name: enable unattended-upgrades + shell: dpkg-reconfigure --priority=low unattended-upgrades + args: + creates: /etc/apt/apt.conf.d/50unattended-upgrades + +- name: restart unattended-upgrades + systemd: + name: unattended-upgrades + state: restarted + enabled: true + when: ansible_service_mgr == 'systemd' + +- name: disable ipv6 in grub + lineinfile: + path: /etc/default/grub + regexp: '^GRUB_CMDLINE_LINUX=' + line: 'GRUB_CMDLINE_LINUX="ipv6.disable=1"' + +- name: update grub + command: update-grub + +- name: allow ssh port and enable ufw + ufw: + rule: allow + port: 22 + proto: tcp + +- name: enable ufw + ufw: + state: enabled + policy: deny + +- name: restart ufw + systemd: + name: ufw + state: restarted + enabled: true + when: ansible_service_mgr == 'systemd' diff --git a/tasks/preflight.yaml b/tasks/preflight.yaml new file mode 100644 index 0000000..3358d46 --- /dev/null +++ b/tasks/preflight.yaml @@ -0,0 +1,16 @@ +- name: ensure script is run as root + assert: + that: + - ansible_effective_user_id == 0 + fail_msg: "this playbook must be run as root" + +- name: check if system is debian-based + command: dpkg -l + register: dpkg_check + changed_when: false + failed_when: false + +- name: fail if not debian-based + fail: + msg: "distribution not Debian-based" + when: dpkg_check.rc != 0 diff --git a/templates/cockpit.conf.j2 b/templates/cockpit.conf.j2 new file mode 100644 index 0000000..e1cf026 --- /dev/null +++ b/templates/cockpit.conf.j2 @@ -0,0 +1,9 @@ +[WebService] +Origins = https://{{ansible_host}} +ProtocolHeader = X-Forwarded-Proto +ForwardedForHeader = X-Forwarded-For + +[Session] +IdleTimeout = 15 +LoginTo = false +AllowMultiHost = false diff --git a/templates/nginx.conf.j2 b/templates/nginx.conf.j2 new file mode 100644 index 0000000..60c8cd4 --- /dev/null +++ b/templates/nginx.conf.j2 @@ -0,0 +1,83 @@ +user www-data; +worker_processes auto; +pid /run/nginx.pid; + +events { + worker_connections 1024; + multi_accept on; +} + +http { + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + server_tokens off; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log warn; + + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_min_length 256; + gzip_types + text/plain + text/css + application/json + application/javascript + text/xml + application/xml + application/xml+rss + text/javascript + image/svg+xml; + + server { + listen 80; + listen 443 ssl; + server_name {{ansible_host}}; + + ssl_certificate /etc/cockpit/ws-certs.d/0-self-signed.cert; + ssl_certificate_key /etc/cockpit/ws-certs.d/0-self-signed.key; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers off; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 1d; + ssl_session_tickets off; + + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; + + location / { + proxy_pass http://127.0.0.1:9090/; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_http_version 1.1; + proxy_buffering off; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + gzip off; + } + + access_log /var/log/nginx/cockpit-access.log; + error_log /var/log/nginx/cockpit-error.log warn; + } +} diff --git a/vars/main.yaml b/vars/main.yaml new file mode 100644 index 0000000..ab2df9c --- /dev/null +++ b/vars/main.yaml @@ -0,0 +1,8 @@ +apt_packages: + - ca-certificates + - cockpit + - curl + - nginx + - ufw + - unattended-upgrades + - vim |