- Add deploy-debian12-vm.yml for basic Debian 12 deployment - Add deploy-linux-vm.yml for multi-distribution support - Support for Debian, Ubuntu, RHEL, CentOS, Rocky, Alma, SUSE - Cloud-init based provisioning - Distribution-specific security hardening - Automatic security updates configuration - UFW/firewalld setup per OS family - SELinux enforcing for RHEL family
977 lines
31 KiB
YAML
977 lines
31 KiB
YAML
---
|
|
# =============================================================================
|
|
# Multi-Distribution Linux VM Deployment Playbook
|
|
# =============================================================================
|
|
# Deploys Linux VMs on KVM hypervisor with support for:
|
|
# - Debian (11, 12)
|
|
# - Ubuntu (20.04 LTS, 22.04 LTS, 24.04 LTS)
|
|
# - RHEL (8, 9)
|
|
# - CentOS Stream (8, 9)
|
|
# - Rocky Linux (8, 9)
|
|
# - AlmaLinux (8, 9)
|
|
# - SLES (15)
|
|
# - openSUSE Leap (15)
|
|
#
|
|
# Uses libvirt/KVM with cloud-init for unattended configuration
|
|
# =============================================================================
|
|
|
|
- name: Deploy Linux VM on KVM hypervisor
|
|
hosts: grokbox
|
|
gather_facts: yes
|
|
become: yes
|
|
|
|
vars:
|
|
# VM Configuration
|
|
vm_name: "linux-guest"
|
|
vm_hostname: "linux-vm"
|
|
vm_domain: "localdomain"
|
|
vm_vcpus: 2
|
|
vm_memory_mb: 2048
|
|
vm_disk_size_gb: 20
|
|
|
|
# Distribution Selection (REQUIRED - set via -e flag)
|
|
# Format: "distro-version" or "distro-major.minor"
|
|
# Examples: debian-12, ubuntu-22.04, rhel-9, centos-stream-9, sles-15
|
|
os_distribution: "debian-12"
|
|
|
|
# Network Configuration
|
|
vm_network: "default"
|
|
vm_bridge: "virbr0"
|
|
|
|
# Storage Configuration
|
|
vm_disk_path: "/var/lib/libvirt/images/{{ vm_name }}.qcow2"
|
|
cloud_init_iso_path: "/var/lib/libvirt/images/{{ vm_name }}-cloud-init.iso"
|
|
|
|
# Ansible User Configuration
|
|
ansible_user_ssh_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILBrnivsqjhAxWYeuuvnYc3neeRRuHsr2SjeKv+Drtpu user@debian"
|
|
|
|
# ==========================================================================
|
|
# Cloud Image Repository Configuration
|
|
# ==========================================================================
|
|
cloud_images:
|
|
# Debian
|
|
debian-11:
|
|
url: "https://cloud.debian.org/images/cloud/bullseye/latest/debian-11-generic-amd64.qcow2"
|
|
checksum_url: "https://cloud.debian.org/images/cloud/bullseye/latest/SHA512SUMS"
|
|
checksum_type: "sha512"
|
|
os_variant: "debian11"
|
|
cache_name: "debian-11-generic-amd64.qcow2"
|
|
package_manager: "apt"
|
|
family: "debian"
|
|
|
|
debian-12:
|
|
url: "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2"
|
|
checksum_url: "https://cloud.debian.org/images/cloud/bookworm/latest/SHA512SUMS"
|
|
checksum_type: "sha512"
|
|
os_variant: "debian12"
|
|
cache_name: "debian-12-generic-amd64.qcow2"
|
|
package_manager: "apt"
|
|
family: "debian"
|
|
|
|
# Ubuntu
|
|
ubuntu-20.04:
|
|
url: "https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img"
|
|
checksum_url: "https://cloud-images.ubuntu.com/focal/current/SHA256SUMS"
|
|
checksum_type: "sha256"
|
|
os_variant: "ubuntu20.04"
|
|
cache_name: "ubuntu-20.04-server-cloudimg-amd64.img"
|
|
package_manager: "apt"
|
|
family: "debian"
|
|
|
|
ubuntu-22.04:
|
|
url: "https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img"
|
|
checksum_url: "https://cloud-images.ubuntu.com/jammy/current/SHA256SUMS"
|
|
checksum_type: "sha256"
|
|
os_variant: "ubuntu22.04"
|
|
cache_name: "ubuntu-22.04-server-cloudimg-amd64.img"
|
|
package_manager: "apt"
|
|
family: "debian"
|
|
|
|
ubuntu-24.04:
|
|
url: "https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img"
|
|
checksum_url: "https://cloud-images.ubuntu.com/noble/current/SHA256SUMS"
|
|
checksum_type: "sha256"
|
|
os_variant: "ubuntu24.04"
|
|
cache_name: "ubuntu-24.04-server-cloudimg-amd64.img"
|
|
package_manager: "apt"
|
|
family: "debian"
|
|
|
|
# RHEL (requires subscription)
|
|
rhel-8:
|
|
url: "https://access.redhat.com/downloads/content/rhel/8/x86_64/latest/rhel-8-x86_64-kvm.qcow2"
|
|
os_variant: "rhel8.0"
|
|
cache_name: "rhel-8-x86_64-kvm.qcow2"
|
|
package_manager: "dnf"
|
|
family: "rhel"
|
|
note: "Requires Red Hat subscription and manual download"
|
|
|
|
rhel-9:
|
|
url: "https://access.redhat.com/downloads/content/rhel/9/x86_64/latest/rhel-9-x86_64-kvm.qcow2"
|
|
os_variant: "rhel9.0"
|
|
cache_name: "rhel-9-x86_64-kvm.qcow2"
|
|
package_manager: "dnf"
|
|
family: "rhel"
|
|
note: "Requires Red Hat subscription and manual download"
|
|
|
|
# CentOS Stream
|
|
centos-stream-8:
|
|
url: "https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-latest.x86_64.qcow2"
|
|
checksum_url: "https://cloud.centos.org/centos/8-stream/x86_64/images/CHECKSUM"
|
|
checksum_type: "sha256"
|
|
os_variant: "centos-stream8"
|
|
cache_name: "centos-stream-8-genericcloud-amd64.qcow2"
|
|
package_manager: "dnf"
|
|
family: "rhel"
|
|
|
|
centos-stream-9:
|
|
url: "https://cloud.centos.org/centos/9-stream/x86_64/images/CentOS-Stream-GenericCloud-9-latest.x86_64.qcow2"
|
|
checksum_url: "https://cloud.centos.org/centos/9-stream/x86_64/images/CHECKSUM"
|
|
checksum_type: "sha256"
|
|
os_variant: "centos-stream9"
|
|
cache_name: "centos-stream-9-genericcloud-amd64.qcow2"
|
|
package_manager: "dnf"
|
|
family: "rhel"
|
|
|
|
# Rocky Linux
|
|
rocky-8:
|
|
url: "https://download.rockylinux.org/pub/rocky/8/images/x86_64/Rocky-8-GenericCloud-Base.latest.x86_64.qcow2"
|
|
checksum_url: "https://download.rockylinux.org/pub/rocky/8/images/x86_64/CHECKSUM"
|
|
checksum_type: "sha256"
|
|
os_variant: "rocky8"
|
|
cache_name: "rocky-8-genericcloud-amd64.qcow2"
|
|
package_manager: "dnf"
|
|
family: "rhel"
|
|
|
|
rocky-9:
|
|
url: "https://download.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2"
|
|
checksum_url: "https://download.rockylinux.org/pub/rocky/9/images/x86_64/CHECKSUM"
|
|
checksum_type: "sha256"
|
|
os_variant: "rocky9"
|
|
cache_name: "rocky-9-genericcloud-amd64.qcow2"
|
|
package_manager: "dnf"
|
|
family: "rhel"
|
|
|
|
# AlmaLinux
|
|
almalinux-8:
|
|
url: "https://repo.almalinux.org/almalinux/8/cloud/x86_64/images/AlmaLinux-8-GenericCloud-latest.x86_64.qcow2"
|
|
checksum_url: "https://repo.almalinux.org/almalinux/8/cloud/x86_64/images/CHECKSUM"
|
|
checksum_type: "sha256"
|
|
os_variant: "almalinux8"
|
|
cache_name: "almalinux-8-genericcloud-amd64.qcow2"
|
|
package_manager: "dnf"
|
|
family: "rhel"
|
|
|
|
almalinux-9:
|
|
url: "https://repo.almalinux.org/almalinux/9/cloud/x86_64/images/AlmaLinux-9-GenericCloud-latest.x86_64.qcow2"
|
|
checksum_url: "https://repo.almalinux.org/almalinux/9/cloud/x86_64/images/CHECKSUM"
|
|
checksum_type: "sha256"
|
|
os_variant: "almalinux9"
|
|
cache_name: "almalinux-9-genericcloud-amd64.qcow2"
|
|
package_manager: "dnf"
|
|
family: "rhel"
|
|
|
|
# SLES (requires registration)
|
|
sles-15:
|
|
url: "https://download.suse.com/Download?buildid=XXXXX"
|
|
os_variant: "sles15"
|
|
cache_name: "sles-15-genericcloud-amd64.qcow2"
|
|
package_manager: "zypper"
|
|
family: "suse"
|
|
note: "Requires SUSE subscription and manual download"
|
|
|
|
# openSUSE Leap
|
|
opensuse-leap-15.5:
|
|
url: "https://download.opensuse.org/distribution/leap/15.5/appliances/openSUSE-Leap-15.5-Minimal-VM.x86_64-Cloud.qcow2"
|
|
checksum_url: "https://download.opensuse.org/distribution/leap/15.5/appliances/openSUSE-Leap-15.5-Minimal-VM.x86_64-Cloud.qcow2.sha256"
|
|
checksum_type: "sha256"
|
|
os_variant: "opensuse15.5"
|
|
cache_name: "opensuse-leap-15.5-minimal-vm-amd64.qcow2"
|
|
package_manager: "zypper"
|
|
family: "suse"
|
|
|
|
opensuse-leap-15.6:
|
|
url: "https://download.opensuse.org/distribution/leap/15.6/appliances/openSUSE-Leap-15.6-Minimal-VM.x86_64-Cloud.qcow2"
|
|
checksum_url: "https://download.opensuse.org/distribution/leap/15.6/appliances/openSUSE-Leap-15.6-Minimal-VM.x86_64-Cloud.qcow2.sha256"
|
|
checksum_type: "sha256"
|
|
os_variant: "opensuse15.6"
|
|
cache_name: "opensuse-leap-15.6-minimal-vm-amd64.qcow2"
|
|
package_manager: "zypper"
|
|
family: "suse"
|
|
|
|
tasks:
|
|
# =========================================================================
|
|
# Validation and Setup
|
|
# =========================================================================
|
|
|
|
- name: Validate distribution selection
|
|
assert:
|
|
that:
|
|
- os_distribution is defined
|
|
- os_distribution in cloud_images.keys()
|
|
fail_msg: |
|
|
Invalid distribution '{{ os_distribution }}'.
|
|
Supported distributions: {{ cloud_images.keys() | list | join(', ') }}
|
|
tags: [validate, preflight]
|
|
|
|
- name: Set distribution facts
|
|
set_fact:
|
|
distro_config: "{{ cloud_images[os_distribution] }}"
|
|
image_cache_path: "/var/lib/libvirt/images/{{ cloud_images[os_distribution].cache_name }}"
|
|
tags: [always]
|
|
|
|
- name: Display deployment information
|
|
debug:
|
|
msg:
|
|
- "=== VM Deployment Configuration ==="
|
|
- "VM Name: {{ vm_name }}"
|
|
- "Distribution: {{ os_distribution }}"
|
|
- "OS Family: {{ distro_config.family }}"
|
|
- "Package Manager: {{ distro_config.package_manager }}"
|
|
- "vCPUs: {{ vm_vcpus }}"
|
|
- "Memory: {{ vm_memory_mb }} MB"
|
|
- "Disk: {{ vm_disk_size_gb }} GB"
|
|
tags: [validate, preflight]
|
|
|
|
- name: Check if VM already exists
|
|
command: virsh dominfo {{ vm_name }}
|
|
register: vm_exists
|
|
failed_when: false
|
|
changed_when: false
|
|
tags: [validate, preflight]
|
|
|
|
- name: Fail if VM already exists
|
|
fail:
|
|
msg: "VM '{{ vm_name }}' already exists on hypervisor. Please choose a different name or destroy the existing VM."
|
|
when: vm_exists.rc == 0
|
|
tags: [validate, preflight]
|
|
|
|
- name: Verify virtualization support
|
|
command: virt-host-validate qemu
|
|
register: virt_validation
|
|
failed_when: false
|
|
changed_when: false
|
|
tags: [validate, preflight]
|
|
|
|
- name: Display virtualization validation results
|
|
debug:
|
|
var: virt_validation.stdout_lines
|
|
tags: [validate, preflight]
|
|
|
|
# =========================================================================
|
|
# Package Installation
|
|
# =========================================================================
|
|
|
|
- name: Install required packages for VM deployment (Debian/Ubuntu)
|
|
apt:
|
|
name:
|
|
- libvirt-daemon-system
|
|
- libvirt-clients
|
|
- virtinst
|
|
- qemu-kvm
|
|
- qemu-utils
|
|
- cloud-image-utils
|
|
- genisoimage
|
|
- wget
|
|
- curl
|
|
- python3-libvirt
|
|
state: present
|
|
update_cache: yes
|
|
when: ansible_os_family == "Debian"
|
|
tags: [install]
|
|
|
|
- name: Install required packages for VM deployment (RHEL/CentOS)
|
|
dnf:
|
|
name:
|
|
- libvirt
|
|
- libvirt-client
|
|
- virt-install
|
|
- qemu-kvm
|
|
- qemu-img
|
|
- cloud-utils
|
|
- genisoimage
|
|
- wget
|
|
- curl
|
|
- python3-libvirt
|
|
state: present
|
|
when: ansible_os_family == "RedHat"
|
|
tags: [install]
|
|
|
|
- name: Ensure libvirtd service is running
|
|
systemd:
|
|
name: libvirtd
|
|
state: started
|
|
enabled: yes
|
|
tags: [install]
|
|
|
|
# =========================================================================
|
|
# Download Cloud Image
|
|
# =========================================================================
|
|
|
|
- name: Check if cloud image already exists
|
|
stat:
|
|
path: "{{ image_cache_path }}"
|
|
register: cloud_image_stat
|
|
tags: [download]
|
|
|
|
- name: Display image cache status
|
|
debug:
|
|
msg: "Cloud image {{ 'exists' if cloud_image_stat.stat.exists else 'not found' }}: {{ image_cache_path }}"
|
|
tags: [download]
|
|
|
|
- name: Check for manual download requirement
|
|
debug:
|
|
msg:
|
|
- "WARNING: {{ os_distribution }} requires manual download"
|
|
- "{{ distro_config.note | default('') }}"
|
|
- "Please download the image and place it at: {{ image_cache_path }}"
|
|
when:
|
|
- not cloud_image_stat.stat.exists
|
|
- distro_config.note is defined
|
|
tags: [download]
|
|
|
|
- name: Download cloud image
|
|
get_url:
|
|
url: "{{ distro_config.url }}"
|
|
dest: "{{ image_cache_path }}"
|
|
mode: '0644'
|
|
timeout: 1200
|
|
when:
|
|
- not cloud_image_stat.stat.exists
|
|
- distro_config.note is not defined
|
|
register: download_result
|
|
tags: [download]
|
|
|
|
- name: Download checksum file
|
|
get_url:
|
|
url: "{{ distro_config.checksum_url }}"
|
|
dest: "/tmp/{{ os_distribution }}-CHECKSUM"
|
|
mode: '0644'
|
|
when:
|
|
- distro_config.checksum_url is defined
|
|
- download_result is changed or cloud_image_stat.stat.exists
|
|
tags: [download, verify]
|
|
|
|
- name: Verify cloud image checksum (SHA512)
|
|
shell: |
|
|
cd /var/lib/libvirt/images
|
|
grep "{{ distro_config.cache_name }}" /tmp/{{ os_distribution }}-CHECKSUM | sha512sum -c -
|
|
register: checksum_result
|
|
changed_when: false
|
|
when:
|
|
- distro_config.checksum_type is defined
|
|
- distro_config.checksum_type == "sha512"
|
|
- distro_config.checksum_url is defined
|
|
tags: [verify]
|
|
|
|
- name: Verify cloud image checksum (SHA256)
|
|
shell: |
|
|
cd /var/lib/libvirt/images
|
|
grep "{{ distro_config.cache_name }}" /tmp/{{ os_distribution }}-CHECKSUM | sha256sum -c -
|
|
register: checksum_result
|
|
changed_when: false
|
|
when:
|
|
- distro_config.checksum_type is defined
|
|
- distro_config.checksum_type == "sha256"
|
|
- distro_config.checksum_url is defined
|
|
tags: [verify]
|
|
|
|
- name: Ensure image file exists before proceeding
|
|
stat:
|
|
path: "{{ image_cache_path }}"
|
|
register: final_image_check
|
|
failed_when: not final_image_check.stat.exists
|
|
tags: [verify]
|
|
|
|
# =========================================================================
|
|
# Create VM Disk
|
|
# =========================================================================
|
|
|
|
- name: Create VM disk from cloud image
|
|
command: >
|
|
qemu-img create -f qcow2 -F qcow2
|
|
-b {{ image_cache_path }}
|
|
{{ vm_disk_path }}
|
|
{{ vm_disk_size_gb }}G
|
|
args:
|
|
creates: "{{ vm_disk_path }}"
|
|
tags: [storage]
|
|
|
|
- name: Set proper permissions on VM disk
|
|
file:
|
|
path: "{{ vm_disk_path }}"
|
|
owner: libvirt-qemu
|
|
group: kvm
|
|
mode: '0600'
|
|
when: ansible_os_family == "Debian"
|
|
tags: [storage]
|
|
|
|
- name: Set proper permissions on VM disk (RHEL)
|
|
file:
|
|
path: "{{ vm_disk_path }}"
|
|
owner: qemu
|
|
group: qemu
|
|
mode: '0600'
|
|
when: ansible_os_family == "RedHat"
|
|
tags: [storage]
|
|
|
|
# =========================================================================
|
|
# Create Cloud-Init Configuration
|
|
# =========================================================================
|
|
|
|
- name: Create cloud-init directory
|
|
file:
|
|
path: /tmp/cloud-init-{{ vm_name }}
|
|
state: directory
|
|
mode: '0755'
|
|
tags: [cloud-init]
|
|
|
|
- name: Create cloud-init meta-data
|
|
copy:
|
|
content: |
|
|
instance-id: {{ vm_name }}
|
|
local-hostname: {{ vm_hostname }}
|
|
dest: /tmp/cloud-init-{{ vm_name }}/meta-data
|
|
mode: '0644'
|
|
tags: [cloud-init]
|
|
|
|
- name: Create cloud-init user-data for Debian/Ubuntu
|
|
copy:
|
|
content: |
|
|
#cloud-config
|
|
hostname: {{ vm_hostname }}
|
|
fqdn: {{ vm_hostname }}.{{ vm_domain }}
|
|
manage_etc_hosts: true
|
|
|
|
# Create ansible user with sudo privileges
|
|
users:
|
|
- name: ansible
|
|
groups: sudo
|
|
shell: /bin/bash
|
|
sudo: ['ALL=(ALL) NOPASSWD:ALL']
|
|
ssh_authorized_keys:
|
|
- {{ ansible_user_ssh_key }}
|
|
- name: root
|
|
lock_passwd: false
|
|
|
|
# Set root password (for emergency console access)
|
|
chpasswd:
|
|
list: |
|
|
root:ChangeMe123!
|
|
expire: false
|
|
|
|
# SSH configuration
|
|
ssh_pwauth: false
|
|
disable_root: false
|
|
|
|
# Install essential packages per CLAUDE.md guidelines
|
|
packages:
|
|
- sudo
|
|
- vim
|
|
- htop
|
|
- tmux
|
|
- curl
|
|
- wget
|
|
- rsync
|
|
- git
|
|
- python3
|
|
- python3-pip
|
|
- jq
|
|
- bc
|
|
- aide
|
|
- auditd
|
|
- chrony
|
|
- ufw
|
|
- lvm2
|
|
- cloud-guest-utils
|
|
- parted
|
|
- unattended-upgrades
|
|
- apt-listchanges
|
|
|
|
# Security configuration files
|
|
write_files:
|
|
- path: /etc/ssh/sshd_config.d/99-security.conf
|
|
content: |
|
|
PermitRootLogin no
|
|
PasswordAuthentication no
|
|
PubkeyAuthentication yes
|
|
MaxAuthTries 3
|
|
MaxSessions 10
|
|
ClientAliveInterval 300
|
|
ClientAliveCountMax 2
|
|
permissions: '0644'
|
|
|
|
- path: /etc/sudoers.d/ansible
|
|
content: |
|
|
ansible ALL=(ALL) NOPASSWD:ALL
|
|
permissions: '0440'
|
|
|
|
- path: /etc/apt/apt.conf.d/50unattended-upgrades
|
|
content: |
|
|
Unattended-Upgrade::Allowed-Origins {
|
|
"${distro_id}:${distro_codename}-security";
|
|
};
|
|
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
|
|
Unattended-Upgrade::MinimalSteps "true";
|
|
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
|
|
Unattended-Upgrade::Remove-Unused-Dependencies "true";
|
|
Unattended-Upgrade::Automatic-Reboot "false";
|
|
permissions: '0644'
|
|
|
|
- path: /etc/apt/apt.conf.d/20auto-upgrades
|
|
content: |
|
|
APT::Periodic::Update-Package-Lists "1";
|
|
APT::Periodic::Unattended-Upgrade "1";
|
|
APT::Periodic::AutocleanInterval "7";
|
|
permissions: '0644'
|
|
|
|
# System configuration commands
|
|
runcmd:
|
|
- systemctl enable ssh
|
|
- systemctl restart ssh
|
|
- systemctl enable chrony
|
|
- systemctl start chrony
|
|
- ufw --force enable
|
|
- ufw allow ssh
|
|
- systemctl enable auditd
|
|
- systemctl start auditd
|
|
- growpart /dev/vda 1 || true
|
|
- resize2fs /dev/vda1 || true
|
|
|
|
package_update: true
|
|
package_upgrade: true
|
|
package_reboot_if_required: false
|
|
|
|
timezone: UTC
|
|
locale: en_US.UTF-8
|
|
|
|
output:
|
|
all: '| tee -a /var/log/cloud-init-output.log'
|
|
|
|
final_message: "{{ os_distribution }} VM deployment completed. System is ready after $UPTIME seconds."
|
|
dest: /tmp/cloud-init-{{ vm_name }}/user-data
|
|
mode: '0644'
|
|
when: distro_config.family == "debian"
|
|
tags: [cloud-init]
|
|
|
|
- name: Create cloud-init user-data for RHEL/CentOS/Rocky/Alma
|
|
copy:
|
|
content: |
|
|
#cloud-config
|
|
hostname: {{ vm_hostname }}
|
|
fqdn: {{ vm_hostname }}.{{ vm_domain }}
|
|
manage_etc_hosts: true
|
|
|
|
# Create ansible user with sudo privileges
|
|
users:
|
|
- name: ansible
|
|
groups: wheel
|
|
shell: /bin/bash
|
|
sudo: ['ALL=(ALL) NOPASSWD:ALL']
|
|
ssh_authorized_keys:
|
|
- {{ ansible_user_ssh_key }}
|
|
- name: root
|
|
lock_passwd: false
|
|
|
|
# Set root password (for emergency console access)
|
|
chpasswd:
|
|
list: |
|
|
root:ChangeMe123!
|
|
expire: false
|
|
|
|
# SSH configuration
|
|
ssh_pwauth: false
|
|
disable_root: false
|
|
|
|
# Install essential packages per CLAUDE.md guidelines
|
|
packages:
|
|
- sudo
|
|
- vim
|
|
- htop
|
|
- tmux
|
|
- curl
|
|
- wget
|
|
- rsync
|
|
- git
|
|
- python3
|
|
- python3-pip
|
|
- jq
|
|
- bc
|
|
- aide
|
|
- audit
|
|
- chrony
|
|
- firewalld
|
|
- lvm2
|
|
- cloud-utils-growpart
|
|
- gdisk
|
|
- dnf-automatic
|
|
- policycoreutils-python-utils
|
|
|
|
# Security configuration files
|
|
write_files:
|
|
- path: /etc/ssh/sshd_config.d/99-security.conf
|
|
content: |
|
|
PermitRootLogin no
|
|
PasswordAuthentication no
|
|
PubkeyAuthentication yes
|
|
MaxAuthTries 3
|
|
MaxSessions 10
|
|
ClientAliveInterval 300
|
|
ClientAliveCountMax 2
|
|
permissions: '0644'
|
|
|
|
- path: /etc/sudoers.d/ansible
|
|
content: |
|
|
ansible ALL=(ALL) NOPASSWD:ALL
|
|
permissions: '0440'
|
|
|
|
- path: /etc/dnf/automatic.conf
|
|
content: |
|
|
[commands]
|
|
upgrade_type = security
|
|
download_updates = yes
|
|
apply_updates = yes
|
|
|
|
[emitters]
|
|
emit_via = stdio
|
|
|
|
[email]
|
|
email_from = root@{{ vm_hostname }}.{{ vm_domain }}
|
|
|
|
[base]
|
|
debuglevel = 1
|
|
permissions: '0644'
|
|
|
|
# System configuration commands
|
|
runcmd:
|
|
- systemctl enable sshd
|
|
- systemctl restart sshd
|
|
- systemctl enable chronyd
|
|
- systemctl start chronyd
|
|
- systemctl enable firewalld
|
|
- systemctl start firewalld
|
|
- firewall-cmd --permanent --add-service=ssh
|
|
- firewall-cmd --reload
|
|
- systemctl enable auditd
|
|
- systemctl start auditd
|
|
- systemctl enable dnf-automatic.timer
|
|
- systemctl start dnf-automatic.timer
|
|
- setenforce 1
|
|
- sed -i 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config
|
|
- growpart /dev/vda 1 || true
|
|
- xfs_growfs / || resize2fs /dev/vda1 || true
|
|
|
|
package_update: true
|
|
package_upgrade: true
|
|
package_reboot_if_required: false
|
|
|
|
timezone: UTC
|
|
locale: en_US.UTF-8
|
|
|
|
output:
|
|
all: '| tee -a /var/log/cloud-init-output.log'
|
|
|
|
final_message: "{{ os_distribution }} VM deployment completed. System is ready after $UPTIME seconds."
|
|
dest: /tmp/cloud-init-{{ vm_name }}/user-data
|
|
mode: '0644'
|
|
when: distro_config.family == "rhel"
|
|
tags: [cloud-init]
|
|
|
|
- name: Create cloud-init user-data for SUSE/openSUSE
|
|
copy:
|
|
content: |
|
|
#cloud-config
|
|
hostname: {{ vm_hostname }}
|
|
fqdn: {{ vm_hostname }}.{{ vm_domain }}
|
|
manage_etc_hosts: true
|
|
|
|
# Create ansible user with sudo privileges
|
|
users:
|
|
- name: ansible
|
|
groups: wheel
|
|
shell: /bin/bash
|
|
sudo: ['ALL=(ALL) NOPASSWD:ALL']
|
|
ssh_authorized_keys:
|
|
- {{ ansible_user_ssh_key }}
|
|
- name: root
|
|
lock_passwd: false
|
|
|
|
# Set root password (for emergency console access)
|
|
chpasswd:
|
|
list: |
|
|
root:ChangeMe123!
|
|
expire: false
|
|
|
|
# SSH configuration
|
|
ssh_pwauth: false
|
|
disable_root: false
|
|
|
|
# Install essential packages
|
|
packages:
|
|
- sudo
|
|
- vim
|
|
- htop
|
|
- tmux
|
|
- curl
|
|
- wget
|
|
- rsync
|
|
- git
|
|
- python3
|
|
- python3-pip
|
|
- jq
|
|
- bc
|
|
- aide
|
|
- audit
|
|
- chrony
|
|
- firewalld
|
|
- lvm2
|
|
- cloud-utils-growpart
|
|
- gdisk
|
|
|
|
# Security configuration files
|
|
write_files:
|
|
- path: /etc/ssh/sshd_config.d/99-security.conf
|
|
content: |
|
|
PermitRootLogin no
|
|
PasswordAuthentication no
|
|
PubkeyAuthentication yes
|
|
MaxAuthTries 3
|
|
MaxSessions 10
|
|
ClientAliveInterval 300
|
|
ClientAliveCountMax 2
|
|
permissions: '0644'
|
|
|
|
- path: /etc/sudoers.d/ansible
|
|
content: |
|
|
ansible ALL=(ALL) NOPASSWD:ALL
|
|
permissions: '0440'
|
|
|
|
# System configuration commands
|
|
runcmd:
|
|
- systemctl enable sshd
|
|
- systemctl restart sshd
|
|
- systemctl enable chronyd
|
|
- systemctl start chronyd
|
|
- systemctl enable firewalld
|
|
- systemctl start firewalld
|
|
- firewall-cmd --permanent --add-service=ssh
|
|
- firewall-cmd --reload
|
|
- systemctl enable auditd
|
|
- systemctl start auditd
|
|
- growpart /dev/vda 1 || true
|
|
- xfs_growfs / || resize2fs /dev/vda1 || btrfs filesystem resize max / || true
|
|
|
|
package_update: true
|
|
package_upgrade: true
|
|
package_reboot_if_required: false
|
|
|
|
timezone: UTC
|
|
locale: en_US.UTF-8
|
|
|
|
output:
|
|
all: '| tee -a /var/log/cloud-init-output.log'
|
|
|
|
final_message: "{{ os_distribution }} VM deployment completed. System is ready after $UPTIME seconds."
|
|
dest: /tmp/cloud-init-{{ vm_name }}/user-data
|
|
mode: '0644'
|
|
when: distro_config.family == "suse"
|
|
tags: [cloud-init]
|
|
|
|
- name: Create cloud-init ISO
|
|
command: >
|
|
genisoimage -output {{ cloud_init_iso_path }}
|
|
-volid cidata -joliet -rock
|
|
/tmp/cloud-init-{{ vm_name }}/user-data
|
|
/tmp/cloud-init-{{ vm_name }}/meta-data
|
|
args:
|
|
creates: "{{ cloud_init_iso_path }}"
|
|
tags: [cloud-init]
|
|
|
|
- name: Set proper permissions on cloud-init ISO (Debian/Ubuntu)
|
|
file:
|
|
path: "{{ cloud_init_iso_path }}"
|
|
owner: libvirt-qemu
|
|
group: kvm
|
|
mode: '0644'
|
|
when: ansible_os_family == "Debian"
|
|
tags: [cloud-init]
|
|
|
|
- name: Set proper permissions on cloud-init ISO (RHEL)
|
|
file:
|
|
path: "{{ cloud_init_iso_path }}"
|
|
owner: qemu
|
|
group: qemu
|
|
mode: '0644'
|
|
when: ansible_os_family == "RedHat"
|
|
tags: [cloud-init]
|
|
|
|
# =========================================================================
|
|
# Create and Start VM
|
|
# =========================================================================
|
|
|
|
- name: Create VM using virt-install
|
|
command: >
|
|
virt-install
|
|
--name {{ vm_name }}
|
|
--memory {{ vm_memory_mb }}
|
|
--vcpus {{ vm_vcpus }}
|
|
--disk path={{ vm_disk_path }},format=qcow2,bus=virtio
|
|
--disk path={{ cloud_init_iso_path }},device=cdrom
|
|
--network network={{ vm_network }},model=virtio
|
|
--os-variant {{ distro_config.os_variant }}
|
|
--graphics none
|
|
--console pty,target_type=serial
|
|
--import
|
|
--noautoconsole
|
|
register: vm_create
|
|
tags: [deploy]
|
|
|
|
- name: Wait for VM to boot and cloud-init to complete
|
|
pause:
|
|
seconds: 90
|
|
prompt: "Waiting for VM to boot and cloud-init to complete configuration..."
|
|
tags: [deploy]
|
|
|
|
- name: Get VM IP address
|
|
shell: |
|
|
virsh domifaddr {{ vm_name }} | grep -oP '(\d{1,3}\.){3}\d{1,3}' | head -1
|
|
register: vm_ip
|
|
retries: 15
|
|
delay: 10
|
|
until: vm_ip.stdout != ""
|
|
changed_when: false
|
|
tags: [deploy, validate]
|
|
|
|
- name: Display VM information
|
|
debug:
|
|
msg:
|
|
- "=== VM Deployment Successful ==="
|
|
- "VM Name: {{ vm_name }}"
|
|
- "Distribution: {{ os_distribution }}"
|
|
- "IP Address: {{ vm_ip.stdout }}"
|
|
- "vCPUs: {{ vm_vcpus }}"
|
|
- "Memory: {{ vm_memory_mb }} MB"
|
|
- "Disk: {{ vm_disk_size_gb }} GB"
|
|
- "OS Variant: {{ distro_config.os_variant }}"
|
|
- "Package Manager: {{ distro_config.package_manager }}"
|
|
- "Access: ssh ansible@{{ vm_ip.stdout }}"
|
|
- ""
|
|
- "Add to inventory:"
|
|
- " {{ vm_name }}:"
|
|
- " ansible_host: {{ vm_ip.stdout }}"
|
|
- " ansible_user: ansible"
|
|
- " ansible_ssh_common_args: '-o ProxyJump=grokbox -o StrictHostKeyChecking=accept-new'"
|
|
- " os_distribution: {{ os_distribution }}"
|
|
- " os_family: {{ distro_config.family }}"
|
|
tags: [deploy, validate]
|
|
|
|
# =========================================================================
|
|
# Validation
|
|
# =========================================================================
|
|
|
|
- name: Test SSH connectivity to new VM
|
|
wait_for:
|
|
host: "{{ vm_ip.stdout }}"
|
|
port: 22
|
|
timeout: 300
|
|
state: started
|
|
tags: [validate]
|
|
|
|
- name: Get VM details
|
|
command: virsh dominfo {{ vm_name }}
|
|
register: vm_details
|
|
changed_when: false
|
|
tags: [validate]
|
|
|
|
- name: Display VM details
|
|
debug:
|
|
var: vm_details.stdout_lines
|
|
tags: [validate]
|
|
|
|
# =========================================================================
|
|
# Cleanup
|
|
# =========================================================================
|
|
|
|
- name: Remove temporary cloud-init directory
|
|
file:
|
|
path: /tmp/cloud-init-{{ vm_name }}
|
|
state: absent
|
|
tags: [cleanup]
|
|
|
|
- name: Remove downloaded checksums
|
|
file:
|
|
path: /tmp/{{ os_distribution }}-CHECKSUM
|
|
state: absent
|
|
tags: [cleanup]
|
|
|
|
# =============================================================================
|
|
# Post-Deployment Validation (Optional)
|
|
# =============================================================================
|
|
|
|
- name: Validate deployed VM
|
|
hosts: "{{ hostvars['grokbox']['vm_ip'].stdout }}"
|
|
gather_facts: yes
|
|
become: yes
|
|
vars:
|
|
ansible_user: ansible
|
|
ansible_ssh_common_args: '-o ProxyJump=grokbox -o StrictHostKeyChecking=accept-new'
|
|
|
|
tasks:
|
|
- name: Wait for cloud-init to complete
|
|
command: cloud-init status --wait
|
|
changed_when: false
|
|
failed_when: false
|
|
tags: [validate]
|
|
|
|
- name: Gather system facts
|
|
setup:
|
|
tags: [validate]
|
|
|
|
- name: Display system information
|
|
debug:
|
|
msg:
|
|
- "=== System Information ==="
|
|
- "OS: {{ ansible_distribution }} {{ ansible_distribution_version }}"
|
|
- "Kernel: {{ ansible_kernel }}"
|
|
- "Architecture: {{ ansible_architecture }}"
|
|
- "Hostname: {{ ansible_hostname }}"
|
|
- "FQDN: {{ ansible_fqdn }}"
|
|
- "Python: {{ ansible_python_version }}"
|
|
- "Package Manager: {{ ansible_pkg_mgr }}"
|
|
tags: [validate]
|
|
|
|
- name: Gather disk usage
|
|
command: df -h
|
|
register: disk_usage
|
|
changed_when: false
|
|
tags: [validate]
|
|
|
|
- name: Display disk usage
|
|
debug:
|
|
var: disk_usage.stdout_lines
|
|
tags: [validate]
|
|
|
|
- name: Gather memory usage
|
|
command: free -h
|
|
register: memory_usage
|
|
changed_when: false
|
|
tags: [validate]
|
|
|
|
- name: Display memory usage
|
|
debug:
|
|
var: memory_usage.stdout_lines
|
|
tags: [validate]
|
|
|
|
- name: Check SELinux status (RHEL family)
|
|
command: getenforce
|
|
register: selinux_status
|
|
changed_when: false
|
|
failed_when: false
|
|
when: ansible_os_family == "RedHat"
|
|
tags: [validate]
|
|
|
|
- name: Display SELinux status
|
|
debug:
|
|
msg: "SELinux Status: {{ selinux_status.stdout }}"
|
|
when: ansible_os_family == "RedHat"
|
|
tags: [validate]
|