如何在 ansible 行动手册中干净地编辑基本安全选项的sshd_config



我正在尝试编写一个干净编辑/etc/ssh/sshd_config 的剧本,以便它PasswordAuthentication noPermitRootLogin no

我能想到一些方法都是有问题的。

首先,我可以使用 lineinfile 删除所有与PasswordAuthentication|PermitRootLogin匹配的行,然后附加我想要的两个新行,但 i) 这可能会以非原子方式失败,并且 ii) 在末尾附加行可以将它们与"匹配"块混合,通常可以出现在末尾。

我也可以使用 lineinfile,将每个行匹配^(# *)?PasswordAuthentication替换为PasswordAuthentication no,但如果匹配行尚不存在,则不起作用。另外,如果有多个匹配的行,我将有重复的PasswordAuthentication no行。

我可以对整个文件使用模板,但这意味着我需要指定所有内容,包括 HostKey,但我不想指定所有内容,并希望将其他选项保留为最初设置的方式。

由于列出的问题,上述方法都不令人满意。是否有一种干净的方法可以可靠地进行所需的更改,是幂等的,并且在中途失败时不会使系统处于错误状态?

此解决方案只需要一个任务,不需要其他文件:

- name: Configure sshd
lineinfile:
path: "/etc/ssh/sshd_config"
regex: "^(#)?{{item.key}}"
line: "{{item.key}} {{item.value}}"
state: present
loop:
- { key: "PermitRootLogin", value: "no" }
- { key: "PasswordAuthentication", value: "no" } 
notify:
- restart sshd

正如评论中指出的那样,此解决方案是有风险的,因为它需要正则表达式匹配。如果没有匹配项,则会在文件末尾生成一个新行,该新行可能与sshd_config文件中的匹配部分冲突。

我可以替换匹配的每一行^(# *)?密码身份验证与密码身份验证 否,也使用 lineinfile,但如果匹配的行尚不存在,则不起作用。另外,如果有多个匹配的行,我将有重复的密码身份验证没有行。

您只是没有获得/测试lineinfile如何有效工作,因为这正是您正在寻找的解决方案。在您的特定情况下,如果没有反向引用,模块将:

  • 查看您要添加的行是否有效,在这种情况下,它将无所事事
  • 如果该行不存在,请查找正则表达式以匹配它
  • 如果找到一个或多个匹配项,则最后一个匹配项将被给定的行替换
  • 如果未找到正则表达式,则将在文件末尾添加该行
  • 如果
  • 仍需要在文件中的特定位置添加该行(如果该行不存在),则可以使用insertbeforeinsertafter

请参阅以下示例:

具有多个匹配行的初始test.config

# PasswordAuthentication no
# PermitRootLogin no
somevalue no
# PasswordAuthentication no
# PermitRootLogin no
othervalue yes
# PasswordAuthentication no
# PermitRootLogin no

test2.config没有匹配

value none
othervalue no
yetanother yes

测试手册:

---
- name: Line in file test
hosts: localhost
gather_facts: false
tasks:
- name: test replace
lineinfile:
path: "{{ item }}"
regex: ^(# *)?PasswordAuthentication
line: PasswordAuthentication no
loop:
- /path/to/test.config
- /path/to/test2.config

运行 playbook 会在首次运行时更改文件,并在后续运行时报告正常(不再进行任何更改)。以下是模块修改后的文件。

test.config

# PasswordAuthentication no
# PermitRootLogin no
somevalue no
# PasswordAuthentication no
# PermitRootLogin no
othervalue yes
PasswordAuthentication no
# PermitRootLogin no

test2.config

value none
othervalue no
yetanother yes
PasswordAuthentication no

我认为您可以使用lineinfile并使用state=absentstate=present

- name: deactivate PermitRootLogin
lineinfile:
path: "/etc/ssh/sshd_config"
line: "PermitRootLogin no"
state: present
notify:
- restart sshd
- name: ensure PermitRootLogin is not activated
lineinfile:
path: "/etc/ssh/sshd_config"
line: "PermitRootLogin yes"
state: absent
notify:
- restart sshd

同样适用于PasswordAuthentication yes/no.

如果模板不是一个选项,则应使用lineinfile。要解决详细信息,请执行以下操作:

.. 附加我想要的两个新行,但我)这可能会非原子失败......

如果无法完成,请修复它并重复播放。

。 如果有多个匹配的行,我将有重复的密码身份验证没有行。

验证配置,如果无法完成,请修复它并重复播放。

validate: "{{ sshd_path }} -t -f %s"

以上方法都不令人满意...

这种期望是不现实的。这两个问题(任意失败、匹配行)都描述了模块不必解决的错误状态。行文件完全符合目的。例如,请参阅 sshd.yml。

您的发行版的默认 sshd 配置/etc/ssh/sshd_config可能有一个包含指令:

Include /etc/ssh/sshd_config.d

理想情况下,这是在配置的开头,因为sshd_config手册页建议第一个值(每个选项)获胜:for each keyword, the first obtained value will be used。在撰写本文时,Ubuntu和Fedora似乎就是这种情况。

这使我们能够将所需的配置放入我们控制的文件中,例如:

- name: explicitly configure password authentication=no
become: yes
lineinfile:
path: "/etc/ssh/sshd_config.d/00-user.conf"
line: "PasswordAuthentication no"
state: present
create: true
notify: restart sshd

仅此一项就足以确保我们的配置始终获胜,因为文件按词法顺序处理(参见sshd_config(5))。

但是,为了更加确定我们可以从其他配置文件中删除任何矛盾的配置:

- name: find all sshd configs
become: yes
ansible.builtin.find:
paths: "/etc/ssh/sshd_config.d"
file_type: file
use_regex: true
recurse: yes
register: sshd_configs
- name: remove password authentication=yes if present
become: yes
lineinfile:
path: "{{ item.path }}"
# Ignore any commented lines, don't change more than we need to
# (?i) case insensitive match, ^(?!#) negative lookahead - line must not start with #
regex: "(?i)^(?!#).*PasswordAuthentication.*yes"
state: absent
loop: "{{ sshd_configs.files + [{'path': '/etc/ssh/sshd_config'}] }}"
notify: restart sshd

逐行编辑文件的一种替代且更优雅的选择是将/etc/ssh/sshd_config文件完全替换为新副本。

这是 RedHat Ansible 安全强化指南中建议的方法。它有一个显着的好处,即它保证了定义的行为,因为意外边缘情况的机会显着降低(就像在现有文件上替换正则表达式模式一样)。这也意味着您可以确保整个服务器群的一致性,并在需要时更轻松地整理/改进配置。

建议sshd_config文件:

HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
SyslogFacility AUTHPRIV
AuthorizedKeysFile  .ssh/authorized_keys
PasswordAuthentication no
ChallengeResponseAuthentication no
GSSAPIAuthentication yes
GSSAPICleanupCredentials no
UsePAM yes
X11Forwarding no
Banner /etc/issue.net
AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
AcceptEnv LC_IDENTIFICATION LC_ALL LANGUAGE
AcceptEnv XMODIFIERS
Subsystem   sftp    /usr/libexec/openssh/sftp-server
PermitRootLogin no

要复制sshd_config的 Ansible YAML:

- name: Add hardened SSH config
copy:
dest: /etc/ssh/sshd_config
src: etc/ssh/sshd_config
owner: root
group: root
mode: 0600
notify: Reload SSH

Ansible YAML 重新启动 SSH:

handlers:
- name: Reload SSH
service:
name: sshd
state: reloaded