CIS + inital

This commit is contained in:
2026-01-26 21:22:41 -05:00
parent 5b6e1567f9
commit 28db1d2104
65 changed files with 4555 additions and 2 deletions
+63
View File
@@ -0,0 +1,63 @@
---
# Create Admin Users
- name: Create admin user accounts
ansible.builtin.user:
name: "{{ item.username }}"
comment: "{{ item.comment | default(item.username) }}"
groups: "{{ item.groups | default(['sudo']) }}"
append: yes
shell: "{{ item.shell | default('/bin/bash') }}"
create_home: yes
state: "{{ item.state | default('present') }}"
loop: "{{ admin_users }}"
loop_control:
label: "{{ item.username }}"
- name: Set password policies for admin users
ansible.builtin.shell: |
chage -M {{ password_max_days }} -m {{ password_min_days }} -W {{ password_warn_age }} -I {{ password_inactive_days }} {{ item.username }}
loop: "{{ admin_users }}"
loop_control:
label: "{{ item.username }}"
when: item.state | default('present') == 'present'
changed_when: false
- name: Configure authorized SSH keys for admin users
ansible.posix.authorized_key:
user: "{{ item.0.username }}"
key: "{{ item.1 }}"
state: present
exclusive: no
loop: "{{ admin_users | subelements('authorized_keys', skip_missing=True) }}"
loop_control:
label: "{{ item.0.username }}"
when:
- item.0.state | default('present') == 'present'
- item.0.authorized_keys is defined
- item.0.authorized_keys | length > 0
- name: Ensure .ssh directory exists for admin users
ansible.builtin.file:
path: "/home/{{ item.username }}/.ssh"
state: directory
owner: "{{ item.username }}"
group: "{{ item.username }}"
mode: '0700'
loop: "{{ admin_users }}"
loop_control:
label: "{{ item.username }}"
when: item.state | default('present') == 'present'
- name: Set umask for admin users
ansible.builtin.lineinfile:
path: "/home/{{ item.username }}/.bashrc"
line: "umask {{ default_umask }}"
create: yes
owner: "{{ item.username }}"
group: "{{ item.username }}"
mode: '0644'
loop: "{{ admin_users }}"
loop_control:
label: "{{ item.username }}"
when: item.state | default('present') == 'present'
+104
View File
@@ -0,0 +1,104 @@
---
# Generate SSH Keys for Admin Users
- name: Create local SSH keys directory on control node
ansible.builtin.file:
path: "{{ ssh_keys_local_dir }}/{{ inventory_hostname }}"
state: directory
mode: '0700'
delegate_to: localhost
run_once: false
- name: Generate SSH key pairs on control node
community.crypto.openssh_keypair:
path: "{{ ssh_keys_local_dir }}/{{ inventory_hostname }}/{{ item.username }}_id_{{ ssh_key_type }}"
type: "{{ ssh_key_type }}"
size: "{{ ssh_key_bits if ssh_key_type == 'rsa' else omit }}"
comment: "{{ item.username }}@{{ inventory_hostname }}"
state: present
loop: "{{ admin_users }}"
loop_control:
label: "{{ item.username }}"
when:
- item.generate_keys | default(false)
- item.state | default('present') == 'present'
delegate_to: localhost
run_once: false
register: generated_keys
- name: Read generated public keys
ansible.builtin.slurp:
src: "{{ ssh_keys_local_dir }}/{{ inventory_hostname }}/{{ item.username }}_id_{{ ssh_key_type }}.pub"
loop: "{{ admin_users }}"
loop_control:
label: "{{ item.username }}"
when:
- item.generate_keys | default(false)
- item.state | default('present') == 'present'
delegate_to: localhost
run_once: false
register: public_keys
- name: Add generated public keys to authorized_keys
ansible.posix.authorized_key:
user: "{{ item.item.username }}"
key: "{{ item.content | b64decode }}"
state: present
loop: "{{ public_keys.results }}"
loop_control:
label: "{{ item.item.username }}"
when:
- not item.skipped | default(false)
- item.content is defined
- name: Create SSH key summary file
ansible.builtin.copy:
dest: "{{ ssh_keys_local_dir }}/{{ inventory_hostname }}/README.md"
content: |
# SSH Keys for {{ inventory_hostname }}
Generated: {{ ansible_date_time.iso8601 }}
## Admin Users
{% for user in admin_users %}
{% if user.generate_keys | default(false) %}
### {{ user.username }}
- **Private Key**: `{{ user.username }}_id_{{ ssh_key_type }}`
- **Public Key**: `{{ user.username }}_id_{{ ssh_key_type }}.pub`
- **Comment**: {{ user.username }}@{{ inventory_hostname }}
**Usage**:
```bash
# Copy private key to your machine
scp {{ ssh_keys_local_dir }}/{{ inventory_hostname }}/{{ user.username }}_id_{{ ssh_key_type }} ~/.ssh/
# Set correct permissions
chmod 600 ~/.ssh/{{ user.username }}_id_{{ ssh_key_type }}
# SSH to server
ssh -i ~/.ssh/{{ user.username }}_id_{{ ssh_key_type }} {{ user.username }}@{{ inventory_hostname }}
```
{% endif %}
{% endfor %}
## Security Notes
- Private keys are stored on the Ansible control node only
- Public keys are deployed to the servers
- Keep private keys secure and never commit to git
- Rotate keys regularly (every 90 days recommended)
## Key Rotation
To rotate keys:
1. Generate new keys by re-running the playbook
2. Test new keys work
3. Remove old keys from authorized_keys
4. Delete old private keys securely
mode: '0600'
delegate_to: localhost
run_once: false
when: admin_users | selectattr('generate_keys', 'defined') | selectattr('generate_keys') | list | length > 0
+31
View File
@@ -0,0 +1,31 @@
---
# SSH Users Role - Main Tasks
- name: Include user creation tasks
ansible.builtin.include_tasks: create_users.yml
when: admin_users | length > 0
- name: Include SSH key generation tasks
ansible.builtin.include_tasks: generate_keys.yml
when: admin_users | length > 0
- name: Include sudo configuration tasks
ansible.builtin.include_tasks: sudo.yml
- name: Include root restrictions tasks
ansible.builtin.include_tasks: root_restrictions.yml
- name: Include password policy tasks
ansible.builtin.include_tasks: password_policy.yml
- name: Display user management summary
ansible.builtin.debug:
msg:
- "========================================="
- "SSH User Management Complete"
- "========================================="
- "Admin users created: {{ admin_users | map(attribute='username') | list | join(', ') }}"
- "Root SSH login: {{ 'DISABLED' if disable_root_login else 'ENABLED' }}"
- "Sudo without password: {{ 'ENABLED' if sudo_nopasswd else 'DISABLED' }}"
- "SSH keys saved to: {{ ssh_keys_local_dir }}"
- "========================================="
+91
View File
@@ -0,0 +1,91 @@
---
# Password Policy Configuration (CIS 5.4.x, 5.5.x)
- name: Set default password expiration (CIS 5.4.1)
ansible.builtin.lineinfile:
path: /etc/login.defs
regexp: '^PASS_MAX_DAYS'
line: "PASS_MAX_DAYS {{ password_max_days }}"
state: present
- name: Set minimum password change days (CIS 5.4.2)
ansible.builtin.lineinfile:
path: /etc/login.defs
regexp: '^PASS_MIN_DAYS'
line: "PASS_MIN_DAYS {{ password_min_days }}"
state: present
- name: Set password expiration warning (CIS 5.4.3)
ansible.builtin.lineinfile:
path: /etc/login.defs
regexp: '^PASS_WARN_AGE'
line: "PASS_WARN_AGE {{ password_warn_age }}"
state: present
- name: Set default umask (CIS 5.4.4)
ansible.builtin.lineinfile:
path: /etc/login.defs
regexp: '^UMASK'
line: "UMASK {{ default_umask }}"
state: present
- name: Configure PAM password quality requirements (CIS 5.5.1)
ansible.builtin.apt:
name: libpam-pwquality
state: present
- name: Set password quality requirements
ansible.builtin.lineinfile:
path: /etc/security/pwquality.conf
regexp: "^{{ item.key }}"
line: "{{ item.key }} = {{ item.value }}"
state: present
loop:
- { key: 'minlen', value: '14' }
- { key: 'dcredit', value: '-1' } # At least 1 digit
- { key: 'ucredit', value: '-1' } # At least 1 uppercase
- { key: 'lcredit', value: '-1' } # At least 1 lowercase
- { key: 'ocredit', value: '-1' } # At least 1 special char
- { key: 'minclass', value: '4' } # All 4 character classes
- name: Configure account lockout (CIS 5.5.2)
ansible.builtin.lineinfile:
path: /etc/pam.d/common-auth
line: "auth required pam_faillock.so preauth audit silent deny=5 unlock_time=900"
insertbefore: '^auth\s+\[success=1 default=ignore\]\s+pam_unix.so'
state: present
- name: Configure account lockout (part 2)
ansible.builtin.lineinfile:
path: /etc/pam.d/common-auth
line: "auth [default=die] pam_faillock.so authfail audit deny=5 unlock_time=900"
insertafter: '^auth\s+\[success=1 default=ignore\]\s+pam_unix.so'
state: present
- name: Configure account lockout (part 3)
ansible.builtin.lineinfile:
path: /etc/pam.d/common-auth
line: "auth sufficient pam_faillock.so authsucc audit deny=5 unlock_time=900"
insertafter: '^auth\s+\[default=die\]\s+pam_faillock.so'
state: present
- name: Configure password reuse prevention (CIS 5.5.3)
ansible.builtin.lineinfile:
path: /etc/pam.d/common-password
regexp: '^password\s+\[success=1 default=ignore\]\s+pam_unix.so'
line: "password [success=1 default=ignore] pam_unix.so obscure use_authtok try_first_pass yescrypt remember=5"
state: present
- name: Ensure password hashing algorithm is yescrypt (CIS 5.5.4)
ansible.builtin.lineinfile:
path: /etc/pam.d/common-password
regexp: '^password\s+\[success=1 default=ignore\]\s+pam_unix.so'
line: "password [success=1 default=ignore] pam_unix.so obscure use_authtok try_first_pass yescrypt"
state: present
- name: Set inactive password lock (CIS 5.4.4)
ansible.builtin.lineinfile:
path: /etc/default/useradd
regexp: '^INACTIVE='
line: "INACTIVE={{ password_inactive_days }}"
state: present
@@ -0,0 +1,38 @@
---
# Root Account Restrictions
- name: Disable root SSH login
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^PermitRootLogin'
line: 'PermitRootLogin no'
state: present
validate: '/usr/sbin/sshd -t -f %s'
when: disable_root_login
notify: restart sshd
- name: Lock root account
ansible.builtin.user:
name: root
password_lock: yes
when: lock_root_account
- name: Ensure root group is GID 0 (CIS 5.4.5)
ansible.builtin.group:
name: root
gid: 0
state: present
- name: Verify root is the only UID 0 account
ansible.builtin.shell: |
awk -F: '($3 == 0) { print $1 }' /etc/passwd
register: uid_zero_accounts
changed_when: false
failed_when: uid_zero_accounts.stdout_lines | length > 1
- name: Display root restrictions status
ansible.builtin.debug:
msg:
- "Root SSH login: {{ 'DISABLED' if disable_root_login else 'ENABLED' }}"
- "Root account: {{ 'LOCKED' if lock_root_account else 'UNLOCKED' }}"
- "UID 0 accounts: {{ uid_zero_accounts.stdout_lines | join(', ') }}"
+76
View File
@@ -0,0 +1,76 @@
---
# Sudo Configuration (CIS 5.3.x)
- name: Ensure sudo is installed
ansible.builtin.apt:
name: sudo
state: present
update_cache: yes
- name: Configure sudo to use pty (CIS 5.3.2)
ansible.builtin.lineinfile:
path: /etc/sudoers.d/cis-hardening
line: "Defaults use_pty"
create: yes
mode: '0440'
validate: '/usr/sbin/visudo -cf %s'
- name: Configure sudo logfile (CIS 5.3.3)
ansible.builtin.lineinfile:
path: /etc/sudoers.d/cis-hardening
line: "Defaults logfile=\"/var/log/sudo.log\""
create: yes
mode: '0440'
validate: '/usr/sbin/visudo -cf %s'
- name: Configure sudo timeout
ansible.builtin.lineinfile:
path: /etc/sudoers.d/cis-hardening
line: "Defaults timestamp_timeout={{ sudo_timeout }}"
create: yes
mode: '0440'
validate: '/usr/sbin/visudo -cf %s'
- name: Configure sudo password requirement (CIS 5.3.4)
ansible.builtin.lineinfile:
path: /etc/sudoers.d/cis-hardening
line: "Defaults !authenticate"
state: "{{ 'present' if sudo_nopasswd else 'absent' }}"
create: yes
mode: '0440'
validate: '/usr/sbin/visudo -cf %s'
- name: Allow sudo group to use sudo
ansible.builtin.lineinfile:
path: /etc/sudoers.d/sudo-group
line: "%sudo ALL=(ALL:ALL) {{ 'NOPASSWD:' if sudo_nopasswd else '' }}ALL"
create: yes
mode: '0440'
validate: '/usr/sbin/visudo -cf %s'
- name: Create sudo log file
ansible.builtin.file:
path: /var/log/sudo.log
state: touch
owner: root
group: root
mode: '0600'
modification_time: preserve
access_time: preserve
- name: Configure sudo log rotation
ansible.builtin.copy:
dest: /etc/logrotate.d/sudo
content: |
/var/log/sudo.log {
weekly
rotate 4
compress
delaycompress
missingok
notifempty
create 0600 root root
}
owner: root
group: root
mode: '0644'