summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--.gitlab-ci.yml30
-rw-r--r--files/cockpit.socket.override.conf3
-rw-r--r--inventory.yaml.example10
-rw-r--r--main.yaml9
-rw-r--r--requirements.txt10
-rw-r--r--tasks/cockpit_configure.yaml105
-rw-r--r--tasks/harden.yaml151
-rw-r--r--tasks/preflight.yaml16
-rw-r--r--templates/cockpit.conf.j29
-rw-r--r--templates/nginx.conf.j283
-rw-r--r--vars/main.yaml8
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