aboutsummaryrefslogtreecommitdiffstats
path: root/modules/openssh/templates/ldap-sshkey2file.py
diff options
context:
space:
mode:
Diffstat (limited to 'modules/openssh/templates/ldap-sshkey2file.py')
-rwxr-xr-xmodules/openssh/templates/ldap-sshkey2file.py204
1 files changed, 151 insertions, 53 deletions
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