diff options
Diffstat (limited to 'modules/openssh')
| -rw-r--r-- | modules/openssh/manifests/pubkeys_directory.pp | 17 | ||||
| -rw-r--r-- | modules/openssh/manifests/server.pp | 3 | ||||
| -rw-r--r-- | modules/openssh/manifests/ssh_keys_from_ldap.pp | 26 | ||||
| -rw-r--r-- | modules/openssh/manifests/symlink_user.pp | 19 | ||||
| -rwxr-xr-x | modules/openssh/templates/ldap-sshkey2file.py | 204 | ||||
| -rw-r--r-- | modules/openssh/templates/sshd_config | 19 | ||||
| -rw-r--r-- | modules/openssh/templates/sshd_config_ldap | 3 |
7 files changed, 171 insertions, 120 deletions
diff --git a/modules/openssh/manifests/pubkeys_directory.pp b/modules/openssh/manifests/pubkeys_directory.pp deleted file mode 100644 index cbcaeb88..00000000 --- a/modules/openssh/manifests/pubkeys_directory.pp +++ /dev/null @@ -1,17 +0,0 @@ -class openssh::pubkeys_directory { - $pubkeys_directory = '/var/lib/pubkeys' - file { $pubkeys_directory: - ensure => directory, - } - - file { "$pubkeys_directory/root": - ensure => directory, - mode => '0700', - } - - file { "$pubkeys_directory/root/authorized_keys": - ensure => link, - target => '/root/.ssh/authorized_keys', - mode => '0700', - } -} diff --git a/modules/openssh/manifests/server.pp b/modules/openssh/manifests/server.pp index ce60646b..c45268d2 100644 --- a/modules/openssh/manifests/server.pp +++ b/modules/openssh/manifests/server.pp @@ -1,6 +1,7 @@ class openssh::server { # some trick to manage sftp server, who is arch dependent on mdv - $path_to_sftp = "$::lib_dir/ssh/" + # TODO: the path changed on Mageia 6 to /usr/libexec/openssh/sftp-server + $path_to_sftp = "${::lib_dir}/ssh/" package { 'openssh-server': } diff --git a/modules/openssh/manifests/ssh_keys_from_ldap.pp b/modules/openssh/manifests/ssh_keys_from_ldap.pp index d35f4d2a..9ea6c139 100644 --- a/modules/openssh/manifests/ssh_keys_from_ldap.pp +++ b/modules/openssh/manifests/ssh_keys_from_ldap.pp @@ -1,32 +1,20 @@ -class openssh::ssh_keys_from_ldap($symlink_users = [], - $config = '') inherits server { - # root account authorized_keys will be symlinked - # if you want to add symlink on other accounts, use $symlink_users parameter - - File ['/etc/ssh/sshd_config'] { - content => template('openssh/sshd_config','openssh/sshd_config_ldap') - } - - package { 'python-ldap': } - - include openssh::pubkeys_directory - $pubkeys_directory = $openssh::pubkeys_directory::pubkeys_directory - - symlink_user { $symlink_users: } +class openssh::ssh_keys_from_ldap inherits server { + package { 'python3-ldap': } $ldap_pwfile = '/etc/ldap.secret' + $nslcd_conf_file = '/etc/nslcd.conf' $ldap_servers = get_ldap_servers() - mga-common::local_script { 'ldap-sshkey2file.py': + mga_common::local_script { 'ldap-sshkey2file.py': content => template('openssh/ldap-sshkey2file.py'), - require => Package['python-ldap'] + require => Package['python3-ldap'] } cron { 'sshkey2file': - command => '/usr/local/bin/ldap-sshkey2file.py', + command => '/bin/bash -c "/usr/local/bin/ldap-sshkey2file.py && ( [[ -f /usr/bin/mgagit && -d /var/lib/git/.gitolite ]] && /bin/su -c \'/usr/bin/mgagit glrun\' - git ) ||:"', hour => '*', minute => '*/10', user => 'root', environment => 'MAILTO=root', - require => Local_script['ldap-sshkey2file.py'], + require => Mga_common::Local_script['ldap-sshkey2file.py'], } } diff --git a/modules/openssh/manifests/symlink_user.pp b/modules/openssh/manifests/symlink_user.pp deleted file mode 100644 index f2e107b1..00000000 --- a/modules/openssh/manifests/symlink_user.pp +++ /dev/null @@ -1,19 +0,0 @@ -define openssh::symlink_user() { - include openssh::pubkeys_directory - $pubkeys_directory = $openssh::pubkeys_directory::pubkeys_directory - file { "$pubkeys_directory/$name": - ensure => directory, - owner => $name, - group => $name, - mode => '0700', - } - - file { "$pubkeys_directory/$name/authorized_keys": - # FIXME : fragile approximation for $HOME - ensure => link, - target => "/home/$name/.ssh/authorized_keys", - mode => '0700', - } -} - - diff --git a/modules/openssh/templates/ldap-sshkey2file.py b/modules/openssh/templates/ldap-sshkey2file.py index eb8456ab..934e2865 100755 --- a/modules/openssh/templates/ldap-sshkey2file.py +++ b/modules/openssh/templates/ldap-sshkey2file.py @@ -1,96 +1,194 @@ -#!/usr/bin/python +#!/usr/bin/python3 -import sys +import argparse import os import random +import shutil +import sys +import tempfile +import textwrap +from typing import Iterable try: import ldap -except ImportError, e: - print "Please install python-ldap before running this program" +except ImportError: + print("Please install python-ldap before running this program") sys.exit(1) -basedn="<%= dc_suffix %>" -peopledn="ou=people,%s" % basedn +basedn = "<%= @dc_suffix %>" +peopledn = f"ou=people,{basedn}" <%- ldap_servers.map! { |l| "'ldaps://#{l}'" } -%> -uris=[<%= ldap_servers.join(", ") %>] +uris = [<%= ldap_servers.join(", ") %>] random.shuffle(uris) uri = " ".join(uris) -timeout=5 -binddn="cn=<%= fqdn %>,ou=Hosts,%s" % basedn -pwfile="<%= ldap_pwfile %>" +timeout = 5 +binddn = f"cn=<%= @fqdn %>,ou=Hosts,{basedn}" +ldap_secret_file = "<%= @ldap_pwfile %>" +nslcd_conf_file = "<%= @nslcd_conf_file %>" # filter out disabled accounts also # too bad uidNumber doesn't support >= filters -filter="(&(objectClass=inetOrgPerson)(objectClass=ldapPublicKey)(objectClass=posixAccount)(sshPublicKey=*))" -keypathprefix="<%= pubkeys_directory %>" - -def usage(): - print "%s" % sys.argv[0] - print - print "Will fetch all enabled user accounts under %s" % peopledn - print "with ssh keys in them and write each one to" - print "%s/<login>/authorized_keys" % keypathprefix - print - print "This script is intented to be run from cron as root" - print - -def get_pw(pwfile): +objfilter = "(&(objectClass=inetOrgPerson)(objectClass=ldapPublicKey)(objectClass=posixAccount)(sshPublicKey=*))" +keypathprefix = "/home" + +parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description=textwrap.dedent(f'''\ + Will fetch all enabled user accounts under {peopledn} + with ssh keys in them and write each one to + {keypathprefix}/<login>/.ssh/authorized_keys + + It will return failure when no keys are updated and success + when one or more keys have changed. + + This script is intended to be run from cron as root; + ''')) +parser.add_argument('-n', '--dry-run', action='store_true') +parser.add_argument('-v', '--verbose', action='store_true') +args = parser.parse_args() + + +def get_bindpw() -> str: try: - f = open(pwfile, 'r') - except IOError, e: - print "Error while reading password file, aborting" - print e - sys.exit(1) - pw = f.readline().strip() - f.close() - return pw + return get_nslcd_bindpw(nslcd_conf_file) + except: + pass -def write_keys(keys, user, uid, gid): try: - os.makedirs("%s/%s" % (keypathprefix,user), 0700) + return get_ldap_secret(ldap_secret_file) except: pass - keyfile = "%s/%s/authorized_keys" % (keypathprefix,user) - f = open(keyfile, 'w') + + print("Error while reading password file, aborting") + sys.exit(1) + + +def get_nslcd_bindpw(pwfile: str) -> str: + try: + with open(pwfile, 'r') as f: + pwfield = "bindpw" + for line in f: + ls = line.strip().split() + if len(ls) == 2 and ls[0] == pwfield: + return ls[1] + except IOError as e: + print("Error while reading nslcd file " + pwfile) + print(e) + raise + + print("No " + pwfield + " field found in nslcd file " + pwfile) + raise Exception() + + +def get_ldap_secret(pwfile: str) -> str: + try: + with open(pwfile, 'r') as f: + pw = f.readline().strip() + except IOError as e: + print("Error while reading password file " + pwfile) + print(e) + raise + return pw + + +def write_keys(keys: Iterable[bytes], user: bytes, uid: int, gid: int) -> bool: + userdir = f"{keypathprefix}/{user.decode('utf-8')}" + keyfile = f"{userdir}/.ssh/authorized_keys" + + fromldap = "" for key in keys: - f.write(key.strip() + "\n") - f.close() - os.chmod(keyfile, 0600) + fromldap += key.decode("utf-8").strip() + "\n" + + fromfile = "" + try: + with open(keyfile, 'r') as f: + fromfile = f.read() + except FileNotFoundError: + pass + + if fromldap == fromfile: + return False + + if args.dry_run: + print(f"Would write {keyfile}") + return True + + if args.verbose: + print(f"Writing {keyfile}") + + if not os.path.isdir(userdir): + shutil.copytree('/etc/skel', userdir) + os.chown(userdir, uid, gid) + for root, dirs, files in os.walk(userdir): + for d in dirs: + os.chown(os.path.join(root, d), uid, gid) + for f in files: + os.chown(os.path.join(root, f), uid, gid) + + try: + os.makedirs(f"{userdir}/.ssh", 0o700) + except FileExistsError: + pass + os.chmod(f"{userdir}/.ssh", 0o700) + os.chown(f"{userdir}/.ssh", uid, gid) + + with tempfile.NamedTemporaryFile( + prefix='ldap-sshkey2file-', mode='w', delete=False) as tmpfile: + tmpfile.write(fromldap) + os.chmod(tmpfile.name, 0o600) + os.chown(tmpfile.name, uid, gid) + shutil.move(tmpfile.name, keyfile) + # Hmm, apparently shutil.move does not preserve user/group so let's reapply + # them. I still like doing it before as this should be more "atomic" + # if it actually worked, so it's "good practice", even if shutil.move sucks os.chown(keyfile, uid, gid) - os.chmod("%s/%s" % (keypathprefix,user), 0700) - os.chown("%s/%s" % (keypathprefix,user), uid, gid) + os.chmod(keyfile, 0o600) + return True -if len(sys.argv) != 1: - usage() - sys.exit(1) -bindpw = get_pw(pwfile) +bindpw = get_bindpw() +changed = False try: ld = ldap.initialize(uri) ld.set_option(ldap.OPT_NETWORK_TIMEOUT, timeout) if uri.startswith("ldap:/"): ld.start_tls_s() ld.bind_s(binddn, bindpw) - res = ld.search_s(peopledn, ldap.SCOPE_ONELEVEL, filter, ['uid','sshPublicKey','uidNumber','gidNumber']) + res = ld.search_s(peopledn, ldap.SCOPE_ONELEVEL, objfilter, + ['uid', 'sshPublicKey', 'uidNumber', 'gidNumber']) try: - os.makedirs(keypathprefix, 0701) - except: + os.makedirs(keypathprefix, 0o701) + except FileExistsError: pass + + if args.verbose: + print("Found users:", + ", ".join(sorted([x[1]['uid'][0].decode('utf-8') for x in res]))) + for result in res: dn, entry = result # skip possible system users - if int(entry['uidNumber'][0]) < 500: + if 'uidNumber' not in entry or int(entry['uidNumber'][0]) < 500: continue - write_keys(entry['sshPublicKey'], entry['uid'][0], int(entry['uidNumber'][0]), int(entry['gidNumber'][0])) + if write_keys(entry['sshPublicKey'], entry['uid'][0], + int(entry['uidNumber'][0]), int(entry['gidNumber'][0])): + changed = True + ld.unbind_s() -except Exception, e: - print "Error" +except Exception: + print("Error") raise -sys.exit(0) +if changed: + if args.verbose: + print("SSH keys changed") + sys.exit(0) + +if args.verbose: + print("No changes in SSH keys") +sys.exit(1) # vim:ts=4:sw=4:et:ai:si diff --git a/modules/openssh/templates/sshd_config b/modules/openssh/templates/sshd_config index 27eee0f1..56ddd725 100644 --- a/modules/openssh/templates/sshd_config +++ b/modules/openssh/templates/sshd_config @@ -18,11 +18,10 @@ # The default requires explicit activation of protocol 1 #Protocol 2 -# HostKey for protocol version 1 -HostKey /etc/ssh/ssh_host_key # HostKeys for protocol version 2 HostKey /etc/ssh/ssh_host_rsa_key -HostKey /etc/ssh/ssh_host_dsa_key +HostKey /etc/ssh/ssh_host_ecdsa_key +HostKey /etc/ssh/ssh_host_ed25519_key # Lifetime and size of ephemeral version 1 server key #KeyRegenerationInterval 1h @@ -57,11 +56,11 @@ PermitRootLogin without-password #IgnoreRhosts yes # To disable tunneled clear text passwords, change to no here! -#PasswordAuthentication yes +PasswordAuthentication no #PermitEmptyPasswords no # Change to no to disable s/key passwords -#ChallengeResponseAuthentication yes +ChallengeResponseAuthentication no # Kerberos options #KerberosAuthentication no @@ -82,7 +81,7 @@ PermitRootLogin without-password # If you just want the PAM account and session checks to run without # PAM authentication, then enable this but set PasswordAuthentication # and ChallengeResponseAuthentication to 'no'. -UsePAM yes +UsePAM no # Accept locale-related environment variables AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES @@ -99,7 +98,6 @@ X11Forwarding yes #PrintLastLog yes #TCPKeepAlive yes #UseLogin no -UsePrivilegeSeparation yes #PermitUserEnvironment no #Compression delayed #ClientAliveInterval 0 @@ -114,10 +112,15 @@ UsePrivilegeSeparation yes #Banner none # override default of no subsystems -Subsystem sftp <%= path_to_sftp %>/sftp-server +Subsystem sftp /usr/libexec/openssh/sftp-server # Example of overriding settings on a per-user basis #Match User anoncvs # X11Forwarding no # AllowTcpForwarding no # ForceCommand cvs server +<% if @hostname == 'duvel' then %> +# git command is already forced to "gitolite <username>" in /var/lib/git/.ssh/authorized_keys +Match User *,!schedbot,!root,!git Group *,!mga-sysadmin,!mga-unrestricted_shell_access + ForceCommand /usr/local/bin/sv_membersh.pl -c "$SSH_ORIGINAL_COMMAND" +<% end %> diff --git a/modules/openssh/templates/sshd_config_ldap b/modules/openssh/templates/sshd_config_ldap deleted file mode 100644 index 31b29e21..00000000 --- a/modules/openssh/templates/sshd_config_ldap +++ /dev/null @@ -1,3 +0,0 @@ - -AuthorizedKeysFile /var/lib/pubkeys/%u/authorized_keys - |
