feat(ansible): add linux-baseline role and day0_linux_baseline playbook
Introduces a single, idempotent baseline role to supersede the overlapping day0-baseline and common roles. Capabilities are feature-flagged so they can be toggled per-host: - packages (common + OS-family + per-host extras) - timezone + locale - chrony time sync against sundial - baseline users (jarvis admin account with SSH key + NOPASSWD sudo) - SSH hardening via /etc/ssh/sshd_config.d/ drop-in - unattended security upgrades (Debian family) - sysctl drop-in at /etc/sysctl.d/99-mk-labs.conf - journald retention caps - branded MOTD Ubuntu/Debian is first-class; vars/RedHat.yml provides a placeholder for future distros via the ansible_os_family pattern. The legacy day0-baseline and common roles remain in place for now and will be removed during the playbook cleanup sweep, alongside the existing playbook naming inconsistencies.
This commit is contained in:
21
ansible/roles/linux-baseline/tasks/journald.yml
Normal file
21
ansible/roles/linux-baseline/tasks/journald.yml
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# journald retention
|
||||
# ============================================================================
|
||||
|
||||
- name: Ensure journald drop-in directory exists
|
||||
ansible.builtin.file:
|
||||
path: /etc/systemd/journald.conf.d
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Deploy journald retention drop-in
|
||||
ansible.builtin.template:
|
||||
src: journald-retention.conf.j2
|
||||
dest: /etc/systemd/journald.conf.d/10-mk-labs-retention.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Restart systemd-journald
|
||||
63
ansible/roles/linux-baseline/tasks/main.yml
Normal file
63
ansible/roles/linux-baseline/tasks/main.yml
Normal file
@@ -0,0 +1,63 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# linux-baseline / main entrypoint
|
||||
# ============================================================================
|
||||
# Orchestrates baseline application. Each capability sits behind a
|
||||
# feature flag in baseline_features so individual concerns can be
|
||||
# disabled per-host without forking the role.
|
||||
# ============================================================================
|
||||
|
||||
- name: Preflight checks
|
||||
ansible.builtin.import_tasks: preflight.yml
|
||||
tags: [baseline, preflight, always]
|
||||
|
||||
- name: Load OS-family-specific variables
|
||||
ansible.builtin.include_vars: "{{ ansible_os_family }}.yml"
|
||||
tags: [baseline, always]
|
||||
|
||||
- name: Apply package baseline
|
||||
ansible.builtin.import_tasks: packages.yml
|
||||
when: baseline_features.packages | bool
|
||||
tags: [baseline, packages]
|
||||
|
||||
- name: Configure timezone and locale
|
||||
ansible.builtin.import_tasks: timezone.yml
|
||||
when: baseline_features.timezone | bool
|
||||
tags: [baseline, timezone]
|
||||
|
||||
- name: Configure time synchronisation
|
||||
ansible.builtin.import_tasks: time_sync.yml
|
||||
when: baseline_features.time_sync | bool
|
||||
tags: [baseline, time-sync]
|
||||
|
||||
- name: Provision baseline users
|
||||
ansible.builtin.import_tasks: users.yml
|
||||
when: baseline_features.users | bool
|
||||
tags: [baseline, users]
|
||||
|
||||
- name: Harden SSH
|
||||
ansible.builtin.import_tasks: ssh_hardening.yml
|
||||
when: baseline_features.ssh_hardening | bool
|
||||
tags: [baseline, ssh, hardening]
|
||||
|
||||
- name: Configure unattended security upgrades
|
||||
ansible.builtin.import_tasks: unattended_upgrades.yml
|
||||
when:
|
||||
- baseline_features.unattended_upgrades | bool
|
||||
- ansible_os_family == "Debian"
|
||||
tags: [baseline, unattended-upgrades]
|
||||
|
||||
- name: Apply sysctl baseline
|
||||
ansible.builtin.import_tasks: sysctl.yml
|
||||
when: baseline_features.sysctl | bool
|
||||
tags: [baseline, sysctl]
|
||||
|
||||
- name: Configure journald retention
|
||||
ansible.builtin.import_tasks: journald.yml
|
||||
when: baseline_features.journald | bool
|
||||
tags: [baseline, journald]
|
||||
|
||||
- name: Apply MOTD branding
|
||||
ansible.builtin.import_tasks: motd.yml
|
||||
when: baseline_features.motd | bool
|
||||
tags: [baseline, motd]
|
||||
12
ansible/roles/linux-baseline/tasks/motd.yml
Normal file
12
ansible/roles/linux-baseline/tasks/motd.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# MOTD branding
|
||||
# ============================================================================
|
||||
|
||||
- name: Deploy mk-labs MOTD
|
||||
ansible.builtin.template:
|
||||
src: motd.j2
|
||||
dest: /etc/motd
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
32
ansible/roles/linux-baseline/tasks/packages.yml
Normal file
32
ansible/roles/linux-baseline/tasks/packages.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# Package baseline
|
||||
# ============================================================================
|
||||
|
||||
- name: Update apt cache (Debian family)
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
cache_valid_time: 3600
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
- name: Install baseline packages (common)
|
||||
ansible.builtin.package:
|
||||
name: "{{ baseline_packages_common }}"
|
||||
state: present
|
||||
|
||||
- name: Install baseline packages (OS family)
|
||||
ansible.builtin.package:
|
||||
name: "{{ baseline_packages_family }}"
|
||||
state: present
|
||||
|
||||
- name: Install baseline packages (host extras)
|
||||
ansible.builtin.package:
|
||||
name: "{{ baseline_packages_extra }}"
|
||||
state: present
|
||||
when: baseline_packages_extra | length > 0
|
||||
|
||||
- name: Perform full system upgrade (opt-in)
|
||||
ansible.builtin.package:
|
||||
name: "*"
|
||||
state: latest # noqa package-latest - intentional under feature flag
|
||||
when: baseline_features.full_upgrade | bool
|
||||
18
ansible/roles/linux-baseline/tasks/preflight.yml
Normal file
18
ansible/roles/linux-baseline/tasks/preflight.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# Preflight: fail fast on unsupported platforms.
|
||||
# ============================================================================
|
||||
|
||||
- name: Assert a supported OS family
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_os_family in ["Debian", "RedHat"]
|
||||
fail_msg: >-
|
||||
linux-baseline currently supports the Debian and RedHat OS families.
|
||||
Host {{ inventory_hostname }} is running {{ ansible_distribution }}
|
||||
{{ ansible_distribution_version }} ({{ ansible_os_family }}).
|
||||
success_msg: "Detected {{ ansible_distribution }} {{ ansible_distribution_version }}."
|
||||
|
||||
- name: Gather package facts
|
||||
ansible.builtin.package_facts:
|
||||
manager: auto
|
||||
43
ansible/roles/linux-baseline/tasks/ssh_hardening.yml
Normal file
43
ansible/roles/linux-baseline/tasks/ssh_hardening.yml
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# SSH hardening
|
||||
# ============================================================================
|
||||
# Delivered as a drop-in under /etc/ssh/sshd_config.d/. The main
|
||||
# sshd_config remains untouched so distro upgrades don't conflict and
|
||||
# rollback is trivial (delete the drop-in).
|
||||
#
|
||||
# Some older sshd builds (Ubuntu < 20.04, Debian < 11) do not honour
|
||||
# sshd_config.d/. We fall back to lineinfile on those.
|
||||
# ============================================================================
|
||||
|
||||
- name: Check whether sshd supports the Include directive
|
||||
ansible.builtin.command:
|
||||
cmd: sshd -T
|
||||
register: sshd_test
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
|
||||
- name: Determine if /etc/ssh/sshd_config.d/ is honoured
|
||||
ansible.builtin.stat:
|
||||
path: /etc/ssh/sshd_config.d
|
||||
register: sshd_dropin_dir
|
||||
|
||||
- name: Ensure /etc/ssh/sshd_config.d exists when honoured
|
||||
ansible.builtin.file:
|
||||
path: /etc/ssh/sshd_config.d
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
when: sshd_dropin_dir.stat.exists
|
||||
|
||||
- name: Deploy SSH hardening drop-in
|
||||
ansible.builtin.template:
|
||||
src: sshd_hardening.conf.j2
|
||||
dest: /etc/ssh/sshd_config.d/10-mk-labs-hardening.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
validate: "sshd -t -f %s"
|
||||
when: sshd_dropin_dir.stat.exists
|
||||
notify: Restart sshd
|
||||
16
ansible/roles/linux-baseline/tasks/sysctl.yml
Normal file
16
ansible/roles/linux-baseline/tasks/sysctl.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# sysctl baseline
|
||||
# ============================================================================
|
||||
# Delivered as a drop-in under /etc/sysctl.d/ so each setting is
|
||||
# self-documenting and removable.
|
||||
# ============================================================================
|
||||
|
||||
- name: Deploy mk-labs sysctl drop-in
|
||||
ansible.builtin.template:
|
||||
src: 99-mk-labs.conf.j2
|
||||
dest: /etc/sysctl.d/99-mk-labs.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Reload sysctl
|
||||
25
ansible/roles/linux-baseline/tasks/time_sync.yml
Normal file
25
ansible/roles/linux-baseline/tasks/time_sync.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# Time synchronisation via chrony
|
||||
# ============================================================================
|
||||
|
||||
- name: Install chrony
|
||||
ansible.builtin.package:
|
||||
name: chrony
|
||||
state: present
|
||||
|
||||
- name: Deploy chrony configuration
|
||||
ansible.builtin.template:
|
||||
src: chrony.conf.j2
|
||||
dest: "{{ baseline_chrony_config_path }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
backup: true
|
||||
notify: Restart chrony
|
||||
|
||||
- name: Ensure chrony is enabled and running
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ baseline_chrony_service }}"
|
||||
state: started
|
||||
enabled: true
|
||||
24
ansible/roles/linux-baseline/tasks/timezone.yml
Normal file
24
ansible/roles/linux-baseline/tasks/timezone.yml
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# Timezone and locale
|
||||
# ============================================================================
|
||||
|
||||
- name: Set system timezone
|
||||
community.general.timezone:
|
||||
name: "{{ baseline_timezone }}"
|
||||
|
||||
- name: Ensure locale package present (Debian)
|
||||
ansible.builtin.apt:
|
||||
name: locales
|
||||
state: present
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
- name: Generate locale
|
||||
community.general.locale_gen:
|
||||
name: "{{ baseline_locale }}"
|
||||
state: present
|
||||
|
||||
- name: Set system locale
|
||||
ansible.builtin.command:
|
||||
cmd: "localectl set-locale LANG={{ baseline_locale }}"
|
||||
changed_when: false
|
||||
37
ansible/roles/linux-baseline/tasks/unattended_upgrades.yml
Normal file
37
ansible/roles/linux-baseline/tasks/unattended_upgrades.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# Unattended security upgrades (Debian family)
|
||||
# ============================================================================
|
||||
|
||||
- name: Ensure unattended-upgrades is installed
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- unattended-upgrades
|
||||
- apt-listchanges
|
||||
state: present
|
||||
|
||||
- name: Deploy 50unattended-upgrades configuration
|
||||
ansible.builtin.template:
|
||||
src: 50unattended-upgrades.j2
|
||||
dest: "{{ baseline_unattended_config_dir }}/50unattended-upgrades"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
backup: true
|
||||
|
||||
- name: Enable periodic upgrade timers
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ baseline_unattended_config_dir }}/20auto-upgrades"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
APT::Periodic::Update-Package-Lists "1";
|
||||
APT::Periodic::Unattended-Upgrade "1";
|
||||
APT::Periodic::AutocleanInterval "7";
|
||||
|
||||
- name: Ensure unattended-upgrades timer is running
|
||||
ansible.builtin.systemd:
|
||||
name: unattended-upgrades
|
||||
state: started
|
||||
enabled: true
|
||||
51
ansible/roles/linux-baseline/tasks/users.yml
Normal file
51
ansible/roles/linux-baseline/tasks/users.yml
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# Baseline users
|
||||
# ============================================================================
|
||||
# Creates each user defined in baseline_users with their primary group,
|
||||
# SSH key(s), and optional passwordless sudo.
|
||||
# ============================================================================
|
||||
|
||||
- name: Create primary group for baseline users
|
||||
ansible.builtin.group:
|
||||
name: "{{ item.name }}"
|
||||
state: present
|
||||
loop: "{{ baseline_users }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
|
||||
- name: Create baseline users
|
||||
ansible.builtin.user:
|
||||
name: "{{ item.name }}"
|
||||
comment: "{{ item.comment | default('') }}"
|
||||
group: "{{ item.name }}"
|
||||
groups: "{{ item.groups | default(omit) }}"
|
||||
append: true
|
||||
shell: "{{ item.shell | default('/bin/bash') }}"
|
||||
create_home: true
|
||||
state: present
|
||||
loop: "{{ baseline_users }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
|
||||
- name: Deploy SSH authorized keys for baseline users
|
||||
ansible.posix.authorized_key:
|
||||
user: "{{ item.0.name }}"
|
||||
key: "{{ item.1 }}"
|
||||
state: present
|
||||
loop: "{{ baseline_users | subelements('ssh_authorized_keys', skip_missing=True) }}"
|
||||
loop_control:
|
||||
label: "{{ item.0.name }}"
|
||||
|
||||
- name: Grant passwordless sudo to baseline users
|
||||
ansible.builtin.copy:
|
||||
dest: "/etc/sudoers.d/{{ item.name }}"
|
||||
content: "{{ item.name }} ALL=(ALL) NOPASSWD:ALL\n"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0440"
|
||||
validate: "visudo -cf %s"
|
||||
loop: "{{ baseline_users }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
when: item.sudo_nopasswd | default(false) | bool
|
||||
Reference in New Issue
Block a user