commit b00d31c48d622c23208a28fcced37c9b6dbdb288 Author: waal70 Date: Sat Oct 26 16:23:45 2024 +0200 Initial commit diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..199cebf --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +# MIT License + +Copyright (c) 2024 André + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7ae1547 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +Common +========= + +This is the role that sets some defaults. You might say, a bootstrap role. + +The role will check the basic requirements. It will then determine the correct +course of action (mainly, "proper" Debian or Raspberry Pi debian) + +This role should be safe to run on all machines, always + +Requirements +------------ + +It requires a Debian instance, set-up by the preseed. +It will also accept a default Raspbian OS. +It also presumes a live sudo package. +Make sure at least the SSH-key for user ansible is set. + +If you have proxmox-ve servers, for which Dell firmware should NOT be installed, +be sure to have them in a group called 'proxmox_servers' + +Role Variables +-------------- + +None + +License +------- + +MIT diff --git a/handlers/main.yml b/handlers/main.yml new file mode 100644 index 0000000..e6364e9 --- /dev/null +++ b/handlers/main.yml @@ -0,0 +1,20 @@ +--- +# file: common/handlers/main.yml +- name: Update packages cache + ansible.builtin.apt: + update_cache: true + +- name: Restart systemd-journald + ansible.builtin.systemd: + name: systemd-journald + state: restarted + +- name: Restart rrdcached + ansible.builtin.systemd_service: + name: rrdcached + state: restarted + +- name: Restart sshd + ansible.builtin.systemd_service: + name: sshd + state: restarted diff --git a/meta/main.yml b/meta/main.yml new file mode 100644 index 0000000..b8e1ac7 --- /dev/null +++ b/meta/main.yml @@ -0,0 +1,21 @@ +galaxy_info: # noqa: role-name + author: waal70 (Andre) + description: Role to set my preferred details on a Debian fresh install + company: waal70 + platforms: + - name: Debian + versions: + - bookworm + + license: MIT + + min_ansible_version: "2.1" + + galaxy_tags: [debian, + bootstrap, + default + ] + +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/tasks/color-shell.yml b/tasks/color-shell.yml new file mode 100644 index 0000000..36a61d0 --- /dev/null +++ b/tasks/color-shell.yml @@ -0,0 +1,14 @@ +--- +# Note the use of "ansible_remote_tmp" - to prevent WARNINGS because of becoming an unprivileged user +- name: Force colors in .bashrc + ansible.builtin.lineinfile: + path: "{{ interactive_home }}/.bashrc" + regexp: "{{ item.regexp }}" + line: "{{ item.line }}" + state: present + with_items: + - { regexp: "^#?force_color_prompt", line: force_color_prompt=yes } + become: true + become_user: "{{ interactive_user }}" + vars: + ansible_remote_tmp: /tmp diff --git a/tasks/custom-fact.yml b/tasks/custom-fact.yml new file mode 100644 index 0000000..6f04ab3 --- /dev/null +++ b/tasks/custom-fact.yml @@ -0,0 +1,18 @@ +--- +- name: Create directory for ansible custom facts + ansible.builtin.file: + state: directory + recurse: true + path: /etc/ansible/facts.d + +- name: Install custom CPU fact + ansible.builtin.template: + src: etc/ansible/facts.d/cpu_info.fact + dest: /etc/ansible/facts.d + owner: "{{ ansible_user_id }}" + group: "{{ ansible_user_id }}" + mode: "0777" + +- name: Re-read facts after adding custom fact + ansible.builtin.setup: + filter: ansible_local diff --git a/tasks/dell-firmware.yml b/tasks/dell-firmware.yml new file mode 100644 index 0000000..59ca0a2 --- /dev/null +++ b/tasks/dell-firmware.yml @@ -0,0 +1,8 @@ +--- +- name: Install Dell firmware + ansible.builtin.apt: + pkg: + - firmware-realtek + - firmware-misc-nonfree + state: present + when: "'proxmox_servers' not in group_names" diff --git a/tasks/edit-journald.yml b/tasks/edit-journald.yml new file mode 100644 index 0000000..6a9058d --- /dev/null +++ b/tasks/edit-journald.yml @@ -0,0 +1,18 @@ +--- +- name: Edit journald.conf + ansible.builtin.lineinfile: + path: /etc/systemd/journald.conf + regexp: "{{ item.regexp }}" + line: "{{ item.line }}" + state: "{{ item.state | default('present') }}" + with_items: + - { regexp: "^#?Storage", line: Storage=volatile } + - { regexp: "^#?ForwardToSyslog", line: ForwardToSyslog=no } + - { regexp: "^#?SystemMaxUse", line: SystemMaxUse=50M } + notify: Restart systemd-journald + +# A succesful vacuum does impact log size, but is not considered a change +- name: Vacuum journalctl before + ansible.builtin.command: journalctl --vacuum-size=10M + register: vacuumresult + changed_when: false diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..fca6626 --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,37 @@ +--- +# file: common/tasks/main.yml +- name: Ensure required packages are present on systems + ansible.builtin.import_tasks: prereq-packages.yml + +- name: Make /tmp non-executable + ansible.builtin.import_tasks: tmp-nonexec.yml + +- name: Import tasks to ensure creation of unprivileged user + ansible.builtin.import_tasks: unpriv-user.yml + +- name: Import custom fact setting + ansible.builtin.import_tasks: custom-fact.yml + +- name: Import color-shell tasks for the common-role + ansible.builtin.import_tasks: color-shell.yml + +- name: Firmware block for Dell servers + when: "'Dell' in ansible_board_vendor" + block: + - name: Import firmware tasks for Dell-based servers + ansible.builtin.import_tasks: dell-firmware.yml + +- name: Import journald tasks for the common-role + ansible.builtin.import_tasks: edit-journald.yml + +- name: Import hostname tasks for the common-role + ansible.builtin.import_tasks: set-hostname.yml + +- name: Import sudoers tasks for the common-role + ansible.builtin.import_tasks: sudoers.yml + +- name: Set the custom message of the day (motd) + ansible.builtin.import_tasks: motd.yml + +- name: Perform SSH daemon hardening + ansible.builtin.import_tasks: ssh-config.yml diff --git a/tasks/motd.yml b/tasks/motd.yml new file mode 100644 index 0000000..955e4aa --- /dev/null +++ b/tasks/motd.yml @@ -0,0 +1,24 @@ +--- +- name: Prepare a custom motd, but do not affect 'proxmox_servers' + when: "'proxmox_servers' not in group_names" + block: + - name: Set a custom motd for all Debian-based systems # noqa: deprecated-bare-vars + ansible.builtin.template: + src: "{{ item.src }}" + dest: "/etc/update-motd.d/{{ item.path }}" + owner: "{{ ansible_user_id }}" + group: "{{ ansible_user_id }}" + mode: "0777" + with_community.general.filetree: + - templates/etc/update-motd.d/ + when: item.state == "file" + + - name: Check for existence of motd + ansible.builtin.stat: + path: /etc/motd + register: motd_file + + - name: Backup current motd + ansible.builtin.command: mv /etc/motd /etc/motd.bak + when: motd_file.stat.exists + changed_when: motd_file.stat.exists diff --git a/tasks/prereq-packages.yml b/tasks/prereq-packages.yml new file mode 100644 index 0000000..a1ba555 --- /dev/null +++ b/tasks/prereq-packages.yml @@ -0,0 +1,23 @@ +--- +- name: Stage packages that are pre-requisites + ansible.builtin.set_fact: + _common_install_packages: + - python3-debian + - python3-requests + - sudo + +- name: Update all packages if needed + ansible.builtin.apt: + update_cache: true + cache_valid_time: 86400 + upgrade: full + autoclean: true + autoremove: true + timeout: 600 # on slow raspberries, updating may take a long time + +- name: Install pre-requisite packages + ansible.builtin.apt: + update_cache: true + cache_valid_time: 86400 + name: "{{ _common_install_packages }}" + state: present diff --git a/tasks/set-hostname.yml b/tasks/set-hostname.yml new file mode 100644 index 0000000..a412df3 --- /dev/null +++ b/tasks/set-hostname.yml @@ -0,0 +1,17 @@ +--- +- name: Set the hostname + ansible.builtin.hostname: + name: "{{ inventory_hostname }}" + +- name: Replace double home with hostname (if present) + ansible.builtin.replace: + path: /etc/hosts + regexp: 127\.0\.1\.1 + replace: "{{ ansible_host }} {{ inventory_hostname }}" + +- name: Ensure at least one full hostname entry is present + ansible.builtin.lineinfile: + path: /etc/hosts + regexp: "{{ ansible_host }}.*{{ inventory_hostname }}" + line: "{{ ansible_host }} {{ inventory_hostname }}" + state: present diff --git a/tasks/ssh-config.yml b/tasks/ssh-config.yml new file mode 100644 index 0000000..2d70a32 --- /dev/null +++ b/tasks/ssh-config.yml @@ -0,0 +1,15 @@ +--- +- name: Set the appropriate options in sshd_config file + ansible.builtin.lineinfile: + path: "{{ sshd_config_file }}" + regexp: "{{ item.regexp }}" + line: "{{ item.line }}" + state: present + with_items: + - { regexp: "^#?PermitRootLogin", line: "PermitRootLogin no" } + - { regexp: "^#?PubkeyAuthentication", line: "PubkeyAuthentication yes" } + - { regexp: "^#?PubkeyAuthOptions", line: "PubkeyAuthOptions verify-required" } # to enable hardware token + - { regexp: "^#?PasswordAuthentication", line: "PasswordAuthentication no" } + - { regexp: "^#?KbdInteractiveAuthentication", line: "KbdInteractiveAuthentication no" } + - { regexp: "^#?UsePAM", line: "UsePAM yes" } # If no, ansible (passwordless) will not be able to perform SSH + notify: Restart sshd diff --git a/tasks/sudoers.yml b/tasks/sudoers.yml new file mode 100644 index 0000000..6ed903d --- /dev/null +++ b/tasks/sudoers.yml @@ -0,0 +1,7 @@ +--- +- name: Edit sudoers for passwordless sudo + ansible.builtin.lineinfile: + path: /etc/sudoers + regexp: ^%sudo + line: "%sudo ALL=(ALL:ALL) NOPASSWD: ALL" + state: present diff --git a/tasks/tmp-nonexec.yml b/tasks/tmp-nonexec.yml new file mode 100644 index 0000000..e21802e --- /dev/null +++ b/tasks/tmp-nonexec.yml @@ -0,0 +1,58 @@ +--- +# These tasks will set nonexec on /tmp +# But only if /tmp is not already nonexec +# Follows: https://waal70blog.wordpress.com/2014/08/08/debian-server-hardening-make-tmp-non-executable/ +- name: Get existence of tmpfs file, which resides in /var + ansible.builtin.stat: + path: /var/tmpfs + register: sttmp + +- name: Block to create, mount and bind tmpfs + when: not sttmp.stat.exists + block: + - name: Create file to hold tmp + community.general.filesize: + path: /var/tmpfs + size: 1G + owner: root + group: root + + - name: Initialize a ext3 filesystem in this file + community.general.filesystem: + dev: /var/tmpfs + fstype: ext3 + opts: "-j" + state: present + + - name: Move old tmp out of the way + ansible.builtin.command: + cmd: mv /tmp /old_tmp + failed_when: "{{ sttmp.stat.exists }}" + changed_when: true + + - name: Make the new file a permanent mount in fstab + ansible.posix.mount: + path: /tmp + src: /var/tmpfs + opts: "loop,nosuid,noexec,rw" + state: mounted + fstype: ext3 + + - name: Move the old stuff back into the new mountpoint + ansible.builtin.command: + cmd: mv /old_tmp/* /tmp/ + failed_when: "{{ sttmp.stat.exists }}" + changed_when: true + + - name: Ensure no more /old_tmp + ansible.builtin.file: + path: /old_tmp + state: absent + + - name: Make tmp world writeable + ansible.builtin.file: + path: /tmp + state: directory + owner: root + group: root + mode: '1777' diff --git a/tasks/unpriv-user.yml b/tasks/unpriv-user.yml new file mode 100644 index 0000000..96f3ff7 --- /dev/null +++ b/tasks/unpriv-user.yml @@ -0,0 +1,29 @@ +--- +- name: Ensure that unprivileged user is present + ansible.builtin.user: + name: "{{ interactive_user }}" + shell: /bin/bash + home: "{{ interactive_home }}" + password: "{{ interactive_password }}" + groups: sudo + create_home: true + skeleton: /etc/skel + append: true + +- name: Set the primary key for the unprivileged user, removing any others + ansible.posix.authorized_key: + user: "{{ interactive_user }}" + key: "{{ lookup('file', '../home/ssh-keys/{{ interactive_user }}/{{ interactive_user }}-yubi-1.pub') }}" + state: present + exclusive: true + +- name: Set the secondary key for the unprivileged user + ansible.posix.authorized_key: + user: "{{ interactive_user }}" + key: "{{ lookup('file', '../home/ssh-keys/{{ interactive_user }}/{{ interactive_user }}-yubi-2.pub') }}" + state: present + +- name: Install required package to become unprivileged users + ansible.builtin.apt: + name: acl + state: present diff --git a/templates/etc/ansible/facts.d/cpu_info.fact b/templates/etc/ansible/facts.d/cpu_info.fact new file mode 100755 index 0000000..3c6c18e --- /dev/null +++ b/templates/etc/ansible/facts.d/cpu_info.fact @@ -0,0 +1,17 @@ +#!/bin/bash + +hostname=$(hostname); +operating_system=$(hostnamectl | grep "Operating System" | cut -d ' ' -f3-); +architecture=$(arch); +processor_model=$(cat /proc/cpuinfo | grep -E '^(model name|Model)\s*:' | sort -u | cut -d ':' -f2- | cut -d ' ' -f2-); +memory=$(free -h --si --total | grep -oP '^Total:\s+([\d,]+[.]*[\d]*)[ ]*([A-Za-z]+)' | awk '{print $2}'); +system_main_ip=$(hostname -I); + +# Hacky way to check for multiple possibilities +cat /proc/cpuinfo | grep -q 'Raspberry'; rc=$? +systemtype=$(if [[ $rc -eq 0 ]]; then echo "Raspberry"; else echo "Generic"; fi) +cat /proc/cpuinfo | grep -q 'Intel'; rc=$? +systemtype=$(if [[ $rc -eq 0 ]]; then echo "Intel"; else echo "$systemtype"; fi) + +printf '{"systemtype":"%s","hostname":"%s","operating_system":"%s","architecture":"%s","processor_name":"%s","memory":"%s","system_main_ip":"%s"}'\ + "$systemtype" "$hostname" "$operating_system" "$architecture" "$processor_model" "$memory" "$system_main_ip" \ No newline at end of file diff --git a/templates/etc/update-motd.d/11-welcome b/templates/etc/update-motd.d/11-welcome new file mode 100755 index 0000000..df6931e --- /dev/null +++ b/templates/etc/update-motd.d/11-welcome @@ -0,0 +1,17 @@ +#!/bin/sh + +############################################################### +# Script : 11-welcome +# Author : Petr Všetečka, Andre +# Email : vsetecka@cesnet.cz +# Date : 27/11/2018, 16/07/2024 +# Description: prints distro name and kernel version +# Args : none +############################################################### + +clear + +printf "\nSystem managed by Ansible" + +printf "\nWelcome to "; lsb_release -ds +printf " System: "; uname -snrvm diff --git a/templates/etc/update-motd.d/13-uptime b/templates/etc/update-motd.d/13-uptime new file mode 100755 index 0000000..018f89a --- /dev/null +++ b/templates/etc/update-motd.d/13-uptime @@ -0,0 +1,13 @@ +#!/bin/sh + +############################################################### +# Script : 13-uptime +# Author : Petr Všetečka +# Email : vsetecka@cesnet.cz +# Date : 27/11/2018 +# Description: prints total uptime in nice format +# Args : none +############################################################### + + +printf "System is "; uptime -p diff --git a/templates/etc/update-motd.d/15-hwstats b/templates/etc/update-motd.d/15-hwstats new file mode 100755 index 0000000..fff291e --- /dev/null +++ b/templates/etc/update-motd.d/15-hwstats @@ -0,0 +1,31 @@ +#!/bin/sh + +############################################################### +# Script : 15-hwstats +# Author : Petr Všetečka +# Email : vsetecka@cesnet.cz +# Date : 27/11/2018 +# Description: prints current time and usage of CPU, RAM & HDD +# Args : none +############################################################### + + +printf "\nSystem information as of "; date +printf "\n" + +# CPU +printf " CPU load: "; cat /proc/loadavg | awk '{ printf "%s %s %s", $1, $2, $3; }' +printf " (" +printf $(($(ps -e --no-headers | wc -l) - 1)) +printf " processes)\n" +# RAM +free -m | awk '/Mem/ { printf " Memory: %4sM (%2d%%) out of %2.1fG\n", $3, ($3/$2) * 100, $2/1000; } + /Swap/ { + if ( $3 == 0 ) + printf " Swap: not available\n"; + else + printf " Swap: %4sM (%2d%%) out of %2.1fG\n", $3, ($3/$2) * 100, $2/1000; + + }' +# Disk +df -h | awk '/^\// { printf " Disk: %5s (%3s) out of %4s\n", $3, $5, $2; }' diff --git a/templates/etc/update-motd.d/17-users b/templates/etc/update-motd.d/17-users new file mode 100755 index 0000000..4d57330 --- /dev/null +++ b/templates/etc/update-motd.d/17-users @@ -0,0 +1,18 @@ +#!/bin/sh + +############################################################### +# Script : 17-users +# Author : Petr Všetečka +# Email : vsetecka@cesnet.cz +# Date : 27/11/2018 +# Description: prints names of all users currently logged in +# Args : none +############################################################### + + +printf "\n" +w -h | awk 'BEGIN { printf "Users logged in:"; } + { printf " %s", $1; }' +top -bn1 | awk 'BEGIN { FS=", "; } + $2~/user/ { print " (" $2 " total)"; } + $3~/user/ { print " (" $3 " total)"; }' diff --git a/templates/etc/update-motd.d/19-ipaddresses b/templates/etc/update-motd.d/19-ipaddresses new file mode 100644 index 0000000..8b038e2 --- /dev/null +++ b/templates/etc/update-motd.d/19-ipaddresses @@ -0,0 +1,19 @@ +#!/bin/sh + +############################################################### +# Script : 19-ipaddresses +# Author : Andre +# Date : 16/07/2024 +# Description: displays ipv4 and ipv6 addresses +# Args : none +############################################################### + +printf "\n" + +printf "IPv4 addresses\n" +printf "===============================================================================\n" +ip -h -br -f inet a +printf "===============================================================================\n" +printf "IPv6 addresses\n" +ip -h -br -f inet6 a +printf "===============================================================================\n" \ No newline at end of file