CIS + inital
This commit is contained in:
@@ -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'
|
||||
@@ -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
|
||||
@@ -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 }}"
|
||||
- "========================================="
|
||||
@@ -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(', ') }}"
|
||||
@@ -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'
|
||||
Reference in New Issue
Block a user