Hardening SSH With Ansible

I think its common practice to “harden” SSH by running the following in one of your playbooks:

`

  • name: Disallow root SSH access

lineinfile:
dest: /etc/ssh/sshd_config
regexp: “^PermitRootLogin no”
line: “PermitRootLogin no”
state: present
notify:

  • restart sshd

`

The way I understand this, if “PermitRootLogin no” does not appear in the sshd_config file, it’ll append that to the bottom of the file.

So in the scenario where Ansible finds “PermitRootLogin yes” in the file, it will append “PermitRootLogin no” at the end of the config file.

The problem is that when SSH reads “PermitRootLogin yes” earlier in the file THAT configuration setting it what it uses. So the ‘no’ setting
at the end of the file is ignored, and Ansible is not doing anything for me. (http://man.openbsd.org/sshd_config.5)

I think its common practice to "harden" SSH by running the following in one of your playbooks:
>
- name: Disallow root SSH access
lineinfile:
dest: /etc/ssh/sshd_config
regexp: "^PermitRootLogin no"
line: "PermitRootLogin no"
state: present
notify:
- restart sshd

>

The way I understand this, if "PermitRootLogin no" does not appear in the sshd_config file, it'll append that to the
bottom of the file.

So in the scenario where Ansible finds "PermitRootLogin yes" in the file, it will append "PermitRootLogin no" at the end
of the config file.

The problem is that when SSH reads "PermitRootLogin yes" earlier in the file THAT configuration setting it what it
uses. So the 'no' setting
at the end of the file is ignored, and Ansible is not doing anything for me. (http://man.openbsd.org/sshd_config.5)

Hello Jon,

you need to adjust the regexp so it covers both "yes" and "no" as configuration values. Or just drop "no" from the
end of the regexp.

Regards
        Racke

I used jinja2 template to solve some of this issues.

Racke,

I’m not sure how that helps? Ansible will still add the ‘no’ setting at the bottom of the sshd_config file and the ‘yes’ setting earlier in the sshd_config file is still active.

Thanks,
Jon

It will replace the line, you test it and you'll see.

Kai, I tried removed the no from my regexp and retested. Ansible did replace that line! It did not add a new entry at the end of the config file.
Now I have to go figure out WHO changed the config file from no to yes.
Thank you.

Hi there

Small correction:

regexp: “^(#|#\s|\s|)PermitRootLogin(\s*)(no|yes)”

The brackets around \s* are not needed. So this is correct:

regexp: “^(#|#\s|\s|)PermitRootLogin\s*(no|yes)”

Actually \s* is not one or many, but zero or many. There should be at least one space so \s+

Cyril,when I tried your regex and Ansible barked about the \s Both before and after PermitRootLogin I tried double bask-slash s (#|#\s|\s|) and that works.

All of these regex’s seem a little too specific. Why not just:

regexp: ‘^PermitRootLogin\b’
line: ‘PermitRootLogin no’

If the entry isn’t there it will add it (who cares if a commented entry already exists). If it already exists and has a different value it will replace the line with the new value.

@Jon glad you found a solution that works for you. Strangely I don’t have issues with just one backslash. Might be due to the old python version that is used on macOS.

@Stefan thanks for the correction!

@SCRigler basically you are right. But if someone sees the line that is commented out she/he might think the setting fell back to the default setting and close the config file again without noticing that way down in the config file there is actually a working setting that does overwrite the default. Might make troubleshooting a bit more frustrating that way.

Inactive hide details for "S C Rigler" ---05.09.2019 15:39:57---All of these regex's seem a little too specific. Why not just:“S C Rigler” —05.09.2019 15:39:57—All of these regex’s seem a little too specific. Why not just: regexp: ‘^PermitRootLogin\b’

Hi !

> regexp: "^(#|#\s|\s|)PermitRootLogin\s*(no|yes)"
Actually \s* is not *one or many*, but *zero or many*. There should be at
least one space so `\s+`

1) Searching for both commented and non-commented lines is risky. There
   might be both present in the file and the result will be unpredictable.
   It's better to ignore commented lines.

         regexp: "^(\s*)PermitRootLogin\s+(no|yes)"

2) Searching for (no|yes) does not make any sense. We're going to replace it
   anyway. This may make things even worse by preventing a potential syntax
   error to be corrected. Replace it with general pattern.

         regexp: "^(\s*)PermitRootLogin\s+(.*)$"

3) Always validate the configuration

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

my two cents, Cheers,

  -vlado

Vlado, It’s my understanding that the validate step should be done once in the playbook. Not for each sshd action in the playbook. Is that what you are saying?

That is because you are using double quotes and not single quotes around the regexp.
You could also remove the quotes since you don't actually need them.

If you are going to do hardening and have some kind reliability that it work you should not be using the lineinfile module.
Instead go for the copy or template module that Jonathan mention previously in this thread.

The reason for that is that lineinfile _*only*_ changes the last line it find in the file and openssh only uses the first line it find.

So you Ansible code can easily be tricked by these lines.

PermitRootLogin yes
PermitRootLogin no

or

PermitRootLogin yes
#PermitRootLogin yes

And many other combination.

Jon, how this might be accomplished? "validate" by design says:
https://docs.ansible.com/ansible/latest/modules/lineinfile_module.html

  "The validation command to run before copying into place..."

It works for me fine that way. The burden of the repeated validation is
present in the first run only (when idempotent). It's worth to be sure sshd
will keep running.

Cheers,

  -vlado

And the presented regents is case specific. Try “(?i)...”.

Mike