From 0efe13e76b8870f95ce38bca486db9d5b9e7af46 Mon Sep 17 00:00:00 2001 From: Alex Tavarez Date: Tue, 11 Nov 2025 00:58:10 -0500 Subject: [PATCH] refactor: restructured project for higher-utility naming practices and optimized data structures for variables --- .ansible/collections/requirements.yml | 5 - .../files/sshd_config.d/sftp.conf.example | 8 - .ansible/roles/lockdown/defaults/main.yml | 16 - .../lockdown/files/bash/bash_aliases.example | 12 - .../files/bash/bash_functions.example | 13 - .../roles/lockdown/files/ssh/config.example | 30 -- .../files/sshd_config.d/auth.conf.example | 2 - .../files/sshd_config.d/denyroot.conf.example | 1 - .../lockdown/files/xdg/user-dirs.dirs.example | 15 - .ansible/roles/lockdown/handlers/main.yml | 13 - .ansible/roles/lockdown/tasks/deshell.yml | 12 - .ansible/roles/lockdown/tasks/git.yml | 128 -------- .ansible/roles/lockdown/tasks/gpg.yml | 54 ---- .ansible/roles/lockdown/tasks/main.yml | 172 ---------- .gitignore | 71 +--- ansible.cfg | 6 +- group_vars/all | 37 +++ host_vars/vps1 | 129 ++++++++ hosts.yml.example | 37 --- playbooks/deroot.yml | 19 ++ .../locals/xdg/usr-dirs.defaults.example | 15 - .../servers/xdg/user-dirs.defaults.example | 15 - playbooks/group_vars/locals/main.yml.example | 6 - playbooks/group_vars/locals/vault.yml.example | 7 - playbooks/group_vars/servers/main.yml.example | 6 - .../group_vars/servers/vault.yml.example | 7 - playbooks/init.yml | 20 ++ playbooks/init_login.yml | 197 ----------- playbooks/manage_root.yml | 27 -- playbooks/servers.yml | 5 - playbooks/vars/ssh_keys.yml.example | 4 - .../lockdown => roles/bootstrap}/README.md | 0 .../bootstrap/defaults/main/software.yml | 0 roles/bootstrap/files/gitconfig.d/commit.msg | 9 + .../bootstrap/files/gitconfig.d/exclude.rules | 0 .../files/sshd_config.d/sftp.example.conf | 18 ++ roles/bootstrap/handlers/caddy.yml | 3 + roles/bootstrap/handlers/git.yml | 306 ++++++++++++++++++ roles/bootstrap/handlers/main.yml | 6 + .../bootstrap}/meta/main.yml | 0 roles/bootstrap/tasks/configure_gpg@linux.yml | 38 +++ roles/bootstrap/tasks/configure_ssh@linux.yml | 131 ++++++++ roles/bootstrap/tasks/create_users@linux.yml | 62 ++++ roles/bootstrap/tasks/init@linux.yml | 9 + roles/bootstrap/tasks/install@linux.yml | 86 +++++ roles/bootstrap/tasks/main.yml | 9 + .../templates/sshd_config.d/allowance.conf.j2 | 15 + .../templates/sshd_config.d/auth.conf.j2 | 27 ++ .../templates/sshd_config.d/denyroot.conf.j2 | 5 + .../templates/sshd_config.d/harden.conf.j2 | 2 + .../bootstrap}/tests/inventory | 0 .../bootstrap}/tests/test.yml | 2 +- roles/bootstrap/vars/main/software.yml | 208 ++++++++++++ 53 files changed, 1151 insertions(+), 874 deletions(-) delete mode 100644 .ansible/collections/requirements.yml delete mode 100644 .ansible/roles/bootstrap/files/sshd_config.d/sftp.conf.example delete mode 100644 .ansible/roles/lockdown/defaults/main.yml delete mode 100644 .ansible/roles/lockdown/files/bash/bash_aliases.example delete mode 100644 .ansible/roles/lockdown/files/bash/bash_functions.example delete mode 100644 .ansible/roles/lockdown/files/ssh/config.example delete mode 100644 .ansible/roles/lockdown/files/sshd_config.d/auth.conf.example delete mode 100644 .ansible/roles/lockdown/files/sshd_config.d/denyroot.conf.example delete mode 100644 .ansible/roles/lockdown/files/xdg/user-dirs.dirs.example delete mode 100644 .ansible/roles/lockdown/handlers/main.yml delete mode 100644 .ansible/roles/lockdown/tasks/deshell.yml delete mode 100644 .ansible/roles/lockdown/tasks/git.yml delete mode 100644 .ansible/roles/lockdown/tasks/gpg.yml delete mode 100644 .ansible/roles/lockdown/tasks/main.yml create mode 100644 group_vars/all create mode 100644 host_vars/vps1 delete mode 100644 hosts.yml.example create mode 100644 playbooks/deroot.yml delete mode 100644 playbooks/files/locals/xdg/usr-dirs.defaults.example delete mode 100644 playbooks/files/servers/xdg/user-dirs.defaults.example delete mode 100644 playbooks/group_vars/locals/main.yml.example delete mode 100644 playbooks/group_vars/locals/vault.yml.example delete mode 100644 playbooks/group_vars/servers/main.yml.example delete mode 100644 playbooks/group_vars/servers/vault.yml.example create mode 100644 playbooks/init.yml delete mode 100644 playbooks/init_login.yml delete mode 100644 playbooks/manage_root.yml delete mode 100644 playbooks/servers.yml delete mode 100644 playbooks/vars/ssh_keys.yml.example rename {.ansible/roles/lockdown => roles/bootstrap}/README.md (100%) rename .ansible/roles/bootstrap/defaults/main.yml => roles/bootstrap/defaults/main/software.yml (100%) create mode 100644 roles/bootstrap/files/gitconfig.d/commit.msg create mode 100644 roles/bootstrap/files/gitconfig.d/exclude.rules create mode 100644 roles/bootstrap/files/sshd_config.d/sftp.example.conf create mode 100644 roles/bootstrap/handlers/caddy.yml create mode 100644 roles/bootstrap/handlers/git.yml create mode 100644 roles/bootstrap/handlers/main.yml rename {.ansible/roles/lockdown => roles/bootstrap}/meta/main.yml (100%) create mode 100644 roles/bootstrap/tasks/configure_gpg@linux.yml create mode 100644 roles/bootstrap/tasks/configure_ssh@linux.yml create mode 100644 roles/bootstrap/tasks/create_users@linux.yml create mode 100644 roles/bootstrap/tasks/init@linux.yml create mode 100644 roles/bootstrap/tasks/install@linux.yml create mode 100644 roles/bootstrap/tasks/main.yml create mode 100644 roles/bootstrap/templates/sshd_config.d/allowance.conf.j2 create mode 100644 roles/bootstrap/templates/sshd_config.d/auth.conf.j2 create mode 100644 roles/bootstrap/templates/sshd_config.d/denyroot.conf.j2 create mode 100644 roles/bootstrap/templates/sshd_config.d/harden.conf.j2 rename {.ansible/roles/lockdown => roles/bootstrap}/tests/inventory (100%) rename {.ansible/roles/lockdown => roles/bootstrap}/tests/test.yml (84%) create mode 100644 roles/bootstrap/vars/main/software.yml diff --git a/.ansible/collections/requirements.yml b/.ansible/collections/requirements.yml deleted file mode 100644 index a6fef9f..0000000 --- a/.ansible/collections/requirements.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -collections: - - name: community.general - version: "11.2.1" - source: "https://galaxy.ansible.com" \ No newline at end of file diff --git a/.ansible/roles/bootstrap/files/sshd_config.d/sftp.conf.example b/.ansible/roles/bootstrap/files/sshd_config.d/sftp.conf.example deleted file mode 100644 index 6c623b1..0000000 --- a/.ansible/roles/bootstrap/files/sshd_config.d/sftp.conf.example +++ /dev/null @@ -1,8 +0,0 @@ -Match Group ftp - ForceCommand internal-sftp -u 003 -d /%u - AuthorizedKeysFile /srv/.ftp/authorized_keys - ChrootDirectory /srv/ftp - PasswordAuthentication no - AllowAgentForwarding no - AllowTcpForwarding no - X11Forwarding no \ No newline at end of file diff --git a/.ansible/roles/lockdown/defaults/main.yml b/.ansible/roles/lockdown/defaults/main.yml deleted file mode 100644 index a8728d4..0000000 --- a/.ansible/roles/lockdown/defaults/main.yml +++ /dev/null @@ -1,16 +0,0 @@ -#SPDX-License-Identifier: MIT-0 ---- -# defaults file for lockdown -files_mode: no -create_users: - - username: "{{ hostvars[inventory_hostname]['passwords'][0].username }}" - password: "{{ hostvars[inventory_hostname]['passwords'][0].password }}" -ssh_pubkey_filename_pattern: '.*\.pub' -include_root_lock: yes -gpg_private_keys_origin_host: localhost -ssh_keypairs_origin_host: localhost -gpg_origin_private_keyids: [] # @NOTE list of gpg key ids from origin or source server -gpg_origin_private_key_passwords: "{{ vaulted_gpg_origin_private_key_passwords }}" # @NOTE list of gpg key passwords from origin or source server -ssh_origin_keypairs_filenames: [] # @NOTE list of basenames (filename sans extension) of SSH keypairs -git_config_name: ~ # @NOTE: has equivalent field under lockdown role vars example YAML file, but different value -git_config_email: ~ # @NOTE: has equivalent field under lockdown role vars example YAML file, but different value \ No newline at end of file diff --git a/.ansible/roles/lockdown/files/bash/bash_aliases.example b/.ansible/roles/lockdown/files/bash/bash_aliases.example deleted file mode 100644 index 2607751..0000000 --- a/.ansible/roles/lockdown/files/bash/bash_aliases.example +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/bash -# ssh aliases -alias ssh_send="scp -C" -# @NOTE consider adding aliases for scp source or destination hosts - -# flatpak aliases -alias clone="rsync -pogAXtlHrDx --stats --info=progress2" -alias flatshell="flatpak run --user --command=sh" -alias codium="flatpak run --user com.vscodium.codium" - -# podman aliases -alias docker="podman" \ No newline at end of file diff --git a/.ansible/roles/lockdown/files/bash/bash_functions.example b/.ansible/roles/lockdown/files/bash/bash_functions.example deleted file mode 100644 index 86a5d95..0000000 --- a/.ansible/roles/lockdown/files/bash/bash_functions.example +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/bash -# podman bash function -conman () { - if command -v podman &> /dev/null; then - CONTAINER_MANAGER="podman" - elif command -v docker &> /dev/null; then - CONTAINER_MANAGER="docker" - else - exit 1 - fi - - $CONTAINER_MANAGER -} \ No newline at end of file diff --git a/.ansible/roles/lockdown/files/ssh/config.example b/.ansible/roles/lockdown/files/ssh/config.example deleted file mode 100644 index a7accba..0000000 --- a/.ansible/roles/lockdown/files/ssh/config.example +++ /dev/null @@ -1,30 +0,0 @@ -Host local - HostName localhost - Port 22 - # IdentitiesOnly yes - # IdentityFile ~/.ssh/*.ppk - # AddKeysToAgent yes - -Host local.me - HostName localhost - Port 22 - # User admin - # IdentitiesOnly yes - # IdentityFile ~/.ssh/*.ppk - # AddKeysToAgent yes - -Host *.local.local - HostName localhost - # IdentitiesOnly yes - # IdentityFile ~/.ssh/*.ppk - # AddKeysToAgent yes - -Host ip4.local.local - # HostName 127.0.0.1 - Port 22 - AddressFamily inet - -Host ip6.local.local - # HostName ::1 - Port 22 - AddressFamily inet6 \ No newline at end of file diff --git a/.ansible/roles/lockdown/files/sshd_config.d/auth.conf.example b/.ansible/roles/lockdown/files/sshd_config.d/auth.conf.example deleted file mode 100644 index 02c2a5c..0000000 --- a/.ansible/roles/lockdown/files/sshd_config.d/auth.conf.example +++ /dev/null @@ -1,2 +0,0 @@ -PasswordAuthentication no -PermitEmptyPasswords no \ No newline at end of file diff --git a/.ansible/roles/lockdown/files/sshd_config.d/denyroot.conf.example b/.ansible/roles/lockdown/files/sshd_config.d/denyroot.conf.example deleted file mode 100644 index 4888ff9..0000000 --- a/.ansible/roles/lockdown/files/sshd_config.d/denyroot.conf.example +++ /dev/null @@ -1 +0,0 @@ -PermitRootLogin no \ No newline at end of file diff --git a/.ansible/roles/lockdown/files/xdg/user-dirs.dirs.example b/.ansible/roles/lockdown/files/xdg/user-dirs.dirs.example deleted file mode 100644 index 8623699..0000000 --- a/.ansible/roles/lockdown/files/xdg/user-dirs.dirs.example +++ /dev/null @@ -1,15 +0,0 @@ -# This file is written by xdg-user-dirs-update -# If you want to change or add directories, just edit the line you're -# interested in. All local changes will be retained on the next run. -# Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped -# homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an -# absolute path. No other format is supported. -# -XDG_DESKTOP_DIR="$HOME/Desktop" -XDG_DOWNLOAD_DIR="$HOME/Downloads" -XDG_TEMPLATES_DIR="$HOME/Templates" -XDG_PUBLICSHARE_DIR="$HOME/Public" -XDG_DOCUMENTS_DIR="$HOME/Documents" -XDG_MUSIC_DIR="$HOME/Music" -XDG_PICTURES_DIR="$HOME/Pictures" -XDG_VIDEOS_DIR="$HOME/Videos" \ No newline at end of file diff --git a/.ansible/roles/lockdown/handlers/main.yml b/.ansible/roles/lockdown/handlers/main.yml deleted file mode 100644 index 7f179ca..0000000 --- a/.ansible/roles/lockdown/handlers/main.yml +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-License-Identifier: MIT-0 ---- -# handlers file for lockdown -- name: Restart SSH server - when: ansible_facts["user_id"] == "root" - ansible.builtin.service: - name: ssh - state: restarted - tags: - - default - - finalize - register: restarted_ssh - listen: "restart ssh" diff --git a/.ansible/roles/lockdown/tasks/deshell.yml b/.ansible/roles/lockdown/tasks/deshell.yml deleted file mode 100644 index e051da2..0000000 --- a/.ansible/roles/lockdown/tasks/deshell.yml +++ /dev/null @@ -1,12 +0,0 @@ -# SPDX-License-Identifier: MIT-0 ---- -# tasks file for lockdown -- name: Disable shell for root user - when: ansible_facts["user_id"] != "root" - become: true - ansible.builtin.user: - name: root - shell: /sbin/nologin - tags: - - deshell_root - register: root_shell_disabled \ No newline at end of file diff --git a/.ansible/roles/lockdown/tasks/git.yml b/.ansible/roles/lockdown/tasks/git.yml deleted file mode 100644 index 4794c1e..0000000 --- a/.ansible/roles/lockdown/tasks/git.yml +++ /dev/null @@ -1,128 +0,0 @@ -# 'preferred_signing_key' -> 'gpg_preferred_signing' -# 'gpg_or_ssh_git_signing' -> 'git_signing_key_type' -- name: Install git package - ansible.builtin.package: - name: git - state: latest -- name: Configure git name and email - block: - - name: Configure git name - community.general.git_config: - name: user.name - scope: global - state: present - value: "{{ git_config_name }}" - - name: Configure git email - community.general.git_config: - name: user.email - scope: global - state: present - value: "{{ git_config_email }}" -- name: Configure git signing GPG key - when: git_signing_key_type == "gpg" - block: - - name: Configure specified git signing GPG key - when: preferred_signing_key > -1 - community.general.git_config: - name: user.signingkey - scope: global - state: present - value: "{{ gpg_origin_private_keyids[preferred_signing_key] }}" - register: selected_signing_key - - name: Configure random git signing GPG key - when: preferred_signing_key <= -1 - community.general.git_config: - name: user.signingkey - scope: global - state: present - value: "{{ gpg_origin_private_keyids | random }}" - register: selected_signing_key -- name: Configure git signing SSH key - when: git_signing_key_type == "ssh" - block: - - name: Acquire SSH key-pairs from other system - when: not files_mode - block: - - name: Acquire private SSH keys from other system - delegate_to: "{{ ssh_keypairs_origin_host }}" - ansible.builtin.command: - argv: - - cat - - "~/.ssh/{{ item }}.ppk" - loop: "{{ ssh_origin_keypairs_filenames }}" - register: ssh_secrets - - name: Find SSH public keys in other system - delegate_to: "{{ ssh_keypairs_origin_host }}" - ansible.builtin.command: - argv: - - cat - - "~/.ssh/{{ item }}.pub" - loop: "{{ ssh_origin_keypairs_filenames }}" - register: ssh_nonsecrets - - name: Create private SSH keys - ansible.builtin.copy: - content: "{{ item }}" - dest: "{{ ansible_facts['user_dir'] }}/.ssh/{{ ssh_origin_keypairs_filenames[idx] }}.ppk" - force: yes - backup: yes - mode: "0600" - state: present - loop: "{{ ssh_secrets.results }}" - loop_control: - index_var: idx - register: created_ssh_private_keys - - name: Create public SSH keys - ansible.builtin.copy: - content: "{{ item }}" - dest: "{{ ansible_facts['user_dir'] }}/.ssh/{{ ssh_origin_keypairs_filenames[idx] }}.pub" - force: yes - backup: yes - mode: "0644" - state: present - loop: "{{ ssh_nonsecrets.results }}" - loop_control: - index_var: idx - register: created_ssh_public_keys - - name: Acquire SSH key-pairs - when: files_mode - block: - - name: Transfer private SSH keys - ansible.builtin.copy: - src: ssh/{{ ansible_facts['user_id'] }}/{{ item }}.ppk - dest: "{{ ansible_facts['user_dir'] }}/.ssh/{{ item }}.ppk" - force: yes - backup: yes - mode: "0600" - state: present - loop: "{{ ssh_origin_keypairs_filenames }}" - loop_control: - index_var: idx - register: created_ssh_private_keys - - name: Transfer public SSH keys - ansible.builtin.copy: - src: ssh/{{ ansible_facts['user_id'] }}/{{ item }}.pub - dest: "{{ ansible_facts['user_dir'] }}/.ssh/{{ item }}.pub" - force: yes - backup: yes - mode: "0644" - state: present - loop: "{{ ssh_origin_keypairs_filenames }}" - loop_control: - index_var: idx - register: created_ssh_public_keys - - name: Configure acquired, specified SSH public key as git signing key - when: preferred_signing_key > -1 - community.general.git_config: - name: user.signingkey - scope: global - state: present - value: "{{ created_ssh_public_keys.results[preferred_signing_key] }}" - register: selected_signing_key - - name: Configure acquired, random SSH public key as git signing key - when: preferred_signing_key <= -1 - community.general.git_config: - name: user.signingkey - scope: global - state: present - value: "{{ created_ssh_public_keys.results | random }}" - register: selected_signing_key diff --git a/.ansible/roles/lockdown/tasks/gpg.yml b/.ansible/roles/lockdown/tasks/gpg.yml deleted file mode 100644 index f206e9c..0000000 --- a/.ansible/roles/lockdown/tasks/gpg.yml +++ /dev/null @@ -1,54 +0,0 @@ ---- -- name: Acquire GPG private keys from other system - when: not files_mode - block: - - name: Acquire GPG private keys' contents from other system - delegate_to: "{{ gpg_private_keys_origin_host }}" - ansible.builtin.command: - argv: - - gpg - - -a - - --export-secret-key - - "{{ item }}" - loop: "{{ gpg_origin_private_keyids }}" - register: gpg_secrets - - name: Create GPG private keys using acquired GPG private keys' contents - ansible.builtin.copy: - content: "{{ item }}" - dest: "{{ ansible_facts['user_dir'] }}/.gnupg/{{ gpg_origin_private_keyids[idx] }}.priv.asc" - force: yes - backup: yes - mode: "0600" - state: present - loop: "{{ gpg_secrets.results }}" - loop_control: - index_var: idx - register: created_gpg_private_keys -- name: Acquire GPG private keys - when: files_mode - ansible.builtin.copy: - src: gnupg/{{ ansible_facts['user_id'] }}/{{ item }}.asc - dest: "{{ ansible_facts['user_dir'] }}/.gnupg/{{ item }}.priv.asc" - force: yes - backup: yes - mode: "0600" - state: present - loop: "{{ gpg_origin_private_keyids }}" - loop_control: - index_var: idx - register: created_gpg_private_keys -- name: Import GPG private keys - when: (gpg_origin_private_key_passwords | length) == (gpg_origin_private_keyids | length) - ansible.builtin.command: - argv: - - gpg - - --batch - - --import - - --yes - - --passphrase-fd - - 0 - - "{{ item.dest }}" - stdin: "{{ gpg_origin_private_key_passwords[idx] }}" - loop: "{{ created_gpg_private_keys.results }}" - loop_control: - index_var: idx \ No newline at end of file diff --git a/.ansible/roles/lockdown/tasks/main.yml b/.ansible/roles/lockdown/tasks/main.yml deleted file mode 100644 index ea027f4..0000000 --- a/.ansible/roles/lockdown/tasks/main.yml +++ /dev/null @@ -1,172 +0,0 @@ -# SPDX-License-Identifier: MIT-0 ---- -# tasks file for lockdown -# @NOTE: assumes one logged in to SSH server as root to begin with, hence no need for privilege escalation -- name: Create users - when: ansible_facts["user_id"] == "root" - block: - - name: Create sys-admin user - ansible.builtin.user: - name: "{{ create_users[0].username }}" - uid: 1000 - password: "{{ create_users[0].password }}" - append: yes - groups: - - sudo - shell: /bin/bash - generate_ssh_key: yes - password_expire_min: 93 - password_expire_max: 186 - password_expire_warn: 45 - comment: sysadmin - # ssh_key_passphrase: "{{ item.password }}" - state: present - tags: - - default - - administrative_user - register: created_admin - - name: Create new user - ansible.builtin.user: - name: "{{ item.username }}" - uid: 1000 - password: "{{ item.password }}" - append: yes - shell: /bin/bash - generate_ssh_key: yes - password_expire_min: 93 - password_expire_max: 186 - password_expire_warn: 45 - comment: administrator - # ssh_key_passphrase: "{{ item.password }}" - state: present - loop: "{{ create_users[1:] }}" - tags: - - other_users - register: created_users -- name: Specify authorized SSH keys for users based on local public keys - when: not files_mode and ansible_facts["user_id"] == "root" - block: - - name: Acquire list of SSH public keys for sys-admin user - delegate_to: "{{ ssh_keypairs_origin_host }}" - ansible.builtin.find: - paths: "{{ lookup('env', 'HOME') }}/.ssh" - patterns: - - '{{ ssh_pubkey_filename_pattern }}' - use_regex: yes - recurse: no - tags: - - default - - administrative_user - - admin_ssh - register: ssh_public_keys - - name: Acquire contents of SSH public keys for sys-admin user - delegate_to: "{{ ssh_keypairs_origin_host }}" - ansible.builtin.command: - argv: - - cat - - "{{ item.path }}" - loop: "{{ ssh_public_keys.files }}" - register: ssh_public_keys_contents - - name: Register SSH public keys as sys-admin user's authorized keys - ansible.builtin.lineinfile: - path: "{{ created_admin.home }}/.ssh/authorized_keys" - line: "{{ item }}" - owner: "{{ created_admin.name }}" - group: "{{ created_admin.name }}" - mode: "0600" - create: yes - insertafter: EOF - state: present - tags: - - default - - administrative_user - - admin_ssh - loop: "{{ ssh_public_keys_contents.results }}" - - name: Register SSH public keys as other users' authorized keys - ansible.builtin.copy: - src: "ssh/{{ item.name }}/authorized_keys" - dest: "{{ item.home }}/.ssh/authorized_keys" - force: yes - backup: yes - owner: "{{ item.name }}" - group: "{{ item.name }}" - mode: "0600" - state: present - tags: - - other_users - - others_ssh - loop: "{{ created_users.results }}" - register: authorized_ssh_pubkeys -- name: Specify authorized SSH keys for users - when: files_mode and ansible_facts["user_id"] == "root" - block: - - name: Specify authorized keys file for sys-admin user - ansible.builtin.copy: - src: ssh/authorized_keys - dest: "{{ created_admin.home }}/.ssh/authorized_keys" - force: yes - backup: yes - owner: "{{ created_admin.name }}" - group: "{{ created_admin.name }}" - mode: "0600" - state: present - tags: - - default - - administrative_user - - admin_ssh - register: authorized_admin_ssh_pubkeys - - name: Specify authorized keys file for other users - ansible.builtin.copy: - src: "ssh/{{ item.name }}/authorized_keys" - dest: "{{ item.home }}/.ssh/authorized_keys" - force: yes - backup: yes - owner: "{{ item.name }}" - group: "{{ item.name }}" - mode: "0600" - tags: - - other_users - - others_ssh - loop: "{{ created_users.results }}" - register: authorized_ssh_pubkeys -- name: Lock down root SSH access - when: ansible_facts["user_id"] == "root" - block: - - name: Constrain SSH authentication methods to using SSH key - ansible.builtin.copy: - src: sshd_config.d/auth.conf - dest: /etc/ssh/sshd_config.d/auth.conf - force: yes - backup: yes - owner: root - group: root - mode: "0644" - state: present - tags: - - depass_root - register: constrained_auth - - name: Prohibit access to root via SSH - ansible.builtin.copy: - src: sshd_config.d/denyroot.conf - dest: /etc/ssh/sshd_config.d/denyroot.conf - force: yes - backup: yes - owner: root - group: root - mode: "0644" - state: present - tags: - - prohib_root_ssh - register: prohibited_root_ssh_login - - name: Lock the root account - when: include_root_lock - ansible.builtin.user: - name: root - password_lock: yes - tags: - - delog_root - register: prohibited_root_login - tags: - - default - - deroot - notify: "restart ssh" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7ed9c7c..f8e6f2f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,65 +1,10 @@ -/.env/ -/.tmp/ -rika/ -senpai/ -.galaxy_cache/ -/galaxy_token -.ansible/log.txt -.ansible/facts/ -.ansible/collections/ansible_collections/ +.env/ *.bak - -group_vars/**/main.yml -host_vars/**/main.yml -**/group_vars/**/main.yml -**/host_vars/**/main.yml -group_vars/**/vault.yml -host_vars/**/vault.yml -**/group_vars/**/vault.yml -**/host_vars/**/vault.yml - -.ansible/roles/**/vars/* -playbooks/vars/ssh_keys_vault.yml -playbooks/vars/ssh_keys.yml -**/playbooks/vars/ssh_keys_vault.yml -**/playbooks/vars/ssh_keys.yml -playbooks/vars/main.yml -playbooks/vars/vault.yml -**/playbooks/vars/main.yml -**/playbooks/vars/vault.yml - -files/**/**/config -**/files/**/**/config -files/**/**/authorized_keys -**/files/**/**/authorized_keys -files/**/**/*.conf -**/files/**/**/*.conf -files/**/**/*.dirs -**/files/**/**/*.dirs -files/**/**/*.defaults -**/files/**/**/*.defaults -files/**/**/bash_aliases -**/files/**/**/bash_aliases -files/**/**/bash_functions -**/files/**/**/bash_functions - -templates/**/**/config -**/templates/**/**/config -templates/**/**/authorized_keys -**/templates/**/**/authorized_keys -templates/**/**/*.conf -**/templates/**/**/*.conf -templates/**/**/*.dirs -**/templates/**/**/*.dirs -templates/**/**/*.defaults -**/templates/**/**/*.defaults -templates/**/**/bash_aliases -**/templates/**/**/bash_aliases -templates/**/**/bash_functions -**/templates/**/**/bash_functions - -hosts.ini hosts.yml -hosts.yaml -hosts.json -vault.yml \ No newline at end of file +.secrets/* +**/*.key +**/*.gpg +**/*.asc +**/*.pem +**/*.ppk +log.txt \ No newline at end of file diff --git a/ansible.cfg b/ansible.cfg index b6b6754..b9eaa09 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -54,7 +54,7 @@ fact_caching_connection=.ansible/facts # (pathspec) Colon separated paths in which Ansible will search for collections content. Collections must be in nested *subdirectories*, not directly in these directories. For example, if ``COLLECTIONS_PATHS`` includes ``'{{ ANSIBLE_HOME ~ "/collections" }}'``, and you want to add ``my.collection`` to that directory, it must be saved as ``'{{ ANSIBLE_HOME} ~ "/collections/ansible_collections/my/collection" }}'``. -collections_path=collections:.ansible/collections:/usr/share/ansible/collections +collections_path=.ansible/collections:collections:/usr/share/collections:/etc/ansible/collections # (boolean) A boolean to enable or disable scanning the sys.path for installed collections ;collections_scan_sys_path=True @@ -223,7 +223,7 @@ netconf_plugins=plugins/netconf:.ansible/netconf:/usr/share/ansible/plugins/netc ;remote_user= # (pathspec) Colon separated paths in which Ansible will search for Roles. -roles_path=roles:.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles +roles_path=.ansible/roles:roles:/usr/share/ansible/roles:/etc/ansible/roles # (string) Set the main callback used to display Ansible output. You can only have one at a time. # You can have many other callbacks, but just one can be in charge of stdout. @@ -357,7 +357,7 @@ host_key_checking=False ;old_plugin_cache_clear=False # (path) A number of non-playbook CLIs have a ``--playbook-dir`` argument; this sets the default value for it. -playbook_dir=playbooks +playbook_dir=playbooks:/etc/ansible/playbooks # (string) This sets which playbook dirs will be used as a root to process vars plugins, which includes finding host_vars/group_vars ;playbook_vars_root=top diff --git a/group_vars/all b/group_vars/all new file mode 100644 index 0000000..15fa8ea --- /dev/null +++ b/group_vars/all @@ -0,0 +1,37 @@ +#SPDX-License-Identifier: MIT-0 +--- +# vars file +custom_vars: ~ +fqdn: ~ +vps_service: + # @DOC + exists: true + # @DOC > + password: ~ + # @DOC > + api_key: ~ + # @DOC + type: "linode" + # @DOC + region: "us-east" + # @DOC > + ssh_authorized_keys: [] + # @DOC + root_fate: disposal +# @DOC > +keywords: [] +groups: + sample_group: + group_name: ~ + type: ~ +users: + admin: + username: admin + password: "password123" + shell: /bin/bash + home: ~ + admin: true + type: regular + group: ~ + groups: [sudo] + ssh_authorized_keys: diff --git a/host_vars/vps1 b/host_vars/vps1 new file mode 100644 index 0000000..446786b --- /dev/null +++ b/host_vars/vps1 @@ -0,0 +1,129 @@ +#SPDX-License-Identifier: MIT-0 +--- +# vars file +custom_vars: + generality: + ssh_authorized_keys: + - sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIIO0sbFLwfgSWpWwn4cy4cddKvV74efUMZVYTTjX2vnjAAAABHNzaDo= rika@hikiki + - sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIHJqHHMplgqm8yiq4Qwisk67p9+f9sLM8tIAzuw2qkwpAAAABHNzaDo= rika@hikiki + ssh_private_key_paths: + - ~/.ssh/id_ed25519_sukaato_yubikey.ppk + - ~/.ssh/id_ed25519_sukaato_miniyubikey.ppk +fqdn: sukaato.moe +vps_service: + exists: true + password: !vault | + $ANSIBLE_VAULT;1.2;AES256;vps1-root + 39303536373434346134346536653462623164373265646430636330616666323437363365366364 + 3030303736323432636631306361313031376238356335350a653032376432333562663361623236 + 30313766633662656637623033313461633662303763306361313337623965396130616531323061 + 6538316265376536630a616330666430323631393035313933346332353939313833623666636164 + 61653264643933636666613665633461646336656337383730396262633239376439 + api_key: !vault | + $ANSIBLE_VAULT;1.2;AES256;vps1-api + 36353161313366323930643037643637636664373266356433616632313766386161666663336366 + 3462666366646338616561643939333134666162616465320a376364363833653136366434633264 + 63643364626235666333363335656536396239646562393837343138653737346537316536303739 + 6565633730326234350a366435653637373061336162343134643431613034623761666264393134 + 61343062323933366235356132376366636534343530316432336265316632393531303161316632 + 64323431666361303137313937316631393266643961613863643035333237613931343533303537 + 66643166303733333761313566343030343762306633613733613762386339653663323730666637 + 38663634383531633838 + type: "linode" + region: "us-east" + ssh_authorized_keys: + - sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBNoC2Z4oLDEEeX7SmRpUlyXVQ+uJg3ZdjMaDONzJtMuZa9/bVzAByiNTXM0yYzas/lFLpOKh3tUw8NCS+3QMjkIAAAAEc3NoOg== rika@hikiki + - sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBDJjW/BGw3Rkr7pB69hwGGCD3poBWMRLPdUlrTjYqP/Lam5FZATRlzpDbCyub0tgBZwWIiGGvS88XWosESk2lToAAAAEc3NoOg== rika@hikiki + root_fate: disposal + ssh_private_key_paths: + - ~/.ssh/id_ecdsa-sha2_sukaato_yubikey.ppk + - ~/.ssh/id_ecdsa-sha2_sukaato_miniyubikey.ppk + ssh_private_key_path_pref: 0 +keywords: + - social media + - internet + - chat + - web + - cloud + - "file-share" + - stream +groups: + # @NOTE key/field names SHOULD match value of 'group_name' key or field of its object + remote: + group_name: remote + type: system +users: + # @NOTE key/field names MUST match value of 'username' key or field of its object + senpai: + username: senpai + password: !vault | + $ANSIBLE_VAULT;1.2;AES256;vps1-senpai + 62626662666239376237616464626630393562373130623934653764333139346337313539613863 + 3163623834636235323433323066373435393432303234320a343433343334386131613062353761 + 30323832666333366330306261386435303066626664336332393263366262626430386230356161 + 3863383530616135390a383361383136366565363066326332306631323730663533623163666133 + 62646339613864356264656362326562636336376136656336323962616236396562623833313531 + 38633938386435656437383033656630373238366663323265326533333035376233646465626363 + 33316364356533616437343439653635626637393137633034613432383530376132656138333636 + 66376535346164393630383532373963663439366339646666336264393731313135343962613932 + 33316433656236353230643332333231623730323262363831396437656331626539 + shell: /bin/bash + home: ~ + admin: true + type: regular + group: ~ + groups: + - sudo + - "{{ groups.remote.group_name }}" + services: [] + ssh_authorized_keys: "{{ custom_vars.generality.ssh_authorized_keys }}" + ssh_private_key_paths: "{{ custom_vars.generality.ssh_private_key_paths }}" + ssh_private_key_path_pref: 0 + gpg_keys: + - id: 558041D5CF2AB23B # @NOTE professional + name: professional + password: !vault | + $ANSIBLE_VAULT;1.2;AES256;vps1-senpai + 30326232323038346232663635343439393130376666616165626339646461663165393539353733 + 3666346333366237643964653633306263373365373731660a663361633030613630623434353332 + 35363939356339623732623061323866353739623936353234636133303534363863666462633133 + 3462653139366138330a336433343566633066643834613836353331316163653739656230663164 + 6131 + - id: F0CA546935C02C76 # @NOTE personal + name: personal + password: !vault | + $ANSIBLE_VAULT;1.2;AES256;vps1-senpai + 62373636643365623161643266313734633632633066373863666661306433393464396565363636 + 3638353234393838623133633839316130393539356464370a346262313262623164623637323066 + 37333432313438343761636330663332383035306131643339326261386231643231353930373961 + 3466643062396465330a656362316336376338653963376137663632646266343335333036656461 + 3964 + - id: CE245A7D7CEE3639 # @NOTE undercover + name: undercover + password: !vault | + $ANSIBLE_VAULT;1.2;AES256;vps1-senpai + 38343338373839336436396431366665383437646233613036393666613339363062616134383631 + 3938333066323838623938353231623034643635663031620a646631346233653535643337623737 + 63373437653665623361663131346137336435623862396262353764323161323338663731613266 + 6466323838306131390a383962616461616237343261666630393166303932623765633239353631 + 3230 + gpg_keyid_pref: 0 + git_profile: + name: Alex Tavarez + email: ajt95@prole.biz + ftp: + username: ftp + password: ~ + shell: /sbin/nologin + home: /srv/ftp + admin: false + type: system + group: ~ + groups: + - "{{ groups.remote.group_name }}" + services: [proftpd,sftp] + ssh_authorized_keys: "{{ custom_vars.generality.ssh_authorized_keys }}" + ssh_private_key_paths: "{{ custom_vars.generality.ssh_private_key_paths }}" + ssh_private_key_path_pref: 0 + gpg_keys: [] + gpg_keyid_pref: 0 diff --git a/hosts.yml.example b/hosts.yml.example deleted file mode 100644 index 7c4347b..0000000 --- a/hosts.yml.example +++ /dev/null @@ -1,37 +0,0 @@ ---- -ungrouped: - hosts: - localhost: - ansible_host: "localhost" - localhost4: - ansible_host: "127.0.0.1" - localhost6: - ansible_host: "::1" - sukaato: - ansible_host: ~ - sukaato4: - ansible_host: ~ - sukaato6: - ansible_host: ~ -localhost_hosts: - hosts: - localhost: - localhost4: - localhost6: -sukaato_hosts: - hosts: - sukaato: - sukaato4: - sukaato6: -locals: - children: - localhost_hosts: - vars: - ansible_connection: local -name_surname: -surname_household: -servers: - children: - sukaato_hosts: - vars: - ansible_port: 22 \ No newline at end of file diff --git a/playbooks/deroot.yml b/playbooks/deroot.yml new file mode 100644 index 0000000..a9761ab --- /dev/null +++ b/playbooks/deroot.yml @@ -0,0 +1,19 @@ +--- +- name: Create new users and lock down VPS + hosts: vps1 + remote_user: root + vars: + ansible_user: root + ansible_ssh_private_key_file: "{{ vps_service.ssh_private_key_paths[vps_service.ssh_private_key_path_pref] }}" + tasks: + - name: Engage in SSH hardening and user creation + ansible.builtin.include_role: + # allow_duplicates: true + defaults_from: main + handlers_from: main + name: bootstrap + # public: false + # rolespec_validate: true + tasks_from: "init@{{ ansible_facts['system'].lowercase() }}" + vars_from: main + diff --git a/playbooks/files/locals/xdg/usr-dirs.defaults.example b/playbooks/files/locals/xdg/usr-dirs.defaults.example deleted file mode 100644 index e71da38..0000000 --- a/playbooks/files/locals/xdg/usr-dirs.defaults.example +++ /dev/null @@ -1,15 +0,0 @@ -# Default settings for user directories -# -# The values are relative pathnames from the home directory and -# will be translated on a per-path-element basis into the users locale -DESKTOP=Desktop -DOWNLOAD=Downloads -TEMPLATES=Templates -PUBLICSHARE=Public -DOCUMENTS=Documents -MUSIC=Music -PICTURES=Pictures -VIDEOS=Videos -# Another alternative is: -#MUSIC=Documents/Music -#PICTURES=Documents/Pictures \ No newline at end of file diff --git a/playbooks/files/servers/xdg/user-dirs.defaults.example b/playbooks/files/servers/xdg/user-dirs.defaults.example deleted file mode 100644 index e71da38..0000000 --- a/playbooks/files/servers/xdg/user-dirs.defaults.example +++ /dev/null @@ -1,15 +0,0 @@ -# Default settings for user directories -# -# The values are relative pathnames from the home directory and -# will be translated on a per-path-element basis into the users locale -DESKTOP=Desktop -DOWNLOAD=Downloads -TEMPLATES=Templates -PUBLICSHARE=Public -DOCUMENTS=Documents -MUSIC=Music -PICTURES=Pictures -VIDEOS=Videos -# Another alternative is: -#MUSIC=Documents/Music -#PICTURES=Documents/Pictures \ No newline at end of file diff --git a/playbooks/group_vars/locals/main.yml.example b/playbooks/group_vars/locals/main.yml.example deleted file mode 100644 index 4677dd1..0000000 --- a/playbooks/group_vars/locals/main.yml.example +++ /dev/null @@ -1,6 +0,0 @@ ---- -passwords: - - username: admin - password: "{{ vaulted_passwords.admin.password }}" -local_ssh_private_key_files: [] # @NOTE list paths to SSH private keys -chosen_local_ssh_private_key_file: "{{ local_ssh_private_key_files | random }}" diff --git a/playbooks/group_vars/locals/vault.yml.example b/playbooks/group_vars/locals/vault.yml.example deleted file mode 100644 index 8946153..0000000 --- a/playbooks/group_vars/locals/vault.yml.example +++ /dev/null @@ -1,7 +0,0 @@ ---- -# @TODO encrypt as vault -# @NOTE see https://docs.ansible.com/ansible/latest/reference_appendices/faq.html#how-do-i-generate-encrypted-passwords-for-the-user-module -# Specifically, section for hashing using python passlib library -vaulted_passwords: - admin: - password: ~ \ No newline at end of file diff --git a/playbooks/group_vars/servers/main.yml.example b/playbooks/group_vars/servers/main.yml.example deleted file mode 100644 index 4677dd1..0000000 --- a/playbooks/group_vars/servers/main.yml.example +++ /dev/null @@ -1,6 +0,0 @@ ---- -passwords: - - username: admin - password: "{{ vaulted_passwords.admin.password }}" -local_ssh_private_key_files: [] # @NOTE list paths to SSH private keys -chosen_local_ssh_private_key_file: "{{ local_ssh_private_key_files | random }}" diff --git a/playbooks/group_vars/servers/vault.yml.example b/playbooks/group_vars/servers/vault.yml.example deleted file mode 100644 index 8946153..0000000 --- a/playbooks/group_vars/servers/vault.yml.example +++ /dev/null @@ -1,7 +0,0 @@ ---- -# @TODO encrypt as vault -# @NOTE see https://docs.ansible.com/ansible/latest/reference_appendices/faq.html#how-do-i-generate-encrypted-passwords-for-the-user-module -# Specifically, section for hashing using python passlib library -vaulted_passwords: - admin: - password: ~ \ No newline at end of file diff --git a/playbooks/init.yml b/playbooks/init.yml new file mode 100644 index 0000000..68f738f --- /dev/null +++ b/playbooks/init.yml @@ -0,0 +1,20 @@ +--- +- name: Initialize VPS + hosts: localhost + connection: local + tasks: + - name: Create a VPS using Linode + when: vps_service.type == "linode" + community.general.linode_v4: + access_token: "{{ vps_service.api_key }}" + authorized_keys: "{{ vps_service.ssh_authorized_keys }}" + image: linode/debian13 + label: sukaato + private_ip: true + region: "{{ vps_service.region }}" + root_pass: "{{ vps_service.password }}" + tags: "{{ hostvars[inventory_hostname].keywords }}" + state: "{{ 'present' if vps_service.exists else 'absent' }}" + tags: + - vps_step + - linode_step \ No newline at end of file diff --git a/playbooks/init_login.yml b/playbooks/init_login.yml deleted file mode 100644 index cd2d526..0000000 --- a/playbooks/init_login.yml +++ /dev/null @@ -1,197 +0,0 @@ ---- -- name: init_login - hosts: servers # @NOTE for IPv6, switch to 'servers6' instead of 'servers4'--for both, 'servers' - vars_files: - # @NOTE if second line is uncommented with its variables actively in use, first line should too be uncommented - # - vars/ssh_keys_vault.yml - - vars/ssh_keys.yml - vars: - ansible_user: "{{ passwords[0].username }}" - # @NOTE one of below two lines should be commented/uncommented in a mutually exclusive fashion - # ansible_ssh_private_key_file: "{{ chosen_native_ssh_private_key_file | default(chosen_local_ssh_private_key_file, true) }}" # @NOTE only works with soft-coded SSH key list building - ansible_ssh_private_key_file: "{{ chosen_local_ssh_private_key_file }}" # @NOTE references an inventory / group variable - # @NOTE below three lines should only be uncommented when above two are commented and vice versa; key-based authentication should have already been enabled prior to running this playbook - # ansible_password: "{{ passwords[0].password }}" - ansible_python_interpreter: “{{ ansible_playbook_python }}” - personal_computers: locals # @NOTE can change to *_households group or {{ name }}_{{ surname }} group name - vars_prompt: - - name: gpg_or_ssh_git_signing - prompt: Enter preferred signing key type (e.g., ssh or gpg) - unsafe: yes - private: no - default: "ssh" - - name: git_preferred_signing - prompt: Enter index or number of preferred signing key (negative number for random) - unsafe: yes - private: no - default: -1 - tasks: - - name: Disable shell access for root - ansible.builtin.include_role: - name: lockdown - defaults_from: main - vars_from: main - handlers_from: main - tasks_from: deshell - apply: - become: yes - tags: - - default - - name: Create global bash aliases - become: yes - ansible.builtin.copy: - src: bash/bash_aliases - dest: /etc/bash_aliases - owner: root - group: root - follow: yes - force: yes - backup: yes - mode: "0644" - state: present - tags: - - default - - source_sys_bashrc - - name: Create global bash functions - become: yes - ansible.builtin.copy: - src: bash/bash_functions - dest: /etc/bash_functions - owner: root - group: root - follow: yes - force: yes - backup: yes - mode: "0644" - state: present - tags: - - default - - source_sys_bashrc - - name: Register bash aliases and functions to global bashrc - become: yes - ansible.builtin.blockinfile: - block: | - if [ -f /etc/bash_aliases ]; then - . /etc/bash_aliases - fi - - if [ -f /etc/bash_functions ]; then - . /etc/bash_functions - fi - path: /etc/bash.bashrc - prepend_newline: yes - marker: "# {mark} ANSIBLE MANAGED SYSTEM-WIDE BASH ALIASES AND FUNCTIONS BLOCK" - insertafter: EOF - create: yes - owner: root - group: root - backup: yes - state: present - tags: - - default - - source_sys_bashrc - - name: Start XDG configuration tasks if current host in servers group - when: "'servers' in group_names and ansible_connection != 'local'" - become: yes - block: - - name: Create XDG user home directory environment variables - ansible.builtin.copy: - src: files/servers/xdg/user-dirs.defaults - dest: /etc/xdg/user-dirs.defaults - owner: root - group: root - follow: yes - force: yes - backup: yes - mode: "0644" - state: present - - name: Create XDG user home directory environment variables - ansible.builtin.copy: - src: "xdg/{{ ansible_facts['user_id'] }}/user-dirs.dirs" - dest: "{{ ansible_facts['user_dir'] }}/.config/user-dirs.dirs" - owner: root - group: root - follow: yes - force: yes - backup: yes - mode: "0644" - state: present - tags: - - default - - create_xdg_config - - servers_exclusive - - name: Start XDG configuration tasks if current host is local or personal - when: "personal_computers in group_names or ansible_connection == 'local'" - become: yes - block: - - name: Create XDG user home directory environment variables - ansible.builtin.copy: - src: files/locals/xdg/user-dirs.defaults - dest: /etc/xdg/user-dirs.defaults - owner: root - group: root - follow: yes - force: yes - backup: yes - mode: "0644" - state: present - - name: Create XDG user home directory environment variables - ansible.builtin.copy: - src: "xdg/{{ ansible_facts['user_id'] }}/user-dirs.dirs" - dest: "{{ ansible_facts['user_dir'] }}/.config/user-dirs.dirs" - owner: root - group: root - follow: yes - force: yes - backup: yes - mode: "0644" - state: present - tags: - - default - - create_xdg_config - - locals_exclusive - - name: Start SSH configuration tasks if current host is local or personal - when: "personal_computers in group_names or ansible_connection == 'local'" - become: yes - block: - - name: Create user SSH configuration - ansible.builtin.copy: - src: "ssh/{{ ansible_facts['user_id'] }}/config" - dest: "{{ ansible_facts['user_dir'] }}/.ssh/config" - follow: yes - force: yes - backup: yes - owner: "{{ ansible_facts['user_id'] }}" - group: "{{ ansible_facts['user_id'] }}" - mode: "0600" - state: present - tags: - - default - - create_ssh_config - - locals_exclusive - - name: Import GPG private keys - ansible.builtin.include_role: - name: lockdown - defaults_from: main - vars_from: main - handlers_from: main - tasks_from: gpg - tags: - - default - - import_gpg_privkeys - - name: Set up git - ansible.builtin.include_role: - name: lockdown - defaults_from: main - vars_from: main - handlers_from: main - tasks_from: git - vars: - git_signing_key_type: gpg_or_ssh_git_signing - preferred_signing_key: git_preferred_signing - tags: - - default - - configure_git - - - diff --git a/playbooks/manage_root.yml b/playbooks/manage_root.yml deleted file mode 100644 index ffd0ad3..0000000 --- a/playbooks/manage_root.yml +++ /dev/null @@ -1,27 +0,0 @@ ---- -- name: manage_root - hosts: servers # @NOTE for IPv6, switch to 'servers6' instead of 'servers4'--for both, 'servers' - remote_user: root # MUST be run as root - vars: - ansible_user: root - # ansible_ssh_user: root - vars_prompt: - - name: ansible_password - prompt: Enter pasword for root user of VPS - unsafe: yes - private: yes - # - name: ansible_ssh_pass - # prompt: Enter pasword for root user of VPS - # unsafe: yes - # private: yes - tasks: - - name: Set up sys-admin account on VPS and secure VPS - ansible.builtin.include_role: - name: lockdown - defaults_from: main - vars_from: main - handlers_from: main - tasks_from: main - tags: - - init - \ No newline at end of file diff --git a/playbooks/servers.yml b/playbooks/servers.yml deleted file mode 100644 index 6c7611c..0000000 --- a/playbooks/servers.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -- name: Lock down VPS - ansible.builtin.import_playbook: manage_root.yml -- name: Bootstrap VPS - ansible.builtin.import_playbook: init_login.yml \ No newline at end of file diff --git a/playbooks/vars/ssh_keys.yml.example b/playbooks/vars/ssh_keys.yml.example deleted file mode 100644 index 836064c..0000000 --- a/playbooks/vars/ssh_keys.yml.example +++ /dev/null @@ -1,4 +0,0 @@ ---- -native_ssh_private_keys: "{{ vaulted_native_ssh_private_keys }}" -native_ssh_private_key_files: [] -chosen_native_ssh_private_key_file: "{{ native_ssh_private_key_files | random }}" diff --git a/.ansible/roles/lockdown/README.md b/roles/bootstrap/README.md similarity index 100% rename from .ansible/roles/lockdown/README.md rename to roles/bootstrap/README.md diff --git a/.ansible/roles/bootstrap/defaults/main.yml b/roles/bootstrap/defaults/main/software.yml similarity index 100% rename from .ansible/roles/bootstrap/defaults/main.yml rename to roles/bootstrap/defaults/main/software.yml diff --git a/roles/bootstrap/files/gitconfig.d/commit.msg b/roles/bootstrap/files/gitconfig.d/commit.msg new file mode 100644 index 0000000..3e536f0 --- /dev/null +++ b/roles/bootstrap/files/gitconfig.d/commit.msg @@ -0,0 +1,9 @@ +# @NOTE possible commit types: https://github.com/qoomon/git-conventional-commits?tab=readme-ov-file#config-file +# @NOTE for description or body consider: motivation for or cause of change, impact and domain of change +[optional scope]: + +[optional body] + +# @NOTE footer should almost be treated as metadata of/for commit, or alerts of significant impact +# @NOTE for footer purpose, see: https://dev.to/mochafreddo/a-comprehensive-guide-to-using-footers-in-conventional-commit-messages-37g6 +[optional footer(s)] \ No newline at end of file diff --git a/roles/bootstrap/files/gitconfig.d/exclude.rules b/roles/bootstrap/files/gitconfig.d/exclude.rules new file mode 100644 index 0000000..e69de29 diff --git a/roles/bootstrap/files/sshd_config.d/sftp.example.conf b/roles/bootstrap/files/sshd_config.d/sftp.example.conf new file mode 100644 index 0000000..e01ae3f --- /dev/null +++ b/roles/bootstrap/files/sshd_config.d/sftp.example.conf @@ -0,0 +1,18 @@ +Match Group ftp + ForceCommand internal-sftp -d /%u + ChrootDirectory /srv/ftp + AllowAgentForwarding no + AllowTcpForwarding no + X11Forwarding no + +Match User ftp + ForceCommand internal-sftp -d /public + AuthorizedKeysFile /srv/ftp/.ssh/authorized_keys + +Match User caddy,www-data + ForceCommand internal-sftp -d /domain1.tld + AuthorizedKeysFile /srv/www/.ssh/authorized_keys + ChrootDirectory /srv/www + AllowAgentForwarding no + AllowTcpForwarding no + X11Forwarding no diff --git a/roles/bootstrap/handlers/caddy.yml b/roles/bootstrap/handlers/caddy.yml new file mode 100644 index 0000000..149963a --- /dev/null +++ b/roles/bootstrap/handlers/caddy.yml @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT-0 +--- +# handlers file for bootstrap \ No newline at end of file diff --git a/roles/bootstrap/handlers/git.yml b/roles/bootstrap/handlers/git.yml new file mode 100644 index 0000000..1fe2a15 --- /dev/null +++ b/roles/bootstrap/handlers/git.yml @@ -0,0 +1,306 @@ +# SPDX-License-Identifier: MIT-0 +--- +# handlers file for bootstrap +- name: Configure git + listen: git + block: + # @NOTE below are system git configuration + - name: Configure git aliases + become: true + block: + - name: Configure non-coding alias for merge subcommand + community.general.git_config: + name: alias.assimilate + add_mode: replace-all + state: present + value: merge + - name: Configure alias for merge subcommand + community.general.git_config: + name: alias.mrg + add_mode: replace-all + state: present + value: merge + - name: Configure non-coding alias for add subcommand + community.general.git_config: + name: alias.approve + add_mode: replace-all + state: present + value: add + - name: Configure non-coding alias for status subcommand + community.general.git_config: + name: alias.revisions + add_mode: replace-all + state: present + value: status + - name: Configure alias for status subcommand + community.general.git_config: + name: alias.stat + add_mode: replace-all + state: present + value: status + - name: Configure non-coding alias for commit subcommand + community.general.git_config: + name: alias.finalize + add_mode: replace-all + state: present + value: commit + - name: Configure alias for commit subcommand + community.general.git_config: + name: alias.cit + add_mode: replace-all + state: present + value: commit + - name: Configure alias for config subcommand + community.general.git_config: + name: alias.cfg + add_mode: replace-all + state: present + value: config + - name: Configure non-coding alias for commit subcommand with signing flag + community.general.git_config: + name: alias.claim + add_mode: replace-all + state: present + value: commit -S + - name: Configure alias for commit subcommand with signing flag + community.general.git_config: + name: alias.sig + add_mode: replace-all + state: present + value: commit -S + - name: Configure non-coding alias for show subcommand + community.general.git_config: + name: alias.review + add_mode: replace-all + state: present + value: show + - name: Configure alias for show subcommand + community.general.git_config: + name: alias.peek + add_mode: replace-all + state: present + value: show + - name: Configure alias for add subcommand with universal staging flag + community.general.git_config: + name: alias.badd + add_mode: replace-all + state: present + value: add -A + - name: Configure non-coding alias for add subcommand with universal staging flag + community.general.git_config: + name: alias.approve-all + add_mode: replace-all + state: present + value: add -A + - name: Configure non-coding alias for rm subcommand + community.general.git_config: + name: alias.redact + add_mode: replace-all + state: present + value: rm + - name: Configure non-coding alias for init subcommand + community.general.git_config: + name: alias.author + add_mode: replace-all + state: present + value: init + - name: Configure non-coding alias for mv subcommand + community.general.git_config: + name: alias.revise + add_mode: replace-all + state: present + value: mv + - name: Configure non-coding alias for rebase subcommand + community.general.git_config: + name: alias.retroact + add_mode: replace-all + state: present + value: rebase + - name: Configure alias for rebase subcommand + community.general.git_config: + name: alias.rb + add_mode: replace-all + state: present + value: rebase + - name: Configure non-coding alias for push subcommand + community.general.git_config: + name: alias.publish + add_mode: replace-all + state: present + value: push + - name: Configure non-coding alias for pull subcommand + community.general.git_config: + name: alias.publication + add_mode: replace-all + state: present + value: pull + - name: Configure non-coding alias for fetch subcommand + community.general.git_config: + name: alias.manuscript + add_mode: replace-all + state: present + value: fetch + - name: Configure alias for fetch subcommand + community.general.git_config: + name: alias.get + add_mode: replace-all + state: present + value: fetch + - name: Configure non-coding alias for clone subcommand + community.general.git_config: + name: alias.copy + add_mode: replace-all + state: present + value: clone + - name: Configure alias for clone subcommand + community.general.git_config: + name: alias.cp + add_mode: replace-all + state: present + value: clone + - name: Configure non-coding alias for branch subcommand + community.general.git_config: + name: alias.draft + add_mode: replace-all + state: present + value: branch + - name: Configure alias for branch subcommand + community.general.git_config: + name: alias.br + add_mode: replace-all + state: present + value: branch + - name: Configure non-coding alias for switch subcommand + community.general.git_config: + name: alias.edit + add_mode: replace-all + state: present + value: switch + - name: Configure alias for switch subcommand + community.general.git_config: + name: alias.cd + add_mode: replace-all + state: present + value: switch + - name: Configure non-coding alias for restore subcommand + community.general.git_config: + name: alias.revert + add_mode: replace-all + state: present + value: restore + - name: Configure alias for restore subcommand + community.general.git_config: + name: alias.rs + add_mode: replace-all + state: present + value: restore + - name: Set default editor for git + become: true + community.general.git_config: + add_mode: replace-all + name: core.editor + state: present + value: "{{ config.git.sys.editor }}" + - name: Create a directory for storing system-level templates for git + become: true + ansible.builtin.file: + group: root + owner: root + path: /etc/gitconfig.d + state: directory + - name: Create a commit message template file for git + become: true + ansible.builtin.copy: + owner: root + group: root + backup: true + dest: /etc/gitconfig.d/commit.msg + force: true + src: gitconfig.d/commit.msg + - name: Set system-level commit message template file path for git + become: true + community.general.git_config: + add_mode: replace-all + name: commit.template + state: present + value: /etc/gitconfig.d/commit.msg + - name: Set UI to have color for git at system-level + become: true + community.general.git_config: + add_mode: replace-all + name: color.ui + state: present + value: "true" + - name: Set line-end conversion behavior for git + become: true + community.general.git_config: + add_mode: replace-all + name: core.autocrlf + state: present + value: input + # @NOTE below are user git configuration + - name: Create a user directory for a user gitignore file for git + when: ansible_facts['user_id'] in hostvars[inventory_hostname].users + ansible.builtin.file: + owner: "{{ ansible_facts['user_id'] }}" + group: "{{ hostvars[inventory_hostname].users[ansible_facts['user_id']].group | default(ansible_facts['user_id']) }}" + path: "{{ ansible_facts['user_dir'] }}/.config/git" + state: directory + - name: Create a user gitignore file for git + when: ansible_facts['user_id'] in hostvars[inventory_hostname].users + ansible.builtin.copy: + owner: "{{ ansible_facts['user_id'] }}" + group: "{{ hostvars[inventory_hostname].users[ansible_facts['user_id']].group | default(ansible_facts['user_id']) }}" + backup: true + dest: "{{ ansible_facts['user_dir'] }}/.config/git/gitignore" + force: true + src: gitconfig.d/exclude.rules + - name: Set user gitignore file path for git + when: ansible_facts['user_id'] in hostvars[inventory_hostname].users + community.general.git_config: + add_mode: replace-all + name: core.excludesfile + scope: global + state: present + value: "{{ ansible_facts['user_dir'] }}/.config/git/gitignore" + - name: Create link from user config directory git config file to user home directory git config file + when: ansible_facts['user_id'] in hostvars[inventory_hostname].users + community.general.file: + src: "{{ ansible_facts['user_dir'] }}/.gitconfig" + dest: "{{ ansible_facts['user_dir'] }}/.config/git/config" + # @TODO check whether below two attributes make sense for links + owner: "{{ ansible_facts['user_id'] }}" + group: "{{ hostvars[inventory_hostname].users[ansible_facts['user_id']].group | default(ansible_facts['user_id']) }}" + state: hard + - name: Set format for keys used by git + when: ansible_facts['user_id'] in hostvars[inventory_hostname].users + community.general.git_config: + add_mode: replace-all + name: gpg.format + scope: global + state: present + value: openpgp + - name: Set signing key to be used by git + when: ansible_facts['user_id'] in hostvars[inventory_hostname].users and hostvars[inventory_hostname].users[ansible_facts['user_id']].gpg_keys is not None and len(hostvars[inventory_hostname].users[ansible_facts['user_id']].gpg_keys) > 0 + community.general.git_config: + add_mode: replace-all + name: user.signingkey + scope: global + state: present + value: "{{ hostvars[inventory_hostname].users[ansible_facts['user_id']].gpg_keys[hostvars[inventory_hostname].users[ansible_facts['user_id']].gpg_keyid_pref].id | default((hostvars[inventory_hostname].users[ansible_facts['user_id']].gpg_keys | random).id) }}" + - name: Set name of user of git + when: ansible_facts['user_id'] in hostvars[inventory_hostname].users + community.general.git_config: + add_mode: replace-all + name: user.name + scope: global + state: present + value: "{{ hostvars[inventory_hostname].users[ansible_facts['user_id']].git_profile.name }}" + - name: Set email of user of git + when: ansible_facts['user_id'] in hostvars[inventory_hostname].users + community.general.git_config: + add_mode: replace-all + name: user.email + scope: global + state: present + value: "{{ hostvars[inventory_hostname].users[ansible_facts['user_id']].git_profile.email }}" diff --git a/roles/bootstrap/handlers/main.yml b/roles/bootstrap/handlers/main.yml new file mode 100644 index 0000000..ccd74e5 --- /dev/null +++ b/roles/bootstrap/handlers/main.yml @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: MIT-0 +--- +# handlers file for bootstrap +- name: Postinstall set-up of git + ansible.builtin.import_tasks: + file: git.yml diff --git a/.ansible/roles/lockdown/meta/main.yml b/roles/bootstrap/meta/main.yml similarity index 100% rename from .ansible/roles/lockdown/meta/main.yml rename to roles/bootstrap/meta/main.yml diff --git a/roles/bootstrap/tasks/configure_gpg@linux.yml b/roles/bootstrap/tasks/configure_gpg@linux.yml new file mode 100644 index 0000000..6798226 --- /dev/null +++ b/roles/bootstrap/tasks/configure_gpg@linux.yml @@ -0,0 +1,38 @@ +#SPDX-License-Identifier: MIT-0 +--- +# tasks file for bootstrap +- name: Create GNUPGP directory in user home directory + when: ansible_facts['user_id'] in hostvars[inventory_hostname].users + ansible.builtin.file: + group: "{{ hostvars[inventory_hostname].users[ansible_facts['user_id']].group | default(ansible_facts['user_id']) }}" + mode: "0700" + owner: "{{ ansible_facts['user_id'] }}" + path: "{{ ansible_facts['user_dir'] }}/.gnupg" + state: directory +- name: Create GPG key files + when: ansible_facts['user_id'] in hostvars[inventory_hostname].users and hostvars[inventory_hostname].users[ansible_facts['user_id']].gpg_keys is not None and len(hostvars[inventory_hostname].users[ansible_facts['user_id']].gpg_keys) > 0 + ansible.builtin.copy: + backup: true + dest: "{{ ansible_facts['user_dir'] }}/.gnupg/{{ item.id }}.key" + force: true + group: "{{ hostvars[inventory_hostname].users[ansible_facts['user_id']].group | default(ansible_facts['user_id']) }}" + mode: "0600" + owner: "{{ ansible_facts['user_id'] }}" + src: "gnupg/{{ item.id }}.key" + # validate: "gpg --verify {{ item.id }}.sig %s" + loop: "{{ hostvars[inventory_hostname].users[ansible_facts['user_id']].gpg_keys }}" + register: created_gpg_keys +- name: Import GPG key files + when: ansible_facts['user_id'] in hostvars[inventory_hostname].users and hostvars[inventory_hostname].users[ansible_facts['user_id']].gpg_keys is not None and len(hostvars[inventory_hostname].users[ansible_facts['user_id']].gpg_keys) > 0 + ansible.builtin.command: + argv: + - gpg + - --batch + - --passphrase-fd 0 + - --import + - "{{ ansible_facts['user_dir'] }}/.gnupg/{{ item.id }}.key" + stdin: "{{ item.password }}" + loop: "{{ hostvars[inventory_hostname].users[ansible_facts['user_id']].gpg_keys }}" + + + diff --git a/roles/bootstrap/tasks/configure_ssh@linux.yml b/roles/bootstrap/tasks/configure_ssh@linux.yml new file mode 100644 index 0000000..fdb22fd --- /dev/null +++ b/roles/bootstrap/tasks/configure_ssh@linux.yml @@ -0,0 +1,131 @@ +#SPDX-License-Identifier: MIT-0 +--- +# tasks file for bootstrap +- name: Create hidden SSH directories under users' home directories + when: hostvars[inventory_hostname].groups.remote.group_name in item.value.groups + ansible.builtin.file: + group: "{{ item.value.group | default(item.value.username) }}" + mode: "0700" + owner: "{{ item.value.username }}" + path: "{{ item.value.home | default('/home/' ~ item.value.username) }}/.ssh" + state: directory + loop: "{{ lookup('ansible.builtin.dict', hostvars[inventory_hostname].users) }}" + tags: + - ensure_paths + - ensure_files +- name: Add authorized SSH public keys for users + when: hostvars[inventory_hostname].groups.remote.group_name in item.value.groups and item.value.ssh_authorized_keys is not None and len(item.value.ssh_authorized_keys) > 0 + ansible.builtin.copy: + backup: true + content: "{{ item.value.ssh_authorized_keys.join('\n') }}" + dest: "{{ item.value.home | default('/home/' ~ item.value.username) }}/.ssh/authorized_keys" + # follow: true + force: true + group: "{{ item.value.group | default(item.value.username) }}" + mode: "0600" + owner: "{{ item.value.username }}" + loop: "{{ lookup('ansible.builtin.dict', hostvars[inventory_hostname].users) }}" + tags: + - ensure_files +- name: Harden SSH security + block: + - name: Set users in group ftp to only be usable with SSH's SFTP service + when: "'sftp' in item.value.services" + ansible.builtin.blockinfile: + backup: true + block: | + Match Group {{ item.value.group | default(item.value.username) }} + ForceCommand internal-sftp -d /%u + ChrootDirectory {{ item.value.home | default('/home/' ~ item.value.username) }} + AllowAgentForwarding no + AllowTcpForwarding no + X11Forwarding no + + Match User {{ item.value.username }} + ForceCommand internal-sftp -d /public + AuthorizedKeysFile {{ item.value.home | default('/home/' ~ item.value.username) }}/.ssh/authorized_keys + create: true + group: root + insertafter: EOF + marker: "# {mark} ANSIBLE-MANAGED SFTP BLOCK" + marker_begin: BEGIN + marker_end: END + owner: root + path: /etc/ssh/sshd_config.d/sftp.conf + append_newline: true + state: present + validate: /bin/sshd -t + loop: "{{ lookup('ansible.builtin.dict', hostvars[inventory_hostname].users) }}" + tags: + - sftp_auth_step + - name: Switch to preferred SSH authentication method + ansible.builtin.template: + backup: true + comment_end_string: "#}" + comment_start_string: "{#" + dest: /etc/ssh/sshd_config.d/auth.conf + # follow: true + force: true + group: root + owner: root + src: sshd_config.d/auth.conf.j2 + validate: /bin/sshd -t + vars: + empty_auth_used: false + pass_auth_used: false + pam_auth_used: false + key_auth_used: true + tags: + - ssh_auth_step + - name: Constrain idle online user accounts + ansible.builtin.template: + backup: true + comment_end_string: "#}" + comment_start_string: "{#" + dest: /etc/ssh/sshd_config.d/harden.conf + force: true + group: root + owner: root + src: sshd_config.d/harden.conf.j2 + validate: /bin/sshd -t + vars: + client_subsistence: 900 + client_subsist_warn_max: 3 + tags: + - ssh_timeout_step + - name: Toggle ability to log in as root via SSH + when: "hostvars[inventory_hostname].vps_service.root_fate == 'disposal'" + ansible.builtin.template: + backup: true + comment_end_string: "#}" + comment_start_string: "{#" + dest: /etc/ssh/sshd_config.d/denyroot.conf + force: true + group: root + owner: root + src: sshd_config.d/denyroot.conf.j2 + validate: /bin/sshd -t + vars: + root_login_allowed: false + tags: + - ssh_root_step + - name: Specify users or groups to whitelist or blacklist for SSH login + when: "hostvars[inventory_hostname].vps_service.root_fate == 'disposal'" + ansible.builtin.template: + backup: true + comment_end_string: "#}" + comment_start_string: "{#" + dest: /etc/ssh/sshd_config.d/allowance.conf + force: true + group: root + owner: root + src: sshd_config.d/allowance.conf.j2 + validate: /bin/sshd -t + vars: + list_type: whitelist + policed_groups: + - "{{ hostvars[inventory_hostname].groups.remote.group_name }}" + tags: + - ssh_gate_step + tags: + - ssh_harden_step diff --git a/roles/bootstrap/tasks/create_users@linux.yml b/roles/bootstrap/tasks/create_users@linux.yml new file mode 100644 index 0000000..625d93c --- /dev/null +++ b/roles/bootstrap/tasks/create_users@linux.yml @@ -0,0 +1,62 @@ +#SPDX-License-Identifier: MIT-0 +--- +# tasks file for bootstrap +- name: Create groups + ansible.builtin.group: + name: "{{ item.value.group_name }}" + state: present + system: "{{ 'true' if item.value.type == 'system' else 'false' }}" + loop: "{{ lookup('ansible.builtin.dict', hostvars[inventory_hostname].groups) }}" +- name: Create users + block: + - name: Create administrative users + when: "item.value.admin and item.value.type != 'system'" + ansible.builtin.user: + comment: "administrator for {{ fqdn.split('.')[0].lowercase }}" + create_home: false + home: "{{ item.value.home | default('/home/' ~ item.value.username) }}" + generate_ssh_key: true + ssh_key_comment: "ansible-generated for {{ item.value.username }}@{{ hostvars[inventory_hostname].fqdn.split('.')[0].lowercase() }}" + ssh_key_type: "ed25519" + group: "{{ item.value.group | default(item.value.username) }}" + name: "{{ item.value.username }}" + shell: "{{ item.value.shell }}" + password: "{{ item.value.password }}" + state: present + system: "{{ 'true' if item.value.type == 'system' else 'false' }}" + update_password: always + loop: "{{ lookup('ansible.builtin.dict', hostvars[inventory_hostname].users) }}" + - name: Create regular users + when: "not item.value.admin and item.value.type != 'system'" + ansible.builtin.user: + comment: "user of {{ fqdn.split('.')[0].lowercase }}" + create_home: true + home: "{{ item.value.home | default('/home/' ~ item.value.username) }}" + generate_ssh_key: true + group: "{{ item.value.group | default(item.value.username) }}" + name: "{{ item.value.username }}" + shell: "{{ item.value.shell }}" + password: "{{ item.value.password }}" + state: present + system: "{{ 'true' if item.value.type == 'system' else 'false' }}" + update_password: always + loop: "{{ lookup('ansible.builtin.dict', hostvars[inventory_hostname].users) }}" + - name: Create users for managing data related to services + when: "not item.value.admin and item.value.type == 'system' and item.value.service is not None" + ansible.builtin.user: + comment: "service data user for {{ item.value.services | random }} at {{ hostvars[inventory_hostname].fqdn.split('.')[0].lowercase() }}" + create_home: false + home: "{{ item.value.home | default('/home/' ~ item.value.username) }}" + group: "{{ item.value.group | default(item.value.username) }}" + name: "{{ item.value.username }}" + shell: "{{ item.value.shell }}" + state: present + system: "{{ 'true' if item.value.type == 'system' else 'false' }}" + loop: "{{ lookup('ansible.builtin.dict', hostvars[inventory_hostname].users) }}" +- name: Adjust users' groups + when: item.value.groups is not None and len(item.value.groups) > 0 + ansible.builtin.user: + name: "{{ item.value.username }}" + append: true + groups: "{{ item.value.groups }}" + loop: "{{ lookup('ansible.builtin.dict', hostvars[inventory_hostname].users) }}" diff --git a/roles/bootstrap/tasks/init@linux.yml b/roles/bootstrap/tasks/init@linux.yml new file mode 100644 index 0000000..9b63533 --- /dev/null +++ b/roles/bootstrap/tasks/init@linux.yml @@ -0,0 +1,9 @@ +#SPDX-License-Identifier: MIT-0 +--- +# tasks file for bootstrap +- name: Populate system with groups and user accounts + ansible.builtin.import_tasks: + file: "create_users@{{ ansible_facts['system'].lowercase() }}.yml" +- name: Configure SSH for root + ansible.builtin.import_tasks: + file: "configure_ssh@{{ ansible_facts['system'].lowercase() }}.yml" \ No newline at end of file diff --git a/roles/bootstrap/tasks/install@linux.yml b/roles/bootstrap/tasks/install@linux.yml new file mode 100644 index 0000000..5c03ca6 --- /dev/null +++ b/roles/bootstrap/tasks/install@linux.yml @@ -0,0 +1,86 @@ +#SPDX-License-Identifier: MIT-0 +--- +# tasks file for bootstrap +# @TODO create bootstrap tasks for installation and configuration of software +- name: Update and upgrade software + block: + - name: Update Aptitude package cache and upgrade Aptitude packages + when: "ansible_facts['pkg_mgr'] == 'apt'" + become: true + ansible.builtin.apt: + upgrade: yes + update_cache: yes + cache_valid_time: 86400 +- name: Install NeoVim editor + become: true + block: + - name: Install NeoVim using Aptitude package manager + when: "ansible_facts['pkg_mgr'] == 'apt'" + ansible.builtin.package: + name: neovim + use: "{{ ansible_facts['pkg_mgr'] }}" + state: present + # @TODO create handler to notify to for configuring neovim + # notify: neovim +- name: Install kitty-terminfo for SSH client xterm-kitty compatibility + become: true + block: + - name: Install kitty-terminfo using Aptitude package manager + when: "ansible_facts['pkg_mgr'] == 'apt'" + ansible.builtin.package: + name: kitty-terminfo + use: "{{ ansible_facts['pkg_mgr'] }}" + state: present +- name: Install necessary software managers and container engines + become: true + block: + - name: Install snapd + when: "ansible_facts['pkg_mgr'] == 'apt'" + block: + - name: Install snapd using Aptitude package manager + ansible.builtin.package: + name: snapd + use: "{{ ansible_facts['pkg_mgr'] }}" + state: present + - name: Install flatpak + when: "ansible_facts['pkg_mgr'] == 'apt'" + block: + - name: Install flatpak using Aptitude package manager + ansible.builtin.package: + name: flatpak + use: "{{ ansible_facts['pkg_mgr'] }}" + state: present + - name: Install podman + when: "ansible_facts['pkg_mgr'] == 'apt'" + block: + - name: Install podman using Aptitude package manager + ansible.builtin.package: + name: podman + use: "{{ ansible_facts['pkg_mgr'] }}" + state: present + - name: Install podman-compose + when: "ansible_facts['pkg_mgr'] == 'apt'" + block: + - name: Install podman-compose using Aptitude package manager + ansible.builtin.package: + name: podman-compose + use: "{{ ansible_facts['pkg_mgr'] }}" + state: present + - name: Install git + block: + - name: Install git using Aptitude package manager + when: "ansible_facts['pkg_mgr'] == 'apt'" + ansible.builtin.package: + name: git + use: "{{ ansible_facts['pkg_mgr'] }}" + state: present + notify: git +- name: Install packages + when: ansible_facts['pkg_mgr'] in item.value.name + ansible.builtin.package: + name: "{{ item.value.name[ansible_facts['pkg_mgr']] }}" + use: "{{ ansible_facts['pkg_mgr'] }}" + state: present + # @TODO research what happens when nonexistent handler is called or notify field is null + notify: "{{ item.key }}" + loop: "{{ lookup('ansible.builtin.dict', software.pkgs) }}" diff --git a/roles/bootstrap/tasks/main.yml b/roles/bootstrap/tasks/main.yml new file mode 100644 index 0000000..5627daf --- /dev/null +++ b/roles/bootstrap/tasks/main.yml @@ -0,0 +1,9 @@ +#SPDX-License-Identifier: MIT-0 +--- +# tasks file for bootstrap +- name: Configure GPG for user + ansible.builtin.import_tasks: + file: "configure_gpg@{{ ansible_facts['system'].lowercase() }}.yml" +- name: Install and configure software on system + ansible.builtin.import_tasks: + file: "install@{{ ansible_facts['system'].lowercase() }}.yml" \ No newline at end of file diff --git a/roles/bootstrap/templates/sshd_config.d/allowance.conf.j2 b/roles/bootstrap/templates/sshd_config.d/allowance.conf.j2 new file mode 100644 index 0000000..3e6dfb7 --- /dev/null +++ b/roles/bootstrap/templates/sshd_config.d/allowance.conf.j2 @@ -0,0 +1,15 @@ +{% if list_type == 'whitelist' %} +{% if policed_groups is not None and len(policed_groups) > 0 %} +AllowGroups {{ policed_groups.join(' ') }} +{% endif %} +{% if policed_users is not None and len(policed_users) > 0 %} +AllowUsers {{ policed_users.join(' ') }} +{% endif %} +{% else %} +{% if policed_groups is not None and len(policed_groups) > 0 %} +DenyGroups {{ policed_groups.join(' ') }} +{% endif %} +{% if policed_users is not None and len(policed_users) > 0 %} +DenyGroups {{ policed_users.join(' ') }} +{% endif %} +{% endif %} \ No newline at end of file diff --git a/roles/bootstrap/templates/sshd_config.d/auth.conf.j2 b/roles/bootstrap/templates/sshd_config.d/auth.conf.j2 new file mode 100644 index 0000000..42b16ca --- /dev/null +++ b/roles/bootstrap/templates/sshd_config.d/auth.conf.j2 @@ -0,0 +1,27 @@ +{% if empty_auth_used %} +PermitEmptyPasswords yes +{% else %} +PermitEmptyPasswords no +{% endif %} +{% if pass_auth_used %} +PasswordAuthentication yes +{% else %} +PasswordAuthentication no +{% endif %} +{% if kbd_auth_used is not None %} +{% if kbd_auth_used %} +KbdInteractiveAuthentication yes +{% else %} +KbdInteractiveAuthentication no # enable if implementing TOTP 2FA +{% endif %} +{% endif %} +{% if pam_auth_used %} +UsePAM yes +{% else %} +UsePAM no # enable if implementing TOTP 2FA +{% endif %} +{% if key_auth_used %} +PubkeyAuthentication yes +{% else %} +PubkeyAuthentication no +{% endif %} \ No newline at end of file diff --git a/roles/bootstrap/templates/sshd_config.d/denyroot.conf.j2 b/roles/bootstrap/templates/sshd_config.d/denyroot.conf.j2 new file mode 100644 index 0000000..8ffc195 --- /dev/null +++ b/roles/bootstrap/templates/sshd_config.d/denyroot.conf.j2 @@ -0,0 +1,5 @@ +{% if root_login_allowed %} +PermitRootLogin yes +{% else %} +PermitRootLogin no +{% endif %} \ No newline at end of file diff --git a/roles/bootstrap/templates/sshd_config.d/harden.conf.j2 b/roles/bootstrap/templates/sshd_config.d/harden.conf.j2 new file mode 100644 index 0000000..e15cc3f --- /dev/null +++ b/roles/bootstrap/templates/sshd_config.d/harden.conf.j2 @@ -0,0 +1,2 @@ +ClientAliveInterval {{ client_subsistence }} +ClientAliveCountMax {{ client_subsist_warn_max }} \ No newline at end of file diff --git a/.ansible/roles/lockdown/tests/inventory b/roles/bootstrap/tests/inventory similarity index 100% rename from .ansible/roles/lockdown/tests/inventory rename to roles/bootstrap/tests/inventory diff --git a/.ansible/roles/lockdown/tests/test.yml b/roles/bootstrap/tests/test.yml similarity index 84% rename from .ansible/roles/lockdown/tests/test.yml rename to roles/bootstrap/tests/test.yml index c583120..e5df577 100644 --- a/.ansible/roles/lockdown/tests/test.yml +++ b/roles/bootstrap/tests/test.yml @@ -3,4 +3,4 @@ - hosts: localhost remote_user: root roles: - - lockdown + - bootstrap diff --git a/roles/bootstrap/vars/main/software.yml b/roles/bootstrap/vars/main/software.yml new file mode 100644 index 0000000..9c80eb9 --- /dev/null +++ b/roles/bootstrap/vars/main/software.yml @@ -0,0 +1,208 @@ +#SPDX-License-Identifier: MIT-0 +--- +# vars file for bootstrap +# @TODO make list or dictionary of software to be installed in bootstrap task +software: + pkgs: + # @NOTE keep fields or keys constant; otherwise will have to edit handler notifiers and listeners elsewhere + gocryptfs: + name: + apt: gocryptfs + lua-lang: + name: + apt: lua5.4 + lua-docs: + name: + apt: luadoc + lua-pkg: + name: + apt: luarocks + python-lang: + name: + apt: python3 + python-pkg: + name: + apt: python3-pip + python-linter: + name: + apt: python3-doc8 + python-docs: + name: + apt: python3-doc + rust-lang: + name: + apt: rustc # @NOTE alternative: rustup + rust-pkg: + name: + apt: cargo + rust-debugger: + name: + apt: rust-analyzer + rust-linter: + name: + apt: rust-clippy + rust-docs: + name: + apt: rust-doc + java-lang: + name: + apt: default-jdk-headless + java-docs: + name: + apt: default-jdk-doc + java-runtime: + name: + apt: default-jre-headless + kotlin-lang: + name: + apt: kotlin + swift-lang: + name: + apt: swiftlang + swift-docs: + name: + apt: swiftlang-doc + erlang-lang: + name: + apt: erlang + erlang-pkg: + name: + apt: erlang-hex + erlang-docs: + name: + apt: erlang-doc + elixir-lang: + name: + apt: elixir + crystal-lang: + name: + apt: crystal + crystal-docs: + name: + apt: crystal-doc + javascript-lang: + name: + apt: nodejs + javascript-docs: + name: + apt: nodejs-doc + javascript-pkg: + name: + apt: npm + javascript-linter: + name: + apt: eslint + php-lang: + name: + apt: php + php-docs: + name: + apt: php-common + php-debugger: + name: + apt: php-xdebug + php-pkg: + name: + apt: composer + html-linter: + name: + apt: tidy + json-linter: + name: + apt: jsonlint + yaml-linter: + name: + apt: yamllint + pandoc: + name: + apt: pandoc + distrobox: + name: + apt: distrobox + # @TODO manually install the commented below on current active new VPS, then uncomment + # duplicity: + # name: + # apt: duplicity + # pass: + # name: + # apt: pass + # sonicpi: + # name: + # apt: sonic-pi-server + # sonicpi-docs: + # name: + # apt: sonic-pi-server-doc + # supercollider: + # name: + # apt: supercollider + # supercollider-docs: + # name: + # apt: supercollider-common + # supercollider-plugins: + # name: + # apt: sc3-plugins-language + qrencode: + name: + apt: qrencode + ffmpeg: + name: + apt: ffmpeg + ffmpeg-docs: + name: + apt: ffmpeg-doc + graphicsmagick: + name: + apt: graphicsmagick + graphicsmagick-compatibility: + name: + apt: graphicsmagick-imagemagick-compat + timg: + name: + apt: timg + tmux: + name: + apt: tmux + # @TODO add glow apt repository in install@linux bootstrap role play before uncommenting the below + # glow: + # name: + # apt: glow + # @TODO add ZFS apt repository in install@linux bootstrap role play before uncommenting the below + # zfs: + # name: + # apt: zfsutils-linux + # @TODO manually install the commented below on current active new VPS, then uncomment + # dpkg-dev: + # name: + # apt: dpkg-dev + # ldap-utils: + # name: + # apt: ldap-utils + # slapd: + # name: + # apt: slapd + # proftpd: + # name: + # apt: proftpd + # rsync: + # name: + # apt: rsync + # rclone: + # name: + # apt: rsync + # aria: + # name: + # apt: aria2 + # mopidy: + # name: + # apt: mopidy + # mopidy-mpd: + # name: + # apt: mopidy-mpd + # caddy: + # name: + # apt: caddy +config: + git: + sys: + editor: nvim +