|
35 | 35 | name: rsyslog |
36 | 36 | state: present |
37 | 37 |
|
38 | | - - name: Ensure /run/sshd exists (required for ssh_hardening on Ubuntu 24.04) |
39 | | - ansible.builtin.file: |
40 | | - path: /run/sshd |
41 | | - state: directory |
42 | | - mode: '0755' |
43 | | - owner: root |
44 | | - group: root |
45 | | - |
46 | | - # Ensure OpenSSH is updated before hardening (fixes socket-activation issues) |
47 | | - # Minimum recommended version: 1:9.6p1-3ubuntu13.8 |
48 | | - # See: https://github.com/dev-sec/ansible-collection-hardening/issues/854 |
49 | | - - name: Update OpenSSH to latest version |
50 | | - ansible.builtin.apt: |
51 | | - name: |
52 | | - - openssh-server |
53 | | - - openssh-client |
54 | | - state: latest |
55 | | - |
56 | | - # Ubuntu 24.04 uses socket activation by default which conflicts with ssh_hardening |
57 | | - # See: https://github.com/dev-sec/ansible-collection-hardening/issues/854 |
58 | | - # See: https://discourse.ubuntu.com/t/sshd-now-uses-socket-based-activation-ubuntu-22-10-and-later/30189 |
59 | | - # |
60 | | - # Best practice approach from DevSec hardening: |
61 | | - # 1. Remove all socket-related config files |
62 | | - # 2. Stop, disable, and MASK ssh.socket (mask prevents any re-activation) |
63 | | - # 3. Reload systemd |
64 | | - # 4. Enable and start ssh.service |
65 | | - |
66 | | - - name: Remove socket activation configuration files |
67 | | - ansible.builtin.file: |
68 | | - path: "{{ item }}" |
69 | | - state: absent |
70 | | - loop: |
71 | | - - /etc/systemd/system/ssh.service.d/00-socket.conf |
72 | | - - /etc/systemd/system/ssh.socket.d/addresses.conf |
73 | | - - /etc/systemd/system/ssh.service.requires/ssh.socket |
74 | | - - /etc/systemd/system/sockets.target.wants/ssh.socket |
75 | | - |
76 | | - - name: Stop, disable and mask ssh.socket |
77 | | - ansible.builtin.systemd: |
78 | | - name: ssh.socket |
79 | | - state: stopped |
80 | | - enabled: false |
81 | | - masked: true |
82 | | - register: ssh_socket_result |
83 | | - failed_when: false |
84 | | - |
85 | | - - name: Log ssh.socket status if unexpected |
86 | | - ansible.builtin.debug: |
87 | | - msg: "Note: ssh.socket operation returned: {{ ssh_socket_result.msg | default('OK') }}" |
88 | | - when: ssh_socket_result is failed |
89 | | - |
90 | | - - name: Reload systemd daemon after socket config removal |
91 | | - ansible.builtin.systemd: |
92 | | - daemon_reload: true |
93 | | - |
94 | | - - name: Enable and start traditional SSH service |
95 | | - ansible.builtin.systemd: |
96 | | - name: ssh.service |
97 | | - enabled: true |
98 | | - state: started |
99 | 38 |
|
100 | 39 | roles: |
101 | 40 | # OS-level hardening (file permissions, kernel params, etc.) |
|
612 | 551 | # Claude Code will auto-enable plugins when it starts |
613 | 552 |
|
614 | 553 |
|
615 | | -# SSH hardening runs LAST to avoid connection issues blocking other tasks |
616 | | -- name: Apply SSH hardening (final step) |
617 | | - hosts: all |
618 | | - become: true |
619 | | - gather_facts: false |
620 | | - tags: [hardening] |
621 | | - |
622 | | - pre_tasks: |
623 | | - - name: Ensure /run/sshd still exists |
624 | | - ansible.builtin.file: |
625 | | - path: /run/sshd |
626 | | - state: directory |
627 | | - mode: '0755' |
628 | | - owner: root |
629 | | - group: root |
630 | | - |
631 | | - # Verify socket activation is still disabled before ssh_hardening runs |
632 | | - - name: Verify ssh.socket is stopped, disabled and masked |
633 | | - ansible.builtin.systemd: |
634 | | - name: ssh.socket |
635 | | - state: stopped |
636 | | - enabled: false |
637 | | - masked: true |
638 | | - register: ssh_socket_verify_result |
639 | | - failed_when: false |
640 | | - |
641 | | - - name: Log ssh.socket verification status if unexpected |
642 | | - ansible.builtin.debug: |
643 | | - msg: "Note: ssh.socket verification returned: {{ ssh_socket_verify_result.msg | default('OK') }}" |
644 | | - when: ssh_socket_verify_result is failed |
645 | | - |
646 | | - # Remove any remaining socket activation config that may have been recreated |
647 | | - - name: Remove socket activation configuration files (second pass) |
648 | | - ansible.builtin.file: |
649 | | - path: "{{ item }}" |
650 | | - state: absent |
651 | | - loop: |
652 | | - - /etc/systemd/system/ssh.service.d/00-socket.conf |
653 | | - - /etc/systemd/system/ssh.socket.d/addresses.conf |
654 | | - - /etc/systemd/system/ssh.service.requires/ssh.socket |
655 | | - |
656 | | - - name: Reload systemd daemon |
657 | | - ansible.builtin.systemd: |
658 | | - daemon_reload: true |
659 | | - |
660 | | - # Check what's holding port 22 for debugging |
661 | | - - name: Check what process is using port 22 |
662 | | - ansible.builtin.shell: ss -tlnp | grep ':22' || true |
663 | | - register: port_22_check |
664 | | - changed_when: false |
665 | | - |
666 | | - - name: Display port 22 usage |
667 | | - ansible.builtin.debug: |
668 | | - msg: "Port 22 status: {{ port_22_check.stdout | default('Port 22 is free') }}" |
669 | | - |
670 | | - # Test sshd config before attempting to start |
671 | | - - name: Test sshd configuration validity before starting |
672 | | - ansible.builtin.command: sshd -t |
673 | | - register: sshd_pretest |
674 | | - ignore_errors: true |
675 | | - changed_when: false |
676 | | - |
677 | | - - name: Display sshd config error if test failed |
678 | | - ansible.builtin.debug: |
679 | | - msg: "sshd -t failed: {{ sshd_pretest.stderr }}" |
680 | | - when: sshd_pretest.rc != 0 |
681 | | - |
682 | | - - name: Fail if sshd config is invalid |
683 | | - ansible.builtin.fail: |
684 | | - msg: "sshd configuration is invalid. Check /etc/ssh/sshd_config. Error: {{ sshd_pretest.stderr }}" |
685 | | - when: sshd_pretest.rc != 0 |
686 | | - |
687 | | - # Check if sshd is already running - if so, we don't need to start it |
688 | | - - name: Check if sshd is already running |
689 | | - ansible.builtin.shell: pgrep -x sshd > /dev/null && echo "running" || echo "stopped" |
690 | | - register: sshd_running |
691 | | - changed_when: false |
692 | | - |
693 | | - - name: Display sshd status |
694 | | - ansible.builtin.debug: |
695 | | - msg: "sshd is {{ sshd_running.stdout }}" |
696 | | - |
697 | | - # Only try to start if not already running |
698 | | - - name: Start ssh.service if not running |
699 | | - ansible.builtin.systemd: |
700 | | - name: ssh.service |
701 | | - state: started |
702 | | - enabled: true |
703 | | - when: sshd_running.stdout == "stopped" |
704 | | - |
705 | | - roles: |
706 | | - - role: devsec.hardening.ssh_hardening |
707 | | - |
708 | | - post_tasks: |
709 | | - # Test sshd config before attempting reload |
710 | | - - name: Test sshd configuration validity |
711 | | - ansible.builtin.command: sshd -t |
712 | | - register: sshd_test |
713 | | - ignore_errors: true |
714 | | - changed_when: false |
715 | | - |
716 | | - - name: Show sshd config test result |
717 | | - ansible.builtin.debug: |
718 | | - msg: "sshd -t output: {{ sshd_test.stderr | default('OK') }}" |
719 | | - when: sshd_test.rc != 0 |
720 | | - |
721 | | - - name: Show generated sshd_config if test failed |
722 | | - ansible.builtin.command: cat /etc/ssh/sshd_config |
723 | | - register: sshd_config_content |
724 | | - when: sshd_test.rc != 0 |
725 | | - changed_when: false |
726 | | - |
727 | | - - name: Display sshd_config content |
728 | | - ansible.builtin.debug: |
729 | | - msg: "{{ sshd_config_content.stdout_lines }}" |
730 | | - when: sshd_test.rc != 0 and sshd_config_content is defined |
731 | | - |
732 | | - # Manually reload SSH since we disabled ssh_server_enabled to skip the role's handler |
733 | | - # Try reload first, fall back to restart if reload fails (Ubuntu 24.04 quirk) |
734 | | - - name: Reload SSH service to apply hardening config |
735 | | - ansible.builtin.systemd: |
736 | | - name: ssh |
737 | | - state: reloaded |
738 | | - when: sshd_test.rc == 0 |
739 | | - register: ssh_reload_result |
740 | | - failed_when: false |
741 | | - |
742 | | - - name: Restart SSH service if reload failed |
743 | | - ansible.builtin.systemd: |
744 | | - name: ssh |
745 | | - state: restarted |
746 | | - when: sshd_test.rc == 0 and ssh_reload_result is defined and ssh_reload_result.failed | default(false) |
747 | | - |
748 | | - - name: Fail if sshd config is invalid |
749 | | - ansible.builtin.fail: |
750 | | - msg: "sshd configuration is invalid - SSH NOT reloaded. Fix config and retry." |
751 | | - when: sshd_test.rc != 0 |
0 commit comments