created role for bootstrapping a Linode VPS via API or a LAN homeserver
This commit is contained in:
38
roles/init-server/README.md
Normal file
38
roles/init-server/README.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
Role Name
|
||||||
|
=========
|
||||||
|
|
||||||
|
A brief description of the role goes here.
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
------------
|
||||||
|
|
||||||
|
Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required.
|
||||||
|
|
||||||
|
Role Variables
|
||||||
|
--------------
|
||||||
|
|
||||||
|
A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well.
|
||||||
|
|
||||||
|
Dependencies
|
||||||
|
------------
|
||||||
|
|
||||||
|
A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles.
|
||||||
|
|
||||||
|
Example Playbook
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too:
|
||||||
|
|
||||||
|
- hosts: servers
|
||||||
|
roles:
|
||||||
|
- { role: username.rolename, x: 42 }
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
BSD
|
||||||
|
|
||||||
|
Author Information
|
||||||
|
------------------
|
||||||
|
|
||||||
|
An optional section for the role authors to include contact information, or a website (HTML is not allowed).
|
||||||
4
roles/init-server/defaults/main/conn_prefs.yml
Normal file
4
roles/init-server/defaults/main/conn_prefs.yml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#SPDX-License-Identifier: MIT-0
|
||||||
|
---
|
||||||
|
# defaults file for roles/init-vps
|
||||||
|
ip_pref: ipv4 # <str<enum>> represents internet protocol in use
|
||||||
2
roles/init-server/defaults/main/security.yml
Normal file
2
roles/init-server/defaults/main/security.yml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# <str<bool>> whether to harden managed node's SSH service
|
||||||
|
harden: true
|
||||||
11
roles/init-server/defaults/main/users@linux.yml
Normal file
11
roles/init-server/defaults/main/users@linux.yml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#SPDX-License-Identifier: MIT-0
|
||||||
|
---
|
||||||
|
# defaults file for roles/init-vps
|
||||||
|
# <list<dict>> list of administrative users (in Linux, users that can use "sudo")
|
||||||
|
admins:
|
||||||
|
- username: "" # <str> arbitrary valid user name
|
||||||
|
services: [] # <list[<str>]> if linux system user, assocated servce
|
||||||
|
keys: [] # <list[<str>]> list of control node or local SSH key basenames for this user
|
||||||
|
password: "" # <str<vault?>> hashed (and maybe salted) password
|
||||||
|
# <list[<dict>]> list of system users, typically for use for particular services
|
||||||
|
sys_users: []
|
||||||
7
roles/init-server/files/sshd_config.d/auth.conf
Normal file
7
roles/init-server/files/sshd_config.d/auth.conf
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
PermitEmptyPasswords no
|
||||||
|
PasswordAuthentication no
|
||||||
|
PubkeyAuthentication yes
|
||||||
|
KbdInteractiveAuthentication no # enable if implementing TOTP 2FA
|
||||||
|
UsePAM yes
|
||||||
|
PrintMotd yes
|
||||||
|
Banner /etc/banner
|
||||||
1
roles/init-server/files/sshd_config.d/denyroot.conf
Normal file
1
roles/init-server/files/sshd_config.d/denyroot.conf
Normal file
@@ -0,0 +1 @@
|
|||||||
|
PermitRootLogin no
|
||||||
2
roles/init-server/files/sshd_config.d/harden.conf
Normal file
2
roles/init-server/files/sshd_config.d/harden.conf
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ClientAliveInterval 900
|
||||||
|
ClientAliveCountMax 3
|
||||||
54
roles/init-server/handlers/main.yml
Normal file
54
roles/init-server/handlers/main.yml
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# SPDX-License-Identifier: MIT-0
|
||||||
|
---
|
||||||
|
# handlers file for roles/init-vps
|
||||||
|
- name: Executing relevant files for software installation from git repository
|
||||||
|
block:
|
||||||
|
- name: Finalizing quartz installation
|
||||||
|
listen: quartz
|
||||||
|
block:
|
||||||
|
- name: Installing NodeJS dependencies of quartz software
|
||||||
|
community.general.npm:
|
||||||
|
executable: "{{ ansible_facts['user_dir'] }}/.nvm/versions/node/v24.11.1/lib/node_modules/npm"
|
||||||
|
path: "{{ ansible_facts['user_dir'] }}/repos/.foreign/quartz"
|
||||||
|
state: latest
|
||||||
|
- name: Configuring quartz software
|
||||||
|
block:
|
||||||
|
- name: Initializing quartz website
|
||||||
|
ansible.builtin.command:
|
||||||
|
chdir: "{{ ansible_facts['user_dir'] }}/repos/.foreign/quartz"
|
||||||
|
cmd: npx quartz create
|
||||||
|
register: stdout
|
||||||
|
changed_when: stdout.rc == 0
|
||||||
|
- name: Installing quartz plugins referenced in website template
|
||||||
|
ansible.builtin.command:
|
||||||
|
chdir: "{{ ansible_facts['user_dir'] }}/repos/.foreign/quartz"
|
||||||
|
cmd: npx quartz plugin install --from-config
|
||||||
|
register: stdout
|
||||||
|
changed_when: stdout.rc == 0
|
||||||
|
# - name: Starting quartz site web server
|
||||||
|
# ansible.builtin.command:
|
||||||
|
# chdir: "{{ ansible_facts['user_dir'] }}/repos/.foreign/quartz"
|
||||||
|
# cmd: npx quartz build --serve
|
||||||
|
# register: stdout
|
||||||
|
# changed_when: stdout
|
||||||
|
- name: Committing requisite actions for building software from source archives
|
||||||
|
block:
|
||||||
|
- name: Finalizing building of Surge
|
||||||
|
listen: surge
|
||||||
|
block:
|
||||||
|
- name: Hardlinking Surge executable
|
||||||
|
ansible.builtin.file:
|
||||||
|
src: "{{ ansible_facts['user_dir'] }}/downloads/archives/released/surge/surge"
|
||||||
|
dest: "{{ ansible_facts['user_dir'] }}/.local/bin/surge"
|
||||||
|
state: hard
|
||||||
|
mode: "755"
|
||||||
|
- name: Copying Surge executable
|
||||||
|
become: true
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: "{{ ansible_facts['user_dir'] }}/downloads/archives/released/surge/surge"
|
||||||
|
dest: /usr/bin/surge
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "755"
|
||||||
|
force: true
|
||||||
|
backup: false
|
||||||
35
roles/init-server/meta/main.yml
Normal file
35
roles/init-server/meta/main.yml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#SPDX-License-Identifier: MIT-0
|
||||||
|
galaxy_info:
|
||||||
|
author: your name
|
||||||
|
description: your role description
|
||||||
|
company: your company (optional)
|
||||||
|
|
||||||
|
# If the issue tracker for your role is not on github, uncomment the
|
||||||
|
# next line and provide a value
|
||||||
|
# issue_tracker_url: http://example.com/issue/tracker
|
||||||
|
|
||||||
|
# Choose a valid license ID from https://spdx.org - some suggested licenses:
|
||||||
|
# - BSD-3-Clause (default)
|
||||||
|
# - MIT
|
||||||
|
# - GPL-2.0-or-later
|
||||||
|
# - GPL-3.0-only
|
||||||
|
# - Apache-2.0
|
||||||
|
# - CC-BY-4.0
|
||||||
|
license: license (GPL-2.0-or-later, MIT, etc)
|
||||||
|
|
||||||
|
min_ansible_version: 2.2
|
||||||
|
|
||||||
|
# If this a Container Enabled role, provide the minimum Ansible Container version.
|
||||||
|
# min_ansible_container_version:
|
||||||
|
|
||||||
|
galaxy_tags: []
|
||||||
|
# List tags for your role here, one per line. A tag is a keyword that describes
|
||||||
|
# and categorizes the role. Users find roles by searching for tags. Be sure to
|
||||||
|
# remove the '[]' above, if you add tags to this list.
|
||||||
|
#
|
||||||
|
# NOTE: A tag is limited to a single word comprised of alphanumeric characters.
|
||||||
|
# Maximum 20 tags per role.
|
||||||
|
|
||||||
|
dependencies: []
|
||||||
|
# List your role dependencies here, one per line. Be sure to remove the '[]' above,
|
||||||
|
# if you add dependencies to this list.
|
||||||
125
roles/init-server/tasks/install-pkgs.yml
Normal file
125
roles/init-server/tasks/install-pkgs.yml
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
#SPDX-License-Identifier: MIT-0
|
||||||
|
---
|
||||||
|
# tasks file for roles/init-vps
|
||||||
|
- name: Creating prerequisite directory tree
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ ansible_facts['user_dir'] }}/.local/bin"
|
||||||
|
recurse: true
|
||||||
|
state: directory
|
||||||
|
- name: Creating prerequisite directory tree
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ ansible_facts['user_dir'] }}/downloads/archives/released"
|
||||||
|
recurse: true
|
||||||
|
state: directory
|
||||||
|
- name: Installing Linux software
|
||||||
|
when: ansible_facts["system"] == "Linux"
|
||||||
|
block:
|
||||||
|
- name: Installing software using Debian package manager
|
||||||
|
when: ansible_facts["os_family"] == "Debian"
|
||||||
|
become: true
|
||||||
|
block:
|
||||||
|
- name: Registering a package signing key
|
||||||
|
when: item.key is defined and item.key_path is defined
|
||||||
|
ansible.builtin.get_url:
|
||||||
|
url: "{{ item.key }}"
|
||||||
|
dest: "{{ item.key_path | default('/etc/apt/keyrings/') }}"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "644"
|
||||||
|
force: true
|
||||||
|
backup: true
|
||||||
|
loop: "{{ pkgs.mngr.core + pkgs.mngr.userspace | rejectattr('key', 'search', '\\.deb$') }}"
|
||||||
|
- name: Installing a package signing key
|
||||||
|
when: item.key is defined
|
||||||
|
ansible.builtin.apt:
|
||||||
|
deb: "{{ item.key }}"
|
||||||
|
state: present
|
||||||
|
loop: "{{ pkgs.mngr.core + pkgs.mngr.userspace | selectattr('key', 'search', '\\.deb$') }}"
|
||||||
|
- name: Registering a package source
|
||||||
|
when: item.src_entry is defined and item.src_path is defined
|
||||||
|
ansible.builtin.copy:
|
||||||
|
content: "{{ item.src_entry }}"
|
||||||
|
dest: "{{ item.src_path }}"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "644"
|
||||||
|
force: true
|
||||||
|
backup: true
|
||||||
|
loop: "{{ pkgs.mngr.core + pkgs.mngr.userspace }}"
|
||||||
|
- name: Installing a local package in managed node
|
||||||
|
when: item.uri is defined
|
||||||
|
ansible.builtin.apt:
|
||||||
|
deb: "{{ item.uri }}"
|
||||||
|
update_cache: true
|
||||||
|
state: present
|
||||||
|
notify: "{{ item.name }}"
|
||||||
|
loop: "{{ pkgs.mngr.core + pkgs.mngr.userspace | selectattr('uri', 'search', '\\.deb$') }}"
|
||||||
|
- name: Installing a package
|
||||||
|
when: item.name is defined and item.uri is undefined
|
||||||
|
ansible.builtin.package:
|
||||||
|
name: "{{ item.name }}"
|
||||||
|
update_cache: true
|
||||||
|
state: latest
|
||||||
|
notify: "{{ item.name }}" # @TODO create corresponding roles/init-vps handlers
|
||||||
|
loop: "{{ pkgs.mngr.core + pkgs.mngr.userspace | rejectattr('uri', 'search', '\\.deb$') }}"
|
||||||
|
tags:
|
||||||
|
- get_mngr_pkgs
|
||||||
|
- name: Installing software by executing installation shell scripts
|
||||||
|
when: item.src is defined
|
||||||
|
block:
|
||||||
|
- name: Acquiring installation shell script
|
||||||
|
ansible.builtin.get_url:
|
||||||
|
url: "{{ item.src }}"
|
||||||
|
dest: "{{ ansible_facts['user_dir'] }}/.local/bin/{{ item.name }}-install.sh"
|
||||||
|
force: true
|
||||||
|
backup: true
|
||||||
|
mode: "744"
|
||||||
|
loop: "{{ pkgs.script.core + pkgs.script.userspace }}"
|
||||||
|
register: install_scripts
|
||||||
|
- name: Executing a shell-scripted installation process
|
||||||
|
become: true
|
||||||
|
ansible.builtin.shell:
|
||||||
|
cmd: "{{ item.dest }}"
|
||||||
|
notify: "{{ (pkgs.script.core + pkgs.script.userspace)[idx].name }}"
|
||||||
|
loop: "{{ install_scripts.results }}"
|
||||||
|
loop_control:
|
||||||
|
index_var: idx
|
||||||
|
tags:
|
||||||
|
- get_script_pkgs
|
||||||
|
# @TODO complete below block task
|
||||||
|
- name: Installing software by building it from source archives
|
||||||
|
block:
|
||||||
|
- name: Acquiring software source archive
|
||||||
|
ansible.builtin.get_url:
|
||||||
|
url: "{{ item.src }}"
|
||||||
|
dest: "{{ ansible_facts['user_dir'] }}/downloads/archives/"
|
||||||
|
force: true
|
||||||
|
backup: true
|
||||||
|
mode: "644"
|
||||||
|
loop: "{{ pkgs.archive.core + pkgs.archive.userspace }}"
|
||||||
|
register: archived_builds
|
||||||
|
- name: Unarchiving software build archive
|
||||||
|
ansible.builtin.unarchive:
|
||||||
|
src: "{{ item.dest }}"
|
||||||
|
remote_src: true
|
||||||
|
dest: "{{ ansible_facts['user_dir'] }}/downloads/archives/released/{{ (pkgs.archive.core + pkgs.archive.userspace)[idx].name }}/"
|
||||||
|
notify: "{{ (pkgs.archive.core + pkgs.archive.userspace)[idx].name }}"
|
||||||
|
loop: "{{ archived_builds.results }}"
|
||||||
|
loop_control:
|
||||||
|
index_var: idx
|
||||||
|
tags:
|
||||||
|
- get_archive_pkgs
|
||||||
|
- name: Installing software from source git repositories
|
||||||
|
block:
|
||||||
|
- name: Clone git bare repository
|
||||||
|
ansible.builtin.git:
|
||||||
|
repo: "{{ item.src }}"
|
||||||
|
dest: "{{ ansible_facts['user_dir'] }}/repos/.foreign/{{ item.name }}"
|
||||||
|
version: "{{ item.branch }}"
|
||||||
|
clone: true
|
||||||
|
single_branch: true
|
||||||
|
notify: "{{ item.name }}"
|
||||||
|
loop: "{{ pkgs.git_repos.core + pkgs.git_repos.userspace }}"
|
||||||
|
register: installation_repos
|
||||||
|
tags:
|
||||||
|
- get_git_pkgs
|
||||||
41
roles/init-server/tasks/lock.yml
Normal file
41
roles/init-server/tasks/lock.yml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#SPDX-License-Identifier: MIT-0
|
||||||
|
---
|
||||||
|
# tasks file for roles/init-vps
|
||||||
|
# @TODO complete below tasks
|
||||||
|
- name: Checking whether administrative login used
|
||||||
|
when: ansible_facts["user_id"] not in (admins | map(attribute="username") | list)
|
||||||
|
ansible.builtin.fail:
|
||||||
|
msg: Administrative user does not exist on managed node
|
||||||
|
- name: Prohibiting SSH root login
|
||||||
|
when: harden
|
||||||
|
become: true
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: sshd_config.d/denyroot.conf
|
||||||
|
dest: /etc/ssh/sshd_config.d/denyroot.conf
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "644"
|
||||||
|
force: true
|
||||||
|
backup: true
|
||||||
|
validate: "sshd -t %s"
|
||||||
|
- name: Create groups for FTP services
|
||||||
|
when: "'internal-sftp' in item.service or 'proftpd' in item.service or 'vsftpd' in item.service"
|
||||||
|
become: true
|
||||||
|
ansible.builtin.group:
|
||||||
|
name: "{{ item.username }}"
|
||||||
|
system: true
|
||||||
|
state: present
|
||||||
|
loop: "{{ sys_users }}"
|
||||||
|
register: ftp_groups
|
||||||
|
- name: Configuring SFTP for FTP group
|
||||||
|
become: true
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: sshd_config.d/sftp.conf.j2
|
||||||
|
dest: /etc/ssh/sshd_config.d/sftp.conf
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "644"
|
||||||
|
force: true
|
||||||
|
backup: true
|
||||||
|
validate: "sshd -t %s"
|
||||||
|
register: configured_sftp
|
||||||
443
roles/init-server/tasks/main.yml
Normal file
443
roles/init-server/tasks/main.yml
Normal file
@@ -0,0 +1,443 @@
|
|||||||
|
#SPDX-License-Identifier: MIT-0
|
||||||
|
---
|
||||||
|
# tasks file for roles/init-vps
|
||||||
|
# @NOTE server deployment method is based on task tags compiled herein
|
||||||
|
# @TODO review 'loop' task attribute return values and make compliant changes
|
||||||
|
- name: Finding SSH public keys for root
|
||||||
|
ansible.builtin.find:
|
||||||
|
paths: "{{ cnode_homedir | default('/home/' ~ ansible_user ~ '/.ssh') }}" # @TODO define 'cnode_homedir' in playbook
|
||||||
|
patterns: "{{ ['^'] | product(keys) | map('join') | list }}"
|
||||||
|
file_type: file
|
||||||
|
use_regex: true
|
||||||
|
register: ssh_keypairs
|
||||||
|
- name: Bootstrapping VPS
|
||||||
|
block:
|
||||||
|
- name: Creating VPS via Linode VPS service API
|
||||||
|
block:
|
||||||
|
- name: Creating the VPS
|
||||||
|
linode.cloud.instance:
|
||||||
|
api_token: "{{ token }}"
|
||||||
|
label: "{{ instance }}"
|
||||||
|
type: g6-standard-2
|
||||||
|
image: "{{ operating_system }}"
|
||||||
|
disk_encryption: enabled
|
||||||
|
region: "{{ origin }}"
|
||||||
|
private_ip: true
|
||||||
|
root_pass: "{{ password }}"
|
||||||
|
authorized_keys: "{{ ssh_keypairs.files | selectattr('path', 'search', '\\.pub$') | map(attribute='path') | map('lookup', 'file') | list }}"
|
||||||
|
state: present
|
||||||
|
register: new_instance
|
||||||
|
- name: Waiting for that VPS to come online
|
||||||
|
delegate_to: "{{ new_instance.instance[ip_pref][0] }}"
|
||||||
|
delegate_facts: true
|
||||||
|
ansible.builtin.wait_for_connection:
|
||||||
|
delay: 20
|
||||||
|
timeout: 300
|
||||||
|
vars:
|
||||||
|
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
|
||||||
|
ansible_user: root
|
||||||
|
- name: Checking if that VPS has required operating system
|
||||||
|
delegate_to: "{{ new_instance.instance[ip_pref][0] }}"
|
||||||
|
delegate_facts: true
|
||||||
|
when: ansible_facts["system"] != "Linux"
|
||||||
|
ansible.builtin.fail:
|
||||||
|
msg: Unsupported operating system found
|
||||||
|
vars:
|
||||||
|
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
|
||||||
|
ansible_user: root
|
||||||
|
- name: Checking if that VPS has required Linux distro
|
||||||
|
delegate_to: "{{ new_instance.instance[ip_pref][0] }}"
|
||||||
|
delegate_facts: true
|
||||||
|
when: ansible_facts["system"] == "Linux" and ansible_facts["os_family"] != "Debian"
|
||||||
|
ansible.builtin.fail:
|
||||||
|
msg: Unsupported Linux distro found
|
||||||
|
vars:
|
||||||
|
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
|
||||||
|
ansible_user: root
|
||||||
|
tags:
|
||||||
|
- linode
|
||||||
|
tags:
|
||||||
|
- vps
|
||||||
|
- name: Bootstrapping homeserver
|
||||||
|
block:
|
||||||
|
- name: Installing operating system or distro in server
|
||||||
|
when: operating_system is defined
|
||||||
|
block:
|
||||||
|
- name: Creating a server
|
||||||
|
block: []
|
||||||
|
tags:
|
||||||
|
- unimplemented
|
||||||
|
- name: Waiting for that VPS to come online
|
||||||
|
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
|
||||||
|
delegate_facts: true
|
||||||
|
ansible.builtin.wait_for_connection:
|
||||||
|
delay: 20
|
||||||
|
timeout: 300
|
||||||
|
vars:
|
||||||
|
ansible_user: root
|
||||||
|
- name: Checking if that server has required operating system
|
||||||
|
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
|
||||||
|
delegate_facts: true
|
||||||
|
when: ansible_facts["system"] != "Linux"
|
||||||
|
ansible.builtin.fail:
|
||||||
|
msg: Unsupported operating system found
|
||||||
|
vars:
|
||||||
|
ansible_user: root
|
||||||
|
- name: Checking if that server has required Linux distro
|
||||||
|
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
|
||||||
|
delegate_facts: true
|
||||||
|
when: ansible_facts["system"] == "Linux" and ansible_facts["os_family"] != "Debian"
|
||||||
|
ansible.builtin.fail:
|
||||||
|
msg: Unsupported Linux distro found
|
||||||
|
vars:
|
||||||
|
ansible_user: root
|
||||||
|
- name: Providing authorized keys for server root account
|
||||||
|
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
|
||||||
|
delegate_facts: true
|
||||||
|
ansible.posix.authorized_key:
|
||||||
|
user: "{{ ansible_facts['user_id'] }}"
|
||||||
|
key: "{{ lookup('file', item) }}"
|
||||||
|
state: present
|
||||||
|
vars:
|
||||||
|
ansible_root: root
|
||||||
|
loop: "{{ ssh_keypairs.files | selectattr('path', 'search', '\\.pub$') | map(attribute='path') | list }}"
|
||||||
|
tags:
|
||||||
|
- lan
|
||||||
|
- name: Starting SSH hardening
|
||||||
|
when: harden
|
||||||
|
delegate_facts: true
|
||||||
|
block:
|
||||||
|
- name: Hardening SSH service for the Linode VPS
|
||||||
|
delegate_to: "{{ new_instance.instance[ip_pref][0] }}"
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: sshd_config.d/harden.conf
|
||||||
|
dest: /etc/ssh/sshd_config.d/harden.conf
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "644"
|
||||||
|
force: true
|
||||||
|
backup: true
|
||||||
|
validate: "sshd -t %s"
|
||||||
|
vars:
|
||||||
|
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
|
||||||
|
ansible_user: root
|
||||||
|
register: ssh_hardened
|
||||||
|
tags:
|
||||||
|
- linode
|
||||||
|
- name: Hardening SSH service for the server
|
||||||
|
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: sshd_config.d/harden.conf
|
||||||
|
dest: /etc/ssh/sshd_config.d/harden.conf
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "644"
|
||||||
|
force: true
|
||||||
|
backup: true
|
||||||
|
validate: "sshd -t %s"
|
||||||
|
vars:
|
||||||
|
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
|
||||||
|
ansible_user: root
|
||||||
|
register: ssh_hardened
|
||||||
|
tags:
|
||||||
|
- lan
|
||||||
|
- name: Starting user and group creation for SSH access
|
||||||
|
block:
|
||||||
|
- name: Creating group remote for managing SSH access
|
||||||
|
delegate_facts: true
|
||||||
|
block:
|
||||||
|
- name: In the Linode VPS
|
||||||
|
delegate_to: "{{ new_instance.instance[ip_pref][0] }}"
|
||||||
|
ansible.builtin.group:
|
||||||
|
name: remote
|
||||||
|
system: true
|
||||||
|
state: present
|
||||||
|
vars:
|
||||||
|
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
|
||||||
|
ansible_user: root
|
||||||
|
register: remote_group
|
||||||
|
tags:
|
||||||
|
- linode
|
||||||
|
- name: In the server
|
||||||
|
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
|
||||||
|
ansible.builtin.group:
|
||||||
|
name: remote
|
||||||
|
system: true
|
||||||
|
state: present
|
||||||
|
vars:
|
||||||
|
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
|
||||||
|
ansible_user: root
|
||||||
|
register: remote_group
|
||||||
|
tags:
|
||||||
|
- lan
|
||||||
|
- name: Creating an administrative user
|
||||||
|
delegate_facts: true
|
||||||
|
when: ansible_facts["system"] == "Linux"
|
||||||
|
block:
|
||||||
|
- name: In the Linode VPS
|
||||||
|
delegate_to: "{{ new_instance.instance[ip_pref][0] }}"
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: "{{ item.username }}"
|
||||||
|
comment: administrator
|
||||||
|
group: "{{ item.username }}"
|
||||||
|
groups:
|
||||||
|
- "{{ remote_group.name }}"
|
||||||
|
- sudo # @NOTE used by Debian
|
||||||
|
append: true
|
||||||
|
generate_ssh_key: true
|
||||||
|
create_home: true
|
||||||
|
password: "{{ item.password }}"
|
||||||
|
shell: "/bin/bash"
|
||||||
|
vars:
|
||||||
|
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
|
||||||
|
ansible_user: root
|
||||||
|
loop: "{{ admins }}"
|
||||||
|
register: admin_users
|
||||||
|
tags:
|
||||||
|
- linode
|
||||||
|
- name: In the server
|
||||||
|
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: "{{ item.username }}"
|
||||||
|
comment: administrator
|
||||||
|
group: "{{ item.username }}"
|
||||||
|
groups:
|
||||||
|
- "{{ remote_group.name }}"
|
||||||
|
- sudo # @NOTE used by Debian
|
||||||
|
append: true
|
||||||
|
generate_ssh_key: true
|
||||||
|
create_home: true
|
||||||
|
password: "{{ item.password }}"
|
||||||
|
shell: "/bin/bash"
|
||||||
|
vars:
|
||||||
|
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
|
||||||
|
ansible_user: root
|
||||||
|
loop: "{{ admins }}"
|
||||||
|
register: admin_users
|
||||||
|
tags:
|
||||||
|
- lan
|
||||||
|
- name: Finding SSH public keys for an administrative user
|
||||||
|
when: item.username in (admin_users.results | map(attribute="name") | list)
|
||||||
|
ansible.builtin.find:
|
||||||
|
paths: "{{ cnode_homedir | default('/home/' ~ ansible_user ~ '/.ssh') }}" # @TODO define 'cnode_homedir' in playbook
|
||||||
|
patterns: "{{ ['^'] | product(item.keys) | map('join') | list }}"
|
||||||
|
file_type: file
|
||||||
|
use_regex: true
|
||||||
|
loop: "{{ admins }}"
|
||||||
|
register: admin_ssh_keypairs
|
||||||
|
- name: Authorizing SSH public key for an administrative user
|
||||||
|
delegate_facts: true
|
||||||
|
block:
|
||||||
|
- name: In the Linode VPS
|
||||||
|
delegate_to: "{{ new_instance.instance[ip_pref][0] }}"
|
||||||
|
ansible.posix.authorized_key:
|
||||||
|
user: "{{ admin_users.results[idx] }}"
|
||||||
|
key: "{{ admin_ssh_keypairs.results[idx].files | selectattr('path', 'search', '\\.pub$') | map(attribute='path') | map('lookup', 'file') | list | map('join','\n') }}"
|
||||||
|
state: present
|
||||||
|
vars:
|
||||||
|
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
|
||||||
|
ansible_user: root
|
||||||
|
loop: "{{ admin_users.results }}"
|
||||||
|
loop_control:
|
||||||
|
index_var: idx
|
||||||
|
register: ssh_authorizations
|
||||||
|
tags:
|
||||||
|
- linode
|
||||||
|
- name: In the server
|
||||||
|
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
|
||||||
|
ansible.posix.authorized_key:
|
||||||
|
user: "{{ admin_users.results[idx] }}"
|
||||||
|
key: "{{ admin_ssh_keypairs.results[idx].files | selectattr('path', 'search', '\\.pub$') | map(attribute='path') | map('lookup', 'file') | list | map('join','\n') }}"
|
||||||
|
state: present
|
||||||
|
vars:
|
||||||
|
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
|
||||||
|
ansible_user: root
|
||||||
|
loop: "{{ admin_users.results }}"
|
||||||
|
loop_control:
|
||||||
|
index_var: idx
|
||||||
|
register: ssh_authorizations
|
||||||
|
tags:
|
||||||
|
- lan
|
||||||
|
- name: Allowing sole SSH access to users in group remote
|
||||||
|
delegate_facts: true
|
||||||
|
block:
|
||||||
|
- name: In Linode VPS
|
||||||
|
delegate_to: "{{ new_instance.instance[ip_pref][0] }}"
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: sshd_config.d/allowance.conf.j2 # @TODO create corresponding role template file
|
||||||
|
dest: /etc/ssh/sshd_config.d/allowance.conf
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "644"
|
||||||
|
force: true
|
||||||
|
backup: true
|
||||||
|
validate: "sshd -t %s"
|
||||||
|
vars:
|
||||||
|
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
|
||||||
|
ansible_user: root
|
||||||
|
register: ssh_gatekept
|
||||||
|
tags:
|
||||||
|
- linode
|
||||||
|
- name: In Linode VPS
|
||||||
|
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: sshd_config.d/allowance.conf.j2 # @TODO create corresponding role template file
|
||||||
|
dest: /etc/ssh/sshd_config.d/allowance.conf
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "644"
|
||||||
|
force: true
|
||||||
|
backup: true
|
||||||
|
validate: "sshd -t %s"
|
||||||
|
vars:
|
||||||
|
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
|
||||||
|
ansible_user: root
|
||||||
|
register: ssh_gatekept
|
||||||
|
tags:
|
||||||
|
- lan
|
||||||
|
- name: Setting approved SSH authentication procedures
|
||||||
|
when: harden
|
||||||
|
delegate_facts: true
|
||||||
|
block:
|
||||||
|
- name: In the Linode VPS
|
||||||
|
delegate_to: "{{ new_instance.instance[ip_pref][0] }}"
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: sshd_config.d/auth.conf
|
||||||
|
dest: /etc/ssh/sshd_config.d/auth.conf
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "644"
|
||||||
|
force: true
|
||||||
|
backup: true
|
||||||
|
validate: "sshd -t %s"
|
||||||
|
vars:
|
||||||
|
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
|
||||||
|
ansible_user: root
|
||||||
|
register: ssh_authenticator
|
||||||
|
tags:
|
||||||
|
- linode
|
||||||
|
- name: In the server
|
||||||
|
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: sshd_config.d/auth.conf
|
||||||
|
dest: /etc/ssh/sshd_config.d/auth.conf
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "644"
|
||||||
|
force: true
|
||||||
|
backup: true
|
||||||
|
validate: "sshd -t %s"
|
||||||
|
vars:
|
||||||
|
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
|
||||||
|
ansible_user: root
|
||||||
|
register: ssh_authenticator
|
||||||
|
tags:
|
||||||
|
- lan
|
||||||
|
- name: Installing core packages
|
||||||
|
delegate_facts: true
|
||||||
|
block:
|
||||||
|
- name: In the Linode VPS
|
||||||
|
delegate_to: "{{ new_instance.instance[ip_pref][0] }}"
|
||||||
|
block:
|
||||||
|
- name: Registering a package signing key
|
||||||
|
when: ansible_facts["os_family"] == "Debian" and item.key is defined and item.key_path is defined
|
||||||
|
ansible.builtin.get_url:
|
||||||
|
url: "{{ item.key }}"
|
||||||
|
dest: "{{ item.key_path | default('/etc/apt/keyrings/') }}"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "644"
|
||||||
|
force: true
|
||||||
|
backup: true
|
||||||
|
loop: "{{ pkgs.mngr.core | rejectattr('key', 'search', '\\.deb$') }}"
|
||||||
|
- name: Installing a package signing key
|
||||||
|
when: ansible_facts["os_family"] == "Debian" and item.key is defined
|
||||||
|
ansible.builtin.apt:
|
||||||
|
deb: "{{ item.key }}"
|
||||||
|
state: latest
|
||||||
|
loop: "{{ pkgs.mngr.core | selectattr('key', 'search', '\\.deb$') }}"
|
||||||
|
- name: Registering a package source
|
||||||
|
when: ansible_facts["os_family"] == "Debian" and item.src_entry is defined and item.src_path is defined
|
||||||
|
ansible.builtin.copy:
|
||||||
|
content: "{{ item.src_entry }}"
|
||||||
|
dest: "{{ item.src_path }}"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "644"
|
||||||
|
force: true
|
||||||
|
backup: true
|
||||||
|
loop: "{{ pkgs.mngr.core }}"
|
||||||
|
- name: Installing a local package in managed node
|
||||||
|
when: ansible_facts["os_family"] == "Debian" and item.uri is defined
|
||||||
|
ansible.builtin.apt:
|
||||||
|
deb: "{{ item.uri }}"
|
||||||
|
update_cache: true
|
||||||
|
state: latest
|
||||||
|
loop: "{{ pkgs.mngr.core | selectattr('uri', 'search', '\\.deb$') }}"
|
||||||
|
- name: Installing a package
|
||||||
|
when: item.name is defined and item.uri is undefined
|
||||||
|
ansible.builtin.package:
|
||||||
|
name: "{{ item.name }}"
|
||||||
|
update_cache: true
|
||||||
|
state: latest
|
||||||
|
notify: "{{ item.name }}" # @TODO create corresponding roles/init-vps handlers
|
||||||
|
loop: "{{ pkgs.mngr.core| rejectattr('uri', 'search', '\\.deb$') }}"
|
||||||
|
vars:
|
||||||
|
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
|
||||||
|
ansible_user: root
|
||||||
|
tags:
|
||||||
|
- linode
|
||||||
|
- name: In the server
|
||||||
|
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
|
||||||
|
block:
|
||||||
|
- name: Registering a package signing key
|
||||||
|
when: ansible_facts["os_family"] == "Debian" and item.key is defined and item.key_path is defined
|
||||||
|
ansible.builtin.get_url:
|
||||||
|
url: "{{ item.key }}"
|
||||||
|
dest: "{{ item.key_path | default('/etc/apt/keyrings/') }}"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "644"
|
||||||
|
force: true
|
||||||
|
backup: true
|
||||||
|
loop: "{{ pkgs.mngr.core | rejectattr('key', 'search', '\\.deb$') }}"
|
||||||
|
- name: Installing a package signing key
|
||||||
|
when: ansible_facts["os_family"] == "Debian" and item.key is defined
|
||||||
|
ansible.builtin.apt:
|
||||||
|
deb: "{{ item.key }}"
|
||||||
|
state: latest
|
||||||
|
loop: "{{ pkgs.mngr.core | selectattr('key', 'search', '\\.deb$') }}"
|
||||||
|
- name: Registering a package source
|
||||||
|
when: ansible_facts["os_family"] == "Debian" and item.src_entry is defined and item.src_path is defined
|
||||||
|
ansible.builtin.copy:
|
||||||
|
content: "{{ item.src_entry }}"
|
||||||
|
dest: "{{ item.src_path }}"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "644"
|
||||||
|
force: true
|
||||||
|
backup: true
|
||||||
|
loop: "{{ pkgs.mngr.core }}"
|
||||||
|
- name: Installing a local package in managed node
|
||||||
|
when: ansible_facts["os_family"] == "Debian" and item.uri is defined
|
||||||
|
ansible.builtin.apt:
|
||||||
|
deb: "{{ item.uri }}"
|
||||||
|
update_cache: true
|
||||||
|
state: latest
|
||||||
|
notify: "{{ item.name }}"
|
||||||
|
loop: "{{ pkgs.mngr.core | selectattr('uri', 'search', '\\.deb$') }}"
|
||||||
|
- name: Installing a package
|
||||||
|
when: item.name is defined and item.uri is undefined
|
||||||
|
ansible.builtin.package:
|
||||||
|
name: "{{ item.name }}"
|
||||||
|
update_cache: true
|
||||||
|
state: latest
|
||||||
|
notify: "{{ item.name }}" # @TODO create corresponding roles/init-vps handlers
|
||||||
|
loop: "{{ pkgs.mngr.core| rejectattr('uri', 'search', '\\.deb$') }}"
|
||||||
|
vars:
|
||||||
|
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
|
||||||
|
ansible_user: root
|
||||||
|
tags:
|
||||||
|
- lan
|
||||||
|
tags:
|
||||||
|
- get_pkgs
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
AllowGroups {{ remote_group.name }}
|
||||||
9
roles/init-server/templates/sshd_config.d/sftp.conf.j2
Normal file
9
roles/init-server/templates/sshd_config.d/sftp.conf.j2
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{% for item in ftp_groups.results %}
|
||||||
|
Match Group {{ item.name }}
|
||||||
|
ForceCommand internal-sftp -d /%u
|
||||||
|
ChrootDirectory /srv/{{ item.name}}
|
||||||
|
AllowAgentForwarding no
|
||||||
|
AllowTcpForwarding no
|
||||||
|
X11Forwarding no
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
3
roles/init-server/tests/inventory
Normal file
3
roles/init-server/tests/inventory
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#SPDX-License-Identifier: MIT-0
|
||||||
|
localhost
|
||||||
|
|
||||||
6
roles/init-server/tests/test.yml
Normal file
6
roles/init-server/tests/test.yml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#SPDX-License-Identifier: MIT-0
|
||||||
|
---
|
||||||
|
- hosts: localhost
|
||||||
|
remote_user: root
|
||||||
|
roles:
|
||||||
|
- roles/init-vps
|
||||||
Reference in New Issue
Block a user