refactor: restructured project for higher-utility naming practices and optimized data structures for variables

This commit is contained in:
2025-11-11 00:58:10 -05:00
parent 44a292f19f
commit 0efe13e76b
53 changed files with 1151 additions and 874 deletions

View File

@@ -1,5 +0,0 @@
---
collections:
- name: community.general
version: "11.2.1"
source: "https://galaxy.ansible.com"

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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
}

View File

@@ -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

View File

@@ -1,2 +0,0 @@
PasswordAuthentication no
PermitEmptyPasswords no

View File

@@ -1 +0,0 @@
PermitRootLogin no

View File

@@ -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"

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"

71
.gitignore vendored
View File

@@ -1,65 +1,10 @@
/.env/ .env/
/.tmp/
rika/
senpai/
.galaxy_cache/
/galaxy_token
.ansible/log.txt
.ansible/facts/
.ansible/collections/ansible_collections/
*.bak *.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.yml
hosts.yaml .secrets/*
hosts.json **/*.key
vault.yml **/*.gpg
**/*.asc
**/*.pem
**/*.ppk
log.txt

View File

@@ -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" }}'``. # (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 # (boolean) A boolean to enable or disable scanning the sys.path for installed collections
;collections_scan_sys_path=True ;collections_scan_sys_path=True
@@ -223,7 +223,7 @@ netconf_plugins=plugins/netconf:.ansible/netconf:/usr/share/ansible/plugins/netc
;remote_user= ;remote_user=
# (pathspec) Colon separated paths in which Ansible will search for Roles. # (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. # (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. # 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 ;old_plugin_cache_clear=False
# (path) A number of non-playbook CLIs have a ``--playbook-dir`` argument; this sets the default value for it. # (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 # (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 ;playbook_vars_root=top

37
group_vars/all Normal file
View File

@@ -0,0 +1,37 @@
#SPDX-License-Identifier: MIT-0
---
# vars file
custom_vars: ~
fqdn: ~
vps_service:
# @DOC <bool>
exists: true
# @DOC <vault<str>>
password: ~
# @DOC <vault<str>>
api_key: ~
# @DOC <str>
type: "linode"
# @DOC <str>
region: "us-east"
# @DOC <list<str>>
ssh_authorized_keys: []
# @DOC <str>
root_fate: disposal
# @DOC <list<str>>
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:

129
host_vars/vps1 Normal file
View File

@@ -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

View File

@@ -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

19
playbooks/deroot.yml Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 }}"

View File

@@ -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: ~

View File

@@ -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 }}"

View File

@@ -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: ~

20
playbooks/init.yml Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 }}"

View File

@@ -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
<type>[optional scope]: <description{<50char}>
[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)]

View File

@@ -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

View File

@@ -0,0 +1,3 @@
# SPDX-License-Identifier: MIT-0
---
# handlers file for bootstrap

View File

@@ -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 }}"

View File

@@ -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

View File

@@ -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 }}"

View File

@@ -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

View File

@@ -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) }}"

View File

@@ -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"

View File

@@ -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) }}"

View File

@@ -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"

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -0,0 +1,5 @@
{% if root_login_allowed %}
PermitRootLogin yes
{% else %}
PermitRootLogin no
{% endif %}

View File

@@ -0,0 +1,2 @@
ClientAliveInterval {{ client_subsistence }}
ClientAliveCountMax {{ client_subsist_warn_max }}

View File

@@ -3,4 +3,4 @@
- hosts: localhost - hosts: localhost
remote_user: root remote_user: root
roles: roles:
- lockdown - bootstrap

View File

@@ -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