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:
@@ -19,6 +19,15 @@ terraform_server: "infra01"
|
||||
# Traefik variables
|
||||
traefik_server: "lightning-lane"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# JARVIS automation account
|
||||
# ---------------------------------------------------------------------------
|
||||
# Public key for the 'jarvis' user provisioned by the linux-baseline role on
|
||||
# every host. Public keys are not secret; the matching private key lives on
|
||||
# the JARVIS command centre (carousel-of-progress) and, when needed, in
|
||||
# group_vars/all/vault as vault_jarvis_ssh_private_key.
|
||||
jarvis_ssh_public_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID5sym5ajFvDyzw395BkHv7qVb66XPTx/OF1p19MGuNo jarvis@mk-labs"
|
||||
|
||||
step_ca_principal_mappings:
|
||||
- local_user: wed
|
||||
principals:
|
||||
|
||||
23
ansible/playbooks/day0_linux_baseline.yml
Normal file
23
ansible/playbooks/day0_linux_baseline.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# day0_linux_baseline.yml
|
||||
# ----------------------------------------------------------------------------
|
||||
# Applies the mk-labs Linux baseline (linux-baseline role) to one or more
|
||||
# hosts. Idempotent and safe to re-run.
|
||||
#
|
||||
# Usage:
|
||||
# ansible-playbook playbooks/day0_linux_baseline.yml
|
||||
# ansible-playbook playbooks/day0_linux_baseline.yml -e target=figment
|
||||
# ansible-playbook playbooks/day0_linux_baseline.yml -e target=semaphore_server
|
||||
#
|
||||
# To trigger an opt-in full system upgrade:
|
||||
# ansible-playbook playbooks/day0_linux_baseline.yml \
|
||||
# -e target=figment -e 'baseline_features={"full_upgrade": true}'
|
||||
# ============================================================================
|
||||
|
||||
- name: Apply mk-labs Linux baseline
|
||||
hosts: "{{ target | default('all') }}"
|
||||
become: true
|
||||
gather_facts: true
|
||||
roles:
|
||||
- linux-baseline
|
||||
64
ansible/roles/linux-baseline/README.md
Normal file
64
ansible/roles/linux-baseline/README.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# linux-baseline
|
||||
|
||||
Idempotent baseline for any Linux host in mk-labs.
|
||||
|
||||
## What it does
|
||||
|
||||
Applied on every host, this role guarantees a known-good operational baseline:
|
||||
|
||||
| Capability | Default | Description |
|
||||
|----------------------|---------|---------------------------------------------------------------|
|
||||
| `packages` | on | Installs curated common + OS-family packages, plus host extras |
|
||||
| `timezone` | on | Sets timezone and locale |
|
||||
| `time_sync` | on | Installs and configures chrony against `sundial` |
|
||||
| `users` | on | Provisions baseline admin accounts (jarvis) with SSH + sudo |
|
||||
| `ssh_hardening` | on | Deploys `/etc/ssh/sshd_config.d/10-mk-labs-hardening.conf` |
|
||||
| `unattended_upgrades`| on | Security-only automatic upgrades on Debian-family hosts |
|
||||
| `sysctl` | on | Drop-in at `/etc/sysctl.d/99-mk-labs.conf` |
|
||||
| `journald` | on | Caps journal size + retention |
|
||||
| `motd` | on | Branded MOTD |
|
||||
| `full_upgrade` | off | Opt-in full dist-upgrade. Use the day2 update playbook instead |
|
||||
|
||||
Every capability sits behind a feature flag in `baseline_features` so it
|
||||
can be disabled per-host without forking the role.
|
||||
|
||||
## OS support
|
||||
|
||||
- **First-class:** Ubuntu (22.04, 24.04) and Debian (bullseye, bookworm).
|
||||
- **Placeholder:** RHEL family — see `vars/RedHat.yml`. Flesh out when a
|
||||
RHEL-family host joins the fleet.
|
||||
|
||||
OS-specific values are loaded from `vars/{{ ansible_os_family }}.yml`.
|
||||
|
||||
## Required inventory variables
|
||||
|
||||
| Variable | Where | Example |
|
||||
|-------------------------|-----------------|-------------------------------------------|
|
||||
| `jarvis_ssh_public_key` | group_vars/all | `ssh-ed25519 AAAA... jarvis@mk-labs` |
|
||||
|
||||
## Usage
|
||||
|
||||
```yaml
|
||||
- name: Apply linux-baseline
|
||||
hosts: "{{ target | default('all') }}"
|
||||
become: true
|
||||
roles:
|
||||
- linux-baseline
|
||||
```
|
||||
|
||||
## Per-host overrides
|
||||
|
||||
```yaml
|
||||
# host_vars/some-host/vars
|
||||
baseline_packages_extra:
|
||||
- postgresql-client
|
||||
- redis-tools
|
||||
|
||||
baseline_features:
|
||||
motd: false
|
||||
```
|
||||
|
||||
## Migration notes
|
||||
|
||||
This role supersedes the legacy `day0-baseline` and `common` roles, both
|
||||
of which are slated for removal once the fleet has been re-baselined.
|
||||
120
ansible/roles/linux-baseline/defaults/main.yml
Normal file
120
ansible/roles/linux-baseline/defaults/main.yml
Normal file
@@ -0,0 +1,120 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# linux-baseline role defaults
|
||||
# ============================================================================
|
||||
# This role applies an idempotent baseline to any Linux host in mk-labs.
|
||||
# Ubuntu is the first-class target; other distributions plug in via
|
||||
# vars/<ansible_os_family>.yml.
|
||||
# ============================================================================
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Feature flags - each capability can be toggled independently
|
||||
# ---------------------------------------------------------------------------
|
||||
baseline_features:
|
||||
packages: true
|
||||
timezone: true
|
||||
time_sync: true
|
||||
ssh_hardening: true
|
||||
users: true
|
||||
unattended_upgrades: true
|
||||
sysctl: true
|
||||
journald: true
|
||||
motd: true
|
||||
# Off by default: a full apt/dnf upgrade is intrusive. Use the dedicated
|
||||
# day2 update playbook for that. Security patches are handled by
|
||||
# unattended-upgrades.
|
||||
full_upgrade: false
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Locale & time
|
||||
# ---------------------------------------------------------------------------
|
||||
baseline_timezone: America/Chicago
|
||||
baseline_locale: en_US.UTF-8
|
||||
baseline_ntp_server: sundial.local.mk-labs.cloud
|
||||
baseline_ntp_fallback_pool: 2.pool.ntp.org
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Packages
|
||||
# ---------------------------------------------------------------------------
|
||||
# Distribution-agnostic baseline. Distro-specific additions are merged in
|
||||
# from vars/<family>.yml at runtime.
|
||||
baseline_packages_common:
|
||||
- curl
|
||||
- wget
|
||||
- vim
|
||||
- htop
|
||||
- git
|
||||
- jq
|
||||
- unzip
|
||||
- rsync
|
||||
- ca-certificates
|
||||
- gnupg
|
||||
- net-tools
|
||||
- dnsutils
|
||||
- acl
|
||||
- tree
|
||||
- tmux
|
||||
|
||||
# Per-host opt-in extras. Set in host_vars or group_vars.
|
||||
baseline_packages_extra: []
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Baseline users
|
||||
# ---------------------------------------------------------------------------
|
||||
# 'jarvis' is the universal admin / automation account that this role
|
||||
# provisions on every host. Service users (e.g. 'cast' for podman
|
||||
# workloads) are created by their respective application roles, not here.
|
||||
baseline_users:
|
||||
- name: jarvis
|
||||
comment: "JARVIS automation account"
|
||||
shell: /bin/bash
|
||||
groups: "{{ baseline_sudo_group }}"
|
||||
sudo_nopasswd: true
|
||||
ssh_authorized_keys:
|
||||
- "{{ jarvis_ssh_public_key }}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SSH hardening
|
||||
# ---------------------------------------------------------------------------
|
||||
# Delivered as a drop-in under /etc/ssh/sshd_config.d/ rather than editing
|
||||
# the main config file. Safer to re-apply, easier to remove.
|
||||
baseline_ssh_permit_root_login: "no"
|
||||
baseline_ssh_password_authentication: "no"
|
||||
baseline_ssh_pubkey_authentication: "yes"
|
||||
baseline_ssh_permit_empty_passwords: "no"
|
||||
baseline_ssh_x11_forwarding: "no"
|
||||
baseline_ssh_client_alive_interval: 300
|
||||
baseline_ssh_client_alive_count_max: 2
|
||||
baseline_ssh_max_auth_tries: 4
|
||||
baseline_ssh_login_grace_time: 30
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Unattended security upgrades (apt / dnf-automatic)
|
||||
# ---------------------------------------------------------------------------
|
||||
baseline_unattended_security_only: true
|
||||
baseline_unattended_auto_reboot: false
|
||||
baseline_unattended_auto_reboot_time: "03:00"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# sysctl
|
||||
# ---------------------------------------------------------------------------
|
||||
# Light hand here. The kernel defaults are generally sensible; we tune
|
||||
# only what consistently bites us in a homelab.
|
||||
baseline_sysctl:
|
||||
vm.swappiness: 10
|
||||
fs.inotify.max_user_watches: 524288
|
||||
fs.inotify.max_user_instances: 512
|
||||
net.ipv4.tcp_keepalive_time: 600
|
||||
net.ipv4.tcp_keepalive_intvl: 60
|
||||
net.ipv4.tcp_keepalive_probes: 6
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# journald retention caps
|
||||
# ---------------------------------------------------------------------------
|
||||
baseline_journald_max_use: "1G"
|
||||
baseline_journald_max_retention: "30day"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# MOTD branding
|
||||
# ---------------------------------------------------------------------------
|
||||
baseline_motd_banner: "mk-labs"
|
||||
20
ansible/roles/linux-baseline/handlers/main.yml
Normal file
20
ansible/roles/linux-baseline/handlers/main.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
- name: Restart chrony
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ baseline_chrony_service }}"
|
||||
state: restarted
|
||||
|
||||
- name: Restart sshd
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ baseline_sshd_service }}"
|
||||
state: restarted
|
||||
|
||||
- name: Reload sysctl
|
||||
ansible.builtin.command:
|
||||
cmd: sysctl --system
|
||||
changed_when: false
|
||||
|
||||
- name: Restart systemd-journald
|
||||
ansible.builtin.systemd:
|
||||
name: systemd-journald
|
||||
state: restarted
|
||||
25
ansible/roles/linux-baseline/meta/main.yml
Normal file
25
ansible/roles/linux-baseline/meta/main.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
galaxy_info:
|
||||
role_name: linux_baseline
|
||||
author: JARVIS
|
||||
description: >-
|
||||
Idempotent Linux baseline for mk-labs hosts. Ubuntu first-class,
|
||||
pluggable for other distributions via vars/<os_family>.yml.
|
||||
license: MIT
|
||||
min_ansible_version: "2.14"
|
||||
platforms:
|
||||
- name: Ubuntu
|
||||
versions:
|
||||
- jammy
|
||||
- noble
|
||||
- name: Debian
|
||||
versions:
|
||||
- bullseye
|
||||
- bookworm
|
||||
galaxy_tags:
|
||||
- baseline
|
||||
- bootstrap
|
||||
- hardening
|
||||
- homelab
|
||||
|
||||
dependencies: []
|
||||
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
|
||||
@@ -0,0 +1,26 @@
|
||||
// {{ ansible_managed }}
|
||||
// mk-labs unattended-upgrades policy
|
||||
|
||||
Unattended-Upgrade::Allowed-Origins {
|
||||
{% if baseline_unattended_security_only %}
|
||||
"${distro_id}:${distro_codename}-security";
|
||||
"${distro_id}ESMApps:${distro_codename}-apps-security";
|
||||
"${distro_id}ESM:${distro_codename}-infra-security";
|
||||
{% else %}
|
||||
"${distro_id}:${distro_codename}";
|
||||
"${distro_id}:${distro_codename}-security";
|
||||
"${distro_id}:${distro_codename}-updates";
|
||||
{% endif %}
|
||||
};
|
||||
|
||||
Unattended-Upgrade::Package-Blacklist { };
|
||||
Unattended-Upgrade::DevRelease "auto";
|
||||
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
|
||||
Unattended-Upgrade::Remove-New-Unused-Dependencies "true";
|
||||
Unattended-Upgrade::Remove-Unused-Dependencies "false";
|
||||
|
||||
Unattended-Upgrade::Automatic-Reboot "{{ baseline_unattended_auto_reboot | string | lower }}";
|
||||
Unattended-Upgrade::Automatic-Reboot-WithUsers "false";
|
||||
Unattended-Upgrade::Automatic-Reboot-Time "{{ baseline_unattended_auto_reboot_time }}";
|
||||
|
||||
Unattended-Upgrade::SyslogEnable "true";
|
||||
@@ -0,0 +1,5 @@
|
||||
# {{ ansible_managed }}
|
||||
# mk-labs sysctl baseline
|
||||
{% for key, value in baseline_sysctl.items() %}
|
||||
{{ key }} = {{ value }}
|
||||
{% endfor %}
|
||||
28
ansible/roles/linux-baseline/templates/chrony.conf.j2
Normal file
28
ansible/roles/linux-baseline/templates/chrony.conf.j2
Normal file
@@ -0,0 +1,28 @@
|
||||
# {{ ansible_managed }}
|
||||
# Managed by Ansible - linux-baseline role
|
||||
# Local edits will be overwritten.
|
||||
|
||||
# Primary NTP source: mk-labs internal NTP server (sundial)
|
||||
server {{ baseline_ntp_server }} iburst prefer
|
||||
|
||||
# Fallback: public NTP pool
|
||||
pool {{ baseline_ntp_fallback_pool }} iburst maxsources 2
|
||||
|
||||
driftfile /var/lib/chrony/chrony.drift
|
||||
|
||||
# Step the clock if the adjustment is >1s on the first 3 corrections
|
||||
makestep 1.0 3
|
||||
|
||||
# Sync RTC from system clock
|
||||
rtcsync
|
||||
|
||||
# Allow chronyd to use the keyfile for hardware refclock
|
||||
{% if ansible_os_family == "Debian" %}
|
||||
keyfile /etc/chrony/chrony.keys
|
||||
{% else %}
|
||||
keyfile /etc/chrony.keys
|
||||
{% endif %}
|
||||
|
||||
# Log selective events
|
||||
logdir /var/log/chrony
|
||||
log measurements statistics tracking
|
||||
@@ -0,0 +1,8 @@
|
||||
# {{ ansible_managed }}
|
||||
# mk-labs journald retention policy
|
||||
[Journal]
|
||||
SystemMaxUse={{ baseline_journald_max_use }}
|
||||
MaxRetentionSec={{ baseline_journald_max_retention }}
|
||||
Storage=persistent
|
||||
Compress=yes
|
||||
ForwardToSyslog=no
|
||||
11
ansible/roles/linux-baseline/templates/motd.j2
Normal file
11
ansible/roles/linux-baseline/templates/motd.j2
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
╔═══════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ {{ baseline_motd_banner }} - {{ inventory_hostname }}{{ ' ' * (50 - (baseline_motd_banner | length) - (inventory_hostname | length)) }}║
|
||||
║ ║
|
||||
║ {{ ansible_distribution }} {{ ansible_distribution_version }} ({{ ansible_kernel }}){{ ' ' * (53 - (ansible_distribution | length) - (ansible_distribution_version | length) - (ansible_kernel | length)) }}║
|
||||
║ ║
|
||||
║ Managed by Ansible. Local changes will be overwritten. ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════════╝
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# {{ ansible_managed }}
|
||||
# mk-labs SSH hardening drop-in
|
||||
# Managed by Ansible - linux-baseline role
|
||||
|
||||
PermitRootLogin {{ baseline_ssh_permit_root_login }}
|
||||
PasswordAuthentication {{ baseline_ssh_password_authentication }}
|
||||
PubkeyAuthentication {{ baseline_ssh_pubkey_authentication }}
|
||||
PermitEmptyPasswords {{ baseline_ssh_permit_empty_passwords }}
|
||||
X11Forwarding {{ baseline_ssh_x11_forwarding }}
|
||||
|
||||
ClientAliveInterval {{ baseline_ssh_client_alive_interval }}
|
||||
ClientAliveCountMax {{ baseline_ssh_client_alive_count_max }}
|
||||
MaxAuthTries {{ baseline_ssh_max_auth_tries }}
|
||||
LoginGraceTime {{ baseline_ssh_login_grace_time }}
|
||||
|
||||
# Prefer modern key exchange and ciphers
|
||||
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512
|
||||
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
|
||||
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
|
||||
22
ansible/roles/linux-baseline/vars/Debian.yml
Normal file
22
ansible/roles/linux-baseline/vars/Debian.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# Debian-family overrides (Ubuntu, Debian)
|
||||
# ============================================================================
|
||||
|
||||
# Package facts module needs python3-apt on the target.
|
||||
baseline_packages_family:
|
||||
- python3-apt
|
||||
- apt-transport-https
|
||||
- software-properties-common
|
||||
- lsb-release
|
||||
- unattended-upgrades
|
||||
|
||||
baseline_sudo_group: sudo
|
||||
|
||||
# Service names
|
||||
baseline_chrony_service: chrony
|
||||
baseline_chrony_config_path: /etc/chrony/chrony.conf
|
||||
baseline_sshd_service: ssh
|
||||
|
||||
# Unattended-upgrades file locations
|
||||
baseline_unattended_config_dir: /etc/apt/apt.conf.d
|
||||
19
ansible/roles/linux-baseline/vars/RedHat.yml
Normal file
19
ansible/roles/linux-baseline/vars/RedHat.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# RedHat-family overrides (Rocky, AlmaLinux, RHEL) - placeholder
|
||||
# ============================================================================
|
||||
# This file exists to make the OS abstraction visible. It is not exercised
|
||||
# today because mk-labs is all-Ubuntu. Flesh out when a RHEL-family host
|
||||
# enters the fleet.
|
||||
|
||||
baseline_packages_family:
|
||||
- dnf-automatic
|
||||
- python3-dnf
|
||||
|
||||
baseline_sudo_group: wheel
|
||||
|
||||
baseline_chrony_service: chronyd
|
||||
baseline_chrony_config_path: /etc/chrony.conf
|
||||
baseline_sshd_service: sshd
|
||||
|
||||
baseline_unattended_config_dir: /etc/dnf/automatic.conf.d
|
||||
Reference in New Issue
Block a user