Skip to content

Commit

Permalink
fix(10-042): Limit user lookups to "local" NSS passwd databases
Browse files Browse the repository at this point in the history
By default, only users from files, compat, db and systemd backends are
enumerated.  If other backends are active, a warning is emitted.  Adds an
option for chaning the set of backends

Also emits a warning if AuthorizedKeysCommand is enabled.
  • Loading branch information
s3lph committed Jan 18, 2023
1 parent de356c2 commit 45100df
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 13 deletions.
70 changes: 57 additions & 13 deletions plugins/modules/audit_ssh_authorizedkeys.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@
required: false
default: []
type: list
limit_nss_backends:
description: Only retrieve users from these NSS backends, and emit a warning if other backends are configured.
required: false
default: [files, compat, db, systemd]
type: list
config:
description: Path to the sshd config fille
required: false
Expand Down Expand Up @@ -73,6 +78,13 @@
allowed:
- 'from="2001:db8::42/128" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKBIpR/ccV9KAL5eoyPaT0frG1+moHO2nM2TsRKrdANU [email protected]'
- 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICZWKDPix+uTd+P+ZdoD3AkrD8cfikji9JKzvrfhczMA'
- name: The same, but also check users from sssd (use with caution if your domain contains a large number of users)
adfinis.maintenance.audit_ssh_authorizedkeys:
allowed:
- 'from="2001:db8::42/128" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKBIpR/ccV9KAL5eoyPaT0frG1+moHO2nM2TsRKrdANU [email protected]'
- 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICZWKDPix+uTd+P+ZdoD3AkrD8cfikji9JKzvrfhczMA'
limit_nss_backends: [files, compat, db, systemd, sss]
'''


Expand All @@ -89,12 +101,17 @@

from ansible.module_utils.basic import AnsibleModule

import collections
import os
import pwd
import subprocess
import shlex


# pwdent class conforming to https://docs.python.org/3/library/pwd.html
GetentPwdEnt = collections.namedtuple('pwdent', ['pw_name', 'pw_passwd', 'pw_uid', 'pw_gid', 'pw_gecos', 'pw_dir', 'pw_shell'])


def run_module():
# define available arguments/parameters a user can pass to the module
module_args = dict(
Expand All @@ -105,6 +122,7 @@ def run_module():
required=dict(type='list', required=False, default=[]),
allowed=dict(type='list', required=False, default=[]),
forbidden=dict(type='list', required=False, default=[]),
limit_nss_backends=dict(type='list', required=False, default=['files', 'compat', 'db', 'systemd'])
)

# seed the result dict in the object
Expand All @@ -125,18 +143,42 @@ def run_module():
supports_check_mode=True,
)

warnings = []

getent_backends = []
# Check NSS passwd db backends against list of limited backends, and emit warnings if additional backends are present
with open('/etc/nsswitch.conf', 'r') as nssf:
for line in nssf.readlines():
line = line.split('#', 1)[0].strip()
if not line:
continue
db, *backends = line.split()
if db != 'passwd:':
continue
for backend in backends:
if backend in module.params['limit_nss_backends']:
getent_backends.append(backend)
else:
msg = 'Users from the NSS passwd backend "{}" are excluded from this check. '.format(backend) + \
'Please audit manually or include the backend in limit_nss_backends'
warnings.append(msg)

# Get user homes
users = []
users = set()
if module.params['user'] is not None:
user = module.params['user']
try:
pwdent = pwd.getpwnam(user)
users.append(pwdent)
users.add(pwdent)
except KeyError:
module.fail_json(msg='User {} does not exist'.format(user), **result)
else:
for pwdent in pwd.getpwall():
users.append(pwdent)
# getpwnam/getpwall don't allow filtering by backends, need to user getent
for backend in getent_backends:
getent = subprocess.Popen(['/usr/bin/getent', 'passwd', '-s', backend], stdout=subprocess.PIPE)
getent_stdout, _ = getent.communicate()
for line in getent_stdout.decode().splitlines():
users.add(GetentPwdEnt(*line.split(':', 6)))

# Read the acutal ssh authorized_keys
result['authorized_keys'] = {}
Expand All @@ -148,18 +190,20 @@ def run_module():
authorized_keys_paths = [module.params['file']]
else:
ufilter = 'host=,addr=,user=' + pwdent.pw_name # host and addr are required by some implementations
sshd_cmdline = [module.params['sshd'], '-C', ufilter , '-T', '-f', module.params['config']]
sshd_cmdline = [module.params['sshd'], '-C', ufilter, '-T', '-f', module.params['config']]
sshd_configtest = subprocess.Popen(sshd_cmdline, stdout=subprocess.PIPE)
sshd_stdout, _ = sshd_configtest.communicate()
if sshd_configtest.returncode != 0:
module.fail_json(msg='SSHD configuration invalid (or insufficient privileges, try become_user=root become=yes)', **result)

for cline in sshd_stdout.decode().splitlines():
conf = cline.split()
if conf[0] != 'authorizedkeysfile':
continue
authorized_keys_paths = conf[1:]

conf = cline.split(maxsplit=1)
if conf[0] == 'authorizedkeyscommand' and conf[1] != 'none':
msg = 'AuthorizedKeysCommand is configured: "{}". Keys returned by this command are not audited.'.format(conf[1])
warnings.append(msg)
elif conf[0] == 'authorizedkeysfile':
authorized_keys_paths = conf[1].split()

if authorized_keys_paths is None:
authorized_keys_paths = []

Expand Down Expand Up @@ -227,13 +271,13 @@ def run_module():
'after_header': 'authorized_keys ({})'.format(user),
})

if len(violations) > 0:
if len(violations) > 0 or len(warnings) > 0:
result['changed'] = True
if not module.check_mode:
module.fail_json(msg=violations, **result)
module.fail_json(warnings=warnings, msg=violations, **result)
# in the event of a successful module execution, you will want to
# simple AnsibleModule.exit_json(), passing the key/value results
module.exit_json(**result)
module.exit_json(warnings=warnings, **result)


def main():
Expand Down
6 changes: 6 additions & 0 deletions roles/maintenance_10_linux/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ linux_allowed_ssh_authorized_keys: []

linux_additional_ssh_authorized_keys: []

linux_allowed_ssh_nss_backends:
- files
- compat
- db
- systemd

linux_allowed_login_users:
- root

Expand Down
1 change: 1 addition & 0 deletions roles/maintenance_10_linux/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@
name: "Security: SSH keys: Check for unknown or outdated keys for root and all users"
adfinis.maintenance.audit_ssh_authorizedkeys:
allowed: "{{ linux_allowed_ssh_authorized_keys + linux_additional_ssh_authorized_keys }}"
limit_nss_backends: "{{ linux_allowed_ssh_nss_backends }}"
check_mode: yes

- <<: *task
Expand Down

0 comments on commit 45100df

Please sign in to comment.