From 94be59bb269e1a87031f0e9cbf3e657ff9cfc08d Mon Sep 17 00:00:00 2001 From: directlx Date: Wed, 4 Feb 2026 08:33:36 -0500 Subject: [PATCH] Add common role, scripts, and documentation - ansible.cfg: Set remote_user to dlxadmin - inventory: Add infrastructure, application hosts with IPs - group_vars/all.yml: Set ansible_user to dlxadmin - playbooks/site.yml: Enable common role - roles/common: Baseline configuration role - Package installation (Debian/RedHat/Arch) - Timezone and locale setup - User management with SSH keys - SSH hardening - UFW firewall and security settings - scripts/create-user.sh: Create ansible user on servers - USAGE.md: Project usage documentation - HOSTS.md: Infrastructure host inventory Co-Authored-By: Claude Opus 4.5 --- HOSTS.md | 63 ++++++++++++ USAGE.md | 175 ++++++++++++++++++++++++++++++++ ansible.cfg | 1 + group_vars/all.yml | 3 +- inventory/hosts.yml | 16 ++- playbooks/site.yml | 4 +- roles/.gitkeep | 0 roles/common/defaults/main.yml | 53 ++++++++++ roles/common/handlers/main.yml | 7 ++ roles/common/tasks/main.yml | 32 ++++++ roles/common/tasks/packages.yml | 26 +++++ roles/common/tasks/security.yml | 66 ++++++++++++ roles/common/tasks/ssh.yml | 28 +++++ roles/common/tasks/timezone.yml | 21 ++++ roles/common/tasks/users.yml | 35 +++++++ roles/common/vars/default.yml | 2 + scripts/create-user.sh | 94 +++++++++++++++++ 17 files changed, 621 insertions(+), 5 deletions(-) create mode 100644 HOSTS.md create mode 100644 USAGE.md delete mode 100644 roles/.gitkeep create mode 100644 roles/common/defaults/main.yml create mode 100644 roles/common/handlers/main.yml create mode 100644 roles/common/tasks/main.yml create mode 100644 roles/common/tasks/packages.yml create mode 100644 roles/common/tasks/security.yml create mode 100644 roles/common/tasks/ssh.yml create mode 100644 roles/common/tasks/timezone.yml create mode 100644 roles/common/tasks/users.yml create mode 100644 roles/common/vars/default.yml create mode 100755 scripts/create-user.sh diff --git a/HOSTS.md b/HOSTS.md new file mode 100644 index 0000000..091d2da --- /dev/null +++ b/HOSTS.md @@ -0,0 +1,63 @@ +# Infrastructure Hosts + +## Summary + +| Host | IP | OS | Group | +|------|-----|-----|-------| +| ansible-node | 192.168.200.106 | Debian 18.0 | control | +| postgres | 192.168.200.103 | Debian 18.1 | dbservers | +| mysql | 192.168.200.110 | Debian 18.1 | dbservers | +| mongo | 192.168.200.111 | Ubuntu 24.10 | dbservers | +| nginx | 192.168.200.65 | Ubuntu 24.04 | webservers | +| npm | 192.168.200.71 | Debian 12.12 | webservers | +| docker | 192.168.200.200 | Ubuntu 25.04 | infrastructure | +| pihole | 192.168.200.100 | Ubuntu 24.04 | infrastructure | +| gitea | 192.168.200.102 | Debian 18.0 | infrastructure | +| jenkins | 192.168.200.91 | Ubuntu 24.04 | infrastructure | +| hiveops | 192.168.200.112 | Ubuntu 24.04 | application | +| smartjournal | 192.168.200.114 | Ubuntu 24.04 | application | +| odoo | 192.168.200.61 | Debian 18.0 | application | +| localhost | - | Pop!_OS 24.04 | local | + +## Groups + +### control +- ansible-node (192.168.200.106) + +### dbservers +- postgres (192.168.200.103) +- mysql (192.168.200.110) +- mongo (192.168.200.111) + +### webservers +- nginx (192.168.200.65) +- npm (192.168.200.71) + +### infrastructure +- docker (192.168.200.200) +- pihole (192.168.200.100) +- gitea (192.168.200.102) +- jenkins (192.168.200.91) + +### application +- hiveops (192.168.200.112) +- smartjournal (192.168.200.114) +- odoo (192.168.200.61) + +## Ansible User + +All hosts use `dlxadmin` as the ansible user with passwordless sudo. + +## Quick Commands + +```bash +# Test all hosts +ansible-playbook playbooks/ping.yml + +# Test specific group +ansible-playbook playbooks/ping.yml -l dbservers + +# Run ad-hoc command +ansible all -a "uptime" +ansible dbservers -a "df -h" +``` diff --git a/USAGE.md b/USAGE.md new file mode 100644 index 0000000..95e5289 --- /dev/null +++ b/USAGE.md @@ -0,0 +1,175 @@ +# Ansible Project Usage + +## Quick Start + +### 1. Setup SSH Access + +```bash +# For existing user +./scripts/setup-ssh.sh + +# Create new user via admin account +./scripts/setup-ssh.sh + +# Examples +./scripts/setup-ssh.sh 192.168.200.103 ansible +./scripts/setup-ssh.sh 192.168.200.103 ansible root +``` + +### 2. Test Connectivity + +```bash +# Test all hosts +ansible-playbook playbooks/ping.yml + +# Test specific group +ansible-playbook playbooks/ping.yml -l dbservers + +# Test single host +ansible-playbook playbooks/ping.yml -l postgres + +# Quick ping (no playbook) +ansible all -m ping +``` + +### 3. Run Playbooks + +```bash +# Apply common configuration to all hosts +ansible-playbook playbooks/site.yml + +# Limit to specific group +ansible-playbook playbooks/site.yml -l dbservers + +# Limit to specific host +ansible-playbook playbooks/site.yml -l postgres + +# Dry run (check mode) +ansible-playbook playbooks/site.yml --check + +# Run specific tags only +ansible-playbook playbooks/site.yml --tags packages +ansible-playbook playbooks/site.yml --tags security +ansible-playbook playbooks/site.yml --tags ssh +``` + +## Inventory + +Hosts are defined in `inventory/hosts.yml`: + +| Group | Host | IP | +|----------------|--------------|-----------------| +| control | ansible-node | 192.168.200.106 | +| dbservers | postgres | 192.168.200.103 | +| dbservers | mysql | 192.168.200.110 | +| dbservers | mongo | 192.168.200.111 | +| webservers | nginx | 192.168.200.65 | +| webservers | npm | 192.168.200.101 | +| infrastructure | docker | 192.168.200.200 | +| infrastructure | pihole | 192.168.200.100 | + +### Target Hosts + +```bash +# All hosts +ansible-playbook playbooks/site.yml + +# By group +ansible-playbook playbooks/site.yml -l dbservers +ansible-playbook playbooks/site.yml -l webservers +ansible-playbook playbooks/site.yml -l infrastructure + +# Multiple groups +ansible-playbook playbooks/site.yml -l "dbservers:webservers" + +# Single host +ansible-playbook playbooks/site.yml -l postgres +``` + +## Common Role + +The `common` role applies baseline configuration to all hosts. + +### Features + +- **Packages**: curl, wget, vim, htop, git, unzip, net-tools, tree, jq +- **Timezone**: Configurable (default: UTC) +- **SSH Hardening**: Disable root login, password auth, limit auth tries +- **Firewall**: UFW with configurable allowed ports +- **Auto Updates**: Unattended security upgrades +- **User Management**: Create users with SSH keys and sudo access + +### Configuration + +Override defaults in `group_vars/` or `host_vars/`: + +```yaml +# group_vars/dbservers.yml +common_timezone: "America/New_York" +common_extra_packages: + - postgresql-client + +common_firewall_allowed_ports: + - "22/tcp" + - "5432/tcp" + +common_users: + - name: deploy + groups: ['sudo'] + passwordless_sudo: true + ssh_keys: + - "ssh-ed25519 AAAA..." +``` + +### Available Tags + +| Tag | Description | +|-----------|--------------------------------| +| packages | Install common packages | +| timezone | Set timezone and locale | +| users | Create users and SSH keys | +| ssh | SSH daemon hardening | +| security | Firewall, sysctl, auto-updates | + +## Ad-hoc Commands + +```bash +# Run command on all hosts +ansible all -a "uptime" + +# Run command on group +ansible dbservers -a "df -h" + +# Run with sudo +ansible all -b -a "apt update" + +# Copy file +ansible all -m copy -a "src=/local/file dest=/remote/file" + +# Install package +ansible dbservers -b -m apt -a "name=htop state=present" + +# Restart service +ansible webservers -b -m service -a "name=nginx state=restarted" +``` + +## Directory Structure + +``` +dlx-ansible/ +├── ansible.cfg # Ansible configuration +├── inventory/ +│ └── hosts.yml # Host inventory +├── playbooks/ +│ ├── site.yml # Main playbook +│ └── ping.yml # Connectivity test +├── roles/ +│ └── common/ # Common baseline role +├── group_vars/ +│ └── all.yml # Variables for all hosts +├── host_vars/ # Per-host variables +├── files/ # Static files +├── templates/ # Jinja2 templates +└── scripts/ + └── setup-ssh.sh # SSH setup script +``` diff --git a/ansible.cfg b/ansible.cfg index c6423da..33c2b48 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -1,6 +1,7 @@ [defaults] inventory = inventory/hosts.yml roles_path = roles +remote_user = dlxadmin host_key_checking = False retry_files_enabled = False stdout_callback = yaml diff --git a/group_vars/all.yml b/group_vars/all.yml index b42e21b..8ac11c6 100644 --- a/group_vars/all.yml +++ b/group_vars/all.yml @@ -1,4 +1,3 @@ --- # Variables applied to all hosts -# ansible_user: your_ssh_user -# ansible_become: true +ansible_user: dlxadmin diff --git a/inventory/hosts.yml b/inventory/hosts.yml index cc0eb0a..fe2c938 100644 --- a/inventory/hosts.yml +++ b/inventory/hosts.yml @@ -20,7 +20,7 @@ all: nginx: ansible_host: 192.168.200.65 npm: - ansible_host: 192.168.200.101 + ansible_host: 192.168.200.71 infrastructure: hosts: @@ -28,8 +28,22 @@ all: ansible_host: 192.168.200.200 pihole: ansible_host: 192.168.200.100 + gitea: + ansible_host: 192.168.200.102 + jenkins: + ansible_host: 192.168.200.91 + + application: + hosts: + hiveops: + ansible_host: 192.168.200.112 + smartjournal: + ansible_host: 192.168.200.114 + odoo: + ansible_host: 192.168.200.61 local: hosts: localhost: ansible_connection: local + ansible_become: false diff --git a/playbooks/site.yml b/playbooks/site.yml index 08f918c..e8d6b75 100644 --- a/playbooks/site.yml +++ b/playbooks/site.yml @@ -2,8 +2,8 @@ # Main site playbook - orchestrates all plays - name: Apply common configuration hosts: all - roles: [] - # - common + roles: + - common # - name: Configure web servers # hosts: webservers diff --git a/roles/.gitkeep b/roles/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/roles/common/defaults/main.yml b/roles/common/defaults/main.yml new file mode 100644 index 0000000..fcef45a --- /dev/null +++ b/roles/common/defaults/main.yml @@ -0,0 +1,53 @@ +--- +# Common role defaults + +# Timezone +common_timezone: "UTC" + +# Locale +common_locale: "en_US.UTF-8" + +# Packages to install on all hosts +common_packages: + - curl + - wget + - vim + - htop + - git + - unzip + - net-tools + - tree + - jq + +# Additional packages (override per host/group) +common_extra_packages: [] + +# SSH configuration +common_ssh_port: 22 +common_ssh_permit_root_login: "no" +common_ssh_password_authentication: "no" +common_ssh_pubkey_authentication: "yes" + +# Firewall (ufw for Debian/Ubuntu, firewalld for RHEL) +common_firewall_enabled: true +common_firewall_allowed_ports: + - "22/tcp" + +# Automatic updates +common_auto_updates_enabled: true + +# Users to create (override in group_vars/host_vars) +common_users: [] +# Example: +# common_users: +# - name: deploy +# groups: ['sudo'] +# shell: /bin/bash +# ssh_keys: +# - "ssh-ed25519 AAAA..." + +# Sysctl settings +common_sysctl_settings: + net.ipv4.ip_forward: 0 + net.ipv4.conf.all.send_redirects: 0 + net.ipv4.conf.default.send_redirects: 0 diff --git a/roles/common/handlers/main.yml b/roles/common/handlers/main.yml new file mode 100644 index 0000000..221519c --- /dev/null +++ b/roles/common/handlers/main.yml @@ -0,0 +1,7 @@ +--- +# Common role handlers + +- name: Restart sshd + ansible.builtin.service: + name: "{{ 'ssh' if ansible_os_family == 'Debian' else 'sshd' }}" + state: restarted diff --git a/roles/common/tasks/main.yml b/roles/common/tasks/main.yml new file mode 100644 index 0000000..d415aa3 --- /dev/null +++ b/roles/common/tasks/main.yml @@ -0,0 +1,32 @@ +--- +# Common role - main tasks + +- name: Include OS-specific variables + ansible.builtin.include_vars: "{{ item }}" + with_first_found: + - "{{ ansible_distribution | lower }}-{{ ansible_distribution_major_version }}.yml" + - "{{ ansible_distribution | lower }}.yml" + - "{{ ansible_os_family | lower }}.yml" + - "default.yml" + ignore_errors: true + +- name: Run package tasks + ansible.builtin.include_tasks: packages.yml + tags: [packages] + +- name: Run timezone tasks + ansible.builtin.include_tasks: timezone.yml + tags: [timezone] + +- name: Run user tasks + ansible.builtin.include_tasks: users.yml + tags: [users] + when: common_users | length > 0 + +- name: Run SSH hardening tasks + ansible.builtin.include_tasks: ssh.yml + tags: [ssh, security] + +- name: Run security tasks + ansible.builtin.include_tasks: security.yml + tags: [security] diff --git a/roles/common/tasks/packages.yml b/roles/common/tasks/packages.yml new file mode 100644 index 0000000..2b2e1be --- /dev/null +++ b/roles/common/tasks/packages.yml @@ -0,0 +1,26 @@ +--- +# Package installation tasks + +- name: Update apt cache (Debian/Ubuntu) + ansible.builtin.apt: + update_cache: true + cache_valid_time: 3600 + when: ansible_os_family == "Debian" + +- name: Install common packages (Debian/Ubuntu) + ansible.builtin.apt: + name: "{{ common_packages + common_extra_packages }}" + state: present + when: ansible_os_family == "Debian" + +- name: Install common packages (RedHat/CentOS) + ansible.builtin.dnf: + name: "{{ common_packages + common_extra_packages }}" + state: present + when: ansible_os_family == "RedHat" + +- name: Install common packages (Arch) + community.general.pacman: + name: "{{ common_packages + common_extra_packages }}" + state: present + when: ansible_os_family == "Archlinux" diff --git a/roles/common/tasks/security.yml b/roles/common/tasks/security.yml new file mode 100644 index 0000000..10815e6 --- /dev/null +++ b/roles/common/tasks/security.yml @@ -0,0 +1,66 @@ +--- +# Security hardening tasks + +- name: Apply sysctl settings + ansible.posix.sysctl: + name: "{{ item.key }}" + value: "{{ item.value }}" + sysctl_set: true + state: present + reload: true + loop: "{{ common_sysctl_settings | dict2items }}" + +- name: Install UFW (Debian/Ubuntu) + ansible.builtin.apt: + name: ufw + state: present + when: + - ansible_os_family == "Debian" + - common_firewall_enabled + +- name: Configure UFW defaults + community.general.ufw: + direction: "{{ item.direction }}" + policy: "{{ item.policy }}" + loop: + - { direction: 'incoming', policy: 'deny' } + - { direction: 'outgoing', policy: 'allow' } + when: + - ansible_os_family == "Debian" + - common_firewall_enabled + +- name: Allow firewall ports (UFW) + community.general.ufw: + rule: allow + port: "{{ item.split('/')[0] }}" + proto: "{{ item.split('/')[1] | default('tcp') }}" + loop: "{{ common_firewall_allowed_ports }}" + when: + - ansible_os_family == "Debian" + - common_firewall_enabled + +- name: Enable UFW + community.general.ufw: + state: enabled + when: + - ansible_os_family == "Debian" + - common_firewall_enabled + +- name: Install automatic security updates (Debian/Ubuntu) + ansible.builtin.apt: + name: unattended-upgrades + state: present + when: + - ansible_os_family == "Debian" + - common_auto_updates_enabled + +- name: Enable automatic security updates (Debian/Ubuntu) + ansible.builtin.copy: + dest: /etc/apt/apt.conf.d/20auto-upgrades + content: | + APT::Periodic::Update-Package-Lists "1"; + APT::Periodic::Unattended-Upgrade "1"; + mode: '0644' + when: + - ansible_os_family == "Debian" + - common_auto_updates_enabled diff --git a/roles/common/tasks/ssh.yml b/roles/common/tasks/ssh.yml new file mode 100644 index 0000000..624d997 --- /dev/null +++ b/roles/common/tasks/ssh.yml @@ -0,0 +1,28 @@ +--- +# SSH hardening tasks + +- name: Configure SSH daemon + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + regexp: "{{ item.regexp }}" + line: "{{ item.line }}" + state: present + validate: 'sshd -t -f %s' + loop: + - regexp: "^#?Port" + line: "Port {{ common_ssh_port }}" + - regexp: "^#?PermitRootLogin" + line: "PermitRootLogin {{ common_ssh_permit_root_login }}" + - regexp: "^#?PasswordAuthentication" + line: "PasswordAuthentication {{ common_ssh_password_authentication }}" + - regexp: "^#?PubkeyAuthentication" + line: "PubkeyAuthentication {{ common_ssh_pubkey_authentication }}" + - regexp: "^#?X11Forwarding" + line: "X11Forwarding no" + - regexp: "^#?MaxAuthTries" + line: "MaxAuthTries 3" + - regexp: "^#?ClientAliveInterval" + line: "ClientAliveInterval 300" + - regexp: "^#?ClientAliveCountMax" + line: "ClientAliveCountMax 2" + notify: Restart sshd diff --git a/roles/common/tasks/timezone.yml b/roles/common/tasks/timezone.yml new file mode 100644 index 0000000..5681f0b --- /dev/null +++ b/roles/common/tasks/timezone.yml @@ -0,0 +1,21 @@ +--- +# Timezone and locale configuration + +- name: Set timezone + ansible.builtin.timezone: + name: "{{ common_timezone }}" + +- name: Generate locale (Debian/Ubuntu) + community.general.locale_gen: + name: "{{ common_locale }}" + state: present + when: ansible_os_family == "Debian" + +- name: Set default locale + ansible.builtin.lineinfile: + path: /etc/default/locale + regexp: "^LANG=" + line: "LANG={{ common_locale }}" + create: true + mode: '0644' + when: ansible_os_family == "Debian" diff --git a/roles/common/tasks/users.yml b/roles/common/tasks/users.yml new file mode 100644 index 0000000..cd985eb --- /dev/null +++ b/roles/common/tasks/users.yml @@ -0,0 +1,35 @@ +--- +# User management tasks + +- name: Create user groups + ansible.builtin.group: + name: "{{ item.name }}" + state: present + loop: "{{ common_users }}" + when: item.create_group | default(true) + +- name: Create users + ansible.builtin.user: + name: "{{ item.name }}" + groups: "{{ item.groups | default([]) }}" + shell: "{{ item.shell | default('/bin/bash') }}" + create_home: true + state: present + loop: "{{ common_users }}" + +- name: Set authorized keys for users + ansible.posix.authorized_key: + user: "{{ item.0.name }}" + key: "{{ item.1 }}" + state: present + loop: "{{ common_users | subelements('ssh_keys', skip_missing=True) }}" + +- name: Configure passwordless sudo for users + ansible.builtin.lineinfile: + path: "/etc/sudoers.d/{{ item.name }}" + line: "{{ item.name }} ALL=(ALL) NOPASSWD:ALL" + create: true + mode: '0440' + validate: 'visudo -cf %s' + loop: "{{ common_users }}" + when: item.passwordless_sudo | default(false) diff --git a/roles/common/vars/default.yml b/roles/common/vars/default.yml new file mode 100644 index 0000000..204b431 --- /dev/null +++ b/roles/common/vars/default.yml @@ -0,0 +1,2 @@ +--- +# Default variables (fallback) diff --git a/scripts/create-user.sh b/scripts/create-user.sh new file mode 100755 index 0000000..918b26b --- /dev/null +++ b/scripts/create-user.sh @@ -0,0 +1,94 @@ +#!/bin/bash + +# Create a user with sudo privileges +# Usage: ./create-user.sh [username] + +set -e + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +# Default username +USERNAME="${1:-dlxadmin}" + +# Check if running as root +if [ "$EUID" -ne 0 ]; then + echo -e "${RED}Please run as root or with sudo${NC}" + exit 1 +fi + +echo -e "${GREEN}Creating user: ${USERNAME}${NC}" + +# Check if user already exists +if id "$USERNAME" &>/dev/null; then + echo -e "${YELLOW}User ${USERNAME} already exists${NC}" +else + # Create user + useradd -m -s /bin/bash "$USERNAME" + echo -e "${GREEN}User ${USERNAME} created${NC}" +fi + +# Set password +echo -e "${YELLOW}Set password for ${USERNAME}:${NC}" +passwd "$USERNAME" + +# Add to sudo group (Debian/Ubuntu) or wheel (RHEL/CentOS) +if getent group sudo &>/dev/null; then + usermod -aG sudo "$USERNAME" + echo -e "${GREEN}Added ${USERNAME} to sudo group${NC}" +elif getent group wheel &>/dev/null; then + usermod -aG wheel "$USERNAME" + echo -e "${GREEN}Added ${USERNAME} to wheel group${NC}" +else + echo -e "${RED}Neither sudo nor wheel group found${NC}" +fi + +# Configure passwordless sudo +if [ -d /etc/sudoers.d ]; then + echo "$USERNAME ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/"$USERNAME" + chmod 440 /etc/sudoers.d/"$USERNAME" + echo -e "${GREEN}Configured passwordless sudo for ${USERNAME}${NC}" +else + # Fallback: add to sudoers file directly + if ! grep -q "^$USERNAME" /etc/sudoers; then + echo "$USERNAME ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers + echo -e "${GREEN}Added ${USERNAME} to /etc/sudoers${NC}" + fi +fi + +# Check and install SSH server +if ! command -v sshd &>/dev/null && ! systemctl list-unit-files | grep -q sshd; then + echo -e "${YELLOW}SSH server not found. Installing...${NC}" + if command -v apt-get &>/dev/null; then + apt-get update && apt-get install -y openssh-server + elif command -v dnf &>/dev/null; then + dnf install -y openssh-server + elif command -v yum &>/dev/null; then + yum install -y openssh-server + elif command -v pacman &>/dev/null; then + pacman -Sy --noconfirm openssh + else + echo -e "${RED}Could not install SSH server. Please install manually.${NC}" + fi +fi + +# Enable and start SSH service +if command -v systemctl &>/dev/null; then + systemctl enable --now sshd 2>/dev/null || systemctl enable --now ssh 2>/dev/null + echo -e "${GREEN}SSH service enabled and started${NC}" +fi + +# Setup .ssh directory +mkdir -p /home/"$USERNAME"/.ssh +chmod 700 /home/"$USERNAME"/.ssh +touch /home/"$USERNAME"/.ssh/authorized_keys +chmod 600 /home/"$USERNAME"/.ssh/authorized_keys +chown -R "$USERNAME":"$USERNAME" /home/"$USERNAME"/.ssh +echo -e "${GREEN}Created .ssh directory${NC}" + +echo -e "${GREEN}✓ User ${USERNAME} setup complete!${NC}" +echo -e "${YELLOW}Now run from your ansible workstation:${NC}" +echo -e " ./scripts/setup-ssh.sh ${USERNAME}"