Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cleanly edit sshd_config for basic security options in an ansible playbook?

Tags:

ansible

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?

like image 908
Joshua Chia Avatar asked Jun 04 '19 03:06

Joshua Chia


4 Answers

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.

like image 69
Wolfgang Avatar answered Oct 21 '22 15:10

Wolfgang


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:

  • Look if the line you want to add is effectively there in which case it will do nothing
  • If the line does not exist, look for the regex to match it
  • If one or several match are found, the last occurrence will be replaced by the given line
  • If no regex is found, the line will be added at the end of the file
  • If you still need to add the line in a particular place in the file if it does not exists, you can use 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
like image 38
Zeitounator Avatar answered Oct 21 '22 14:10

Zeitounator


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.

like image 3
Vladimir Botka Avatar answered Oct 21 '22 14:10

Vladimir Botka


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.

like image 3
Andi Avatar answered Oct 21 '22 14:10

Andi