diff --git a/roles/init-server/README.md b/roles/init-server/README.md new file mode 100644 index 0000000..225dd44 --- /dev/null +++ b/roles/init-server/README.md @@ -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). diff --git a/roles/init-server/defaults/main/conn_prefs.yml b/roles/init-server/defaults/main/conn_prefs.yml new file mode 100644 index 0000000..0c049fb --- /dev/null +++ b/roles/init-server/defaults/main/conn_prefs.yml @@ -0,0 +1,4 @@ +#SPDX-License-Identifier: MIT-0 +--- +# defaults file for roles/init-vps +ip_pref: ipv4 # > represents internet protocol in use \ No newline at end of file diff --git a/roles/init-server/defaults/main/security.yml b/roles/init-server/defaults/main/security.yml new file mode 100644 index 0000000..47f3670 --- /dev/null +++ b/roles/init-server/defaults/main/security.yml @@ -0,0 +1,2 @@ +# > whether to harden managed node's SSH service +harden: true \ No newline at end of file diff --git a/roles/init-server/defaults/main/users@linux.yml b/roles/init-server/defaults/main/users@linux.yml new file mode 100644 index 0000000..7cdb3fa --- /dev/null +++ b/roles/init-server/defaults/main/users@linux.yml @@ -0,0 +1,11 @@ +#SPDX-License-Identifier: MIT-0 +--- +# defaults file for roles/init-vps +# > list of administrative users (in Linux, users that can use "sudo") +admins: + - username: "" # arbitrary valid user name + services: [] # ]> if linux system user, assocated servce + keys: [] # ]> list of control node or local SSH key basenames for this user + password: "" # > hashed (and maybe salted) password +# ]> list of system users, typically for use for particular services +sys_users: [] \ No newline at end of file diff --git a/roles/init-server/files/sshd_config.d/auth.conf b/roles/init-server/files/sshd_config.d/auth.conf new file mode 100644 index 0000000..31cebf5 --- /dev/null +++ b/roles/init-server/files/sshd_config.d/auth.conf @@ -0,0 +1,7 @@ +PermitEmptyPasswords no +PasswordAuthentication no +PubkeyAuthentication yes +KbdInteractiveAuthentication no # enable if implementing TOTP 2FA +UsePAM yes +PrintMotd yes +Banner /etc/banner \ No newline at end of file diff --git a/roles/init-server/files/sshd_config.d/denyroot.conf b/roles/init-server/files/sshd_config.d/denyroot.conf new file mode 100644 index 0000000..4888ff9 --- /dev/null +++ b/roles/init-server/files/sshd_config.d/denyroot.conf @@ -0,0 +1 @@ +PermitRootLogin no \ No newline at end of file diff --git a/roles/init-server/files/sshd_config.d/harden.conf b/roles/init-server/files/sshd_config.d/harden.conf new file mode 100644 index 0000000..d56b46b --- /dev/null +++ b/roles/init-server/files/sshd_config.d/harden.conf @@ -0,0 +1,2 @@ +ClientAliveInterval 900 +ClientAliveCountMax 3 \ No newline at end of file diff --git a/roles/init-server/handlers/main.yml b/roles/init-server/handlers/main.yml new file mode 100644 index 0000000..9118abf --- /dev/null +++ b/roles/init-server/handlers/main.yml @@ -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 diff --git a/roles/init-server/meta/main.yml b/roles/init-server/meta/main.yml new file mode 100644 index 0000000..6f91fd3 --- /dev/null +++ b/roles/init-server/meta/main.yml @@ -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. diff --git a/roles/init-server/tasks/install-pkgs.yml b/roles/init-server/tasks/install-pkgs.yml new file mode 100644 index 0000000..f973d7f --- /dev/null +++ b/roles/init-server/tasks/install-pkgs.yml @@ -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 \ No newline at end of file diff --git a/roles/init-server/tasks/lock.yml b/roles/init-server/tasks/lock.yml new file mode 100644 index 0000000..814c992 --- /dev/null +++ b/roles/init-server/tasks/lock.yml @@ -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 \ No newline at end of file diff --git a/roles/init-server/tasks/main.yml b/roles/init-server/tasks/main.yml new file mode 100644 index 0000000..250459c --- /dev/null +++ b/roles/init-server/tasks/main.yml @@ -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 \ No newline at end of file diff --git a/roles/init-server/templates/sshd_config.d/allowance.conf.j2 b/roles/init-server/templates/sshd_config.d/allowance.conf.j2 new file mode 100644 index 0000000..bd52d4c --- /dev/null +++ b/roles/init-server/templates/sshd_config.d/allowance.conf.j2 @@ -0,0 +1 @@ +AllowGroups {{ remote_group.name }} \ No newline at end of file diff --git a/roles/init-server/templates/sshd_config.d/sftp.conf.j2 b/roles/init-server/templates/sshd_config.d/sftp.conf.j2 new file mode 100644 index 0000000..c0e47e2 --- /dev/null +++ b/roles/init-server/templates/sshd_config.d/sftp.conf.j2 @@ -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 %} \ No newline at end of file diff --git a/roles/init-server/tests/inventory b/roles/init-server/tests/inventory new file mode 100644 index 0000000..03ca42f --- /dev/null +++ b/roles/init-server/tests/inventory @@ -0,0 +1,3 @@ +#SPDX-License-Identifier: MIT-0 +localhost + diff --git a/roles/init-server/tests/test.yml b/roles/init-server/tests/test.yml new file mode 100644 index 0000000..6f9f6fd --- /dev/null +++ b/roles/init-server/tests/test.yml @@ -0,0 +1,6 @@ +#SPDX-License-Identifier: MIT-0 +--- +- hosts: localhost + remote_user: root + roles: + - roles/init-vps