diff options
Diffstat (limited to 'modules/openssh')
| -rw-r--r-- | modules/openssh/manifests/init.pp | 26 | ||||
| -rw-r--r-- | modules/openssh/manifests/server.pp | 17 | ||||
| -rw-r--r-- | modules/openssh/manifests/ssh_keys_from_ldap.pp | 20 | ||||
| -rwxr-xr-x | modules/openssh/templates/ldap-sshkey2file.py | 194 | ||||
| -rw-r--r-- | modules/openssh/templates/sshd_config | 22 |
5 files changed, 245 insertions, 34 deletions
diff --git a/modules/openssh/manifests/init.pp b/modules/openssh/manifests/init.pp index e55660fd..bae0fa5c 100644 --- a/modules/openssh/manifests/init.pp +++ b/modules/openssh/manifests/init.pp @@ -1,25 +1 @@ -class openssh { - - # some trick to manage sftp server, who is arch dependent on mdv - $path_to_sftp = "$lib_dir/ssh/" - - package { "openssh-server": - ensure => installed - } - - service { sshd: - ensure => running, - path => "/etc/init.d/sshd", - subscribe => [ Package["openssh-server"], File["sshd_config"] ] - } - - file { "sshd_config": - path => "/etc/ssh/sshd_config", - ensure => present, - owner => root, - group => root, - mode => 644, - require => Package["openssh-server"], - content => template("openssh/sshd_config") - } -} +class openssh { } diff --git a/modules/openssh/manifests/server.pp b/modules/openssh/manifests/server.pp new file mode 100644 index 00000000..c45268d2 --- /dev/null +++ b/modules/openssh/manifests/server.pp @@ -0,0 +1,17 @@ +class openssh::server { + # some trick to manage sftp server, who is arch dependent on mdv + # TODO: the path changed on Mageia 6 to /usr/libexec/openssh/sftp-server + $path_to_sftp = "${::lib_dir}/ssh/" + + package { 'openssh-server': } + + service { 'sshd': + subscribe => Package['openssh-server'], + } + + file { '/etc/ssh/sshd_config': + require => Package['openssh-server'], + content => template('openssh/sshd_config'), + notify => Service['sshd'] + } +} diff --git a/modules/openssh/manifests/ssh_keys_from_ldap.pp b/modules/openssh/manifests/ssh_keys_from_ldap.pp new file mode 100644 index 00000000..9ea6c139 --- /dev/null +++ b/modules/openssh/manifests/ssh_keys_from_ldap.pp @@ -0,0 +1,20 @@ +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': + content => template('openssh/ldap-sshkey2file.py'), + require => Package['python3-ldap'] + } + + cron { 'sshkey2file': + 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 => Mga_common::Local_script['ldap-sshkey2file.py'], + } +} diff --git a/modules/openssh/templates/ldap-sshkey2file.py b/modules/openssh/templates/ldap-sshkey2file.py new file mode 100755 index 00000000..934e2865 --- /dev/null +++ b/modules/openssh/templates/ldap-sshkey2file.py @@ -0,0 +1,194 @@ +#!/usr/bin/python3 + +import argparse +import os +import random +import shutil +import sys +import tempfile +import textwrap +from typing import Iterable + +try: + import ldap +except ImportError: + print("Please install python-ldap before running this program") + sys.exit(1) + +basedn = "<%= @dc_suffix %>" +peopledn = f"ou=people,{basedn}" +<%- + ldap_servers.map! { |l| "'ldaps://#{l}'" } +-%> +uris = [<%= ldap_servers.join(", ") %>] +random.shuffle(uris) +uri = " ".join(uris) +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 +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: + return get_nslcd_bindpw(nslcd_conf_file) + except: + pass + + try: + return get_ldap_secret(ldap_secret_file) + except: + pass + + 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: + 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(keyfile, 0o600) + return True + + +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, objfilter, + ['uid', 'sshPublicKey', 'uidNumber', 'gidNumber']) + try: + 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 'uidNumber' not in entry or int(entry['uidNumber'][0]) < 500: + continue + if write_keys(entry['sshPublicKey'], entry['uid'][0], + int(entry['uidNumber'][0]), int(entry['gidNumber'][0])): + changed = True + + ld.unbind_s() +except Exception: + print("Error") + raise + +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 cb40a961..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 @@ -45,6 +44,7 @@ PermitRootLogin without-password #PubkeyAuthentication yes #AuthorizedKeysFile .ssh/authorized_keys + # For this to work you will also need host keys in /etc/ssh/ssh_known_hosts #RhostsRSAAuthentication no # similar for protocol version 2 @@ -56,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 @@ -81,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 no +UsePAM no # Accept locale-related environment variables AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES @@ -89,7 +89,7 @@ AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT AcceptEnv LC_IDENTIFICATION LC_ALL #AllowAgentForwarding yes -#AllowTcpForwarding yes +AllowTcpForwarding no #GatewayPorts no X11Forwarding yes #X11DisplayOffset 10 @@ -98,7 +98,6 @@ X11Forwarding yes #PrintLastLog yes #TCPKeepAlive yes #UseLogin no -UsePrivilegeSeparation yes #PermitUserEnvironment no #Compression delayed #ClientAliveInterval 0 @@ -113,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 %> |
