I'm trying to write a playbook that cleanly edits /etc/ssh/sshd_config so that it has PasswordAuthentication no
and PermitRootLogin no
.
I can think of a few ways that are all problematic.
Firstly, I could delete all the lines matching PasswordAuthentication|PermitRootLogin
using lineinfile, and then append two new lines that I want, but i) this can fail non-atomically AND ii) appending lines at the end can mix them up with 'Match' blocks, which can typically appear at the end.
I could replace every line matching ^(# *)?PasswordAuthentication
with PasswordAuthentication no
, also using lineinfile, but that doesn't work if a matching line doesn't already exist. Also, if there are multiple matching lines, I'll have duplicate PasswordAuthentication no
lines.
I could use a template for the entire file, but that means I need to specify everything, including HostKey, but I don't want to specify everything and want to leave the other options the way they were originally setup.
None of the above ways are satisfactory because of the problems listed. Is there a clean way that makes the desired changes reliably, is idempotent, and does not leave the system in a bad state if it fails halfway?
This solution requires only one task and no additional files:
- 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
As pointed out in the comments, this solution is risky, as it requires a regex match. Without a match, a new line is generated at the end of the file which can conflict with match sections in the sshd_config
file.
I could replace every line matching ^(# *)?PasswordAuthentication with PasswordAuthentication no, also using lineinfile, but that doesn't work if a matching line doesn't already exist. Also, if there are multiple matching lines, I'll have duplicate PasswordAuthentication no lines.
You just did not get/test how lineinfile
is effectively working as this is exactly the solution you are looking for. In your particular case, without a backreference, the module will:
insertbefore
or insertafter
See the following example:
initial test.config
with multiple matching lines:
# PasswordAuthentication no
# PermitRootLogin no
somevalue no
# PasswordAuthentication no
# PermitRootLogin no
othervalue yes
# PasswordAuthentication no
# PermitRootLogin no
and test2.config
with no match
value none
othervalue no
yetanother yes
The test playbook:
---
- 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
Running the playbook changes the files on first run and reports ok on subsequent runs (no more changes being made). Here are the files once modified by the module.
test.config
:
# PasswordAuthentication no
# PermitRootLogin no
somevalue no
# PasswordAuthentication no
# PermitRootLogin no
othervalue yes
PasswordAuthentication no
# PermitRootLogin no
and test2.config
value none
othervalue no
yetanother yes
PasswordAuthentication no
If template is not an option then lineinfile shall be used. To address the details:
.. append two new lines that I want, but i) this can fail non-atomically ...
If it fails to complete, fix it and repeat the play.
... if there are multiple matching lines, I'll have duplicate PasswordAuthentication no lines.
Validate the configuration and if it fails to complete fix it and repeat the play.
validate: "{{ sshd_path }} -t -f %s"
None of the above ways are satisfactory ...
Such an expectation is not realistic. Both problems (arbitrary fail, matching lines) describe the erroneous states that do not have to be necessarily resolved by the module. lineinfile fits the purpose completely. For example, see sshd.yml.
I think you could work with lineinfile
and use state=absent
and state=present
like this:
- 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
Same would work for PasswordAuthentication yes/no
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With