Integrating kerberised and shibboleth based authentication
Die web authorisierung von benutzer basiert am KIT auf shibboleth und idp. Allerdings gibt es eine menge 'legacy' produkten und services wofuer eine integration mit dieses neue verfahren erforderlich ist. Damit Linux und Linux services weiterhin mit der web welt mithalten koennen soll in dieses projekt untersucht werden wie das Network File System (NFS) transparent integriert werden kan in die neue KIT authorisirung.
Test change.
General scheme how to add Kerberos authentication to service
1. Add the service account to LDAP with random password, for example nfs/iwrcgop1.fzk.de@TEST.KIT.EDU:
dn: uid=nfs1,ou=users,ou=fed,dc=test,dc=kit,dc=edu objectClass: top objectClass: organizationalUnit objectClass: krb5kdcentry objectClass: uidObject objectClass: krb5principal krb5KeyVersionNumber: 0 krb5PrincipalName: nfs1/iwrcgop1.fzk.de@TEST.KIT.EDU ou: NFS1 uid: nfs1 userPassword: KJ983432989bfjsbJHdab4
2. Get service keys in keytab format. An example of a script:
#!/usr/bin/env python2 # Alexander Bersenev, bay@hackerdom.ru, 2015 # 1. Gets a keys from the service # 2. Prints the keys in the keytab format # Works only with apacheDS from pyasn1.codec.der import decoder import ldap import random import sys import user import re import string import struct import time LDAP_URI = "ldap://host.kit.edu:10389" USERS_DN = "ou=users,ou=fed,dc=test,dc=kit,dc=edu" REALM = "TEST.KIT.EDU" def get_user_keys_classes_and_passwd(user, ldap_obj): """Tries to get a key and password from ldap""" keys = [] classes = [] passwd = None user_dn = ("uid=%s," % user) + USERS_DN # make a ldap search request pairs = ldap_obj.search_s(user_dn, ldap.SCOPE_BASE) if len(pairs) != 1: # wrong LDAP scheme print("Multiple users") return keys, classes, passwd dn, attrs = pairs[0] if "krb5Key" in attrs: keys = attrs["krb5Key"] if "objectClass" in attrs: classes = attrs["objectClass"] if "userPassword" in attrs: passwd = attrs["userPassword"] return keys, classes, passwd def put_and_get_keys(user): keys = None try: l = ldap.initialize(LDAP_URI) keys, classes, passwd = get_user_keys_classes_and_passwd(user, l) if keys: return keys except ldap.NO_SUCH_OBJECT: print("No such user") return keys except ldap.OBJECT_CLASS_VIOLATION as E: print("Object class violation") # print(E) except ldap.LDAPError as E: print("LDAP Error") # print(E) return keys def gen_keytab(keys, service, host): entries = [] for key_type, key_val in keys: entry = "" entry += struct.pack(">H", 2) # number of components entry += struct.pack(">H", len(REALM)) entry += REALM entry += struct.pack(">H", len(service)) entry += service entry += struct.pack(">H", len(host)) entry += host entry += struct.pack(">L", 1) # name type(1 is KRB5_NT_PRINCIPAL) entry += struct.pack(">L", int(time.time())) # generation time entry += struct.pack("B", 1) # key version entry += struct.pack(">H", key_type) entry += struct.pack(">H", len(key_val)) entry += key_val entry = struct.pack(">L", len(entry)) + entry entries.append(entry) keytab = "\x05\x02" # version of the format keytab += "".join(entries) return keytab def main(user, service, host): keys = put_and_get_keys(user) if not keys: return decoded_keys = [] for key in keys: try: key_type, key_val = decoder.decode(key)[0] key_type = int(key_type) key_val = str(key_val) decoded_keys.append((key_type, key_val)) except Exception: print("Passing a bad key") pass sys.stdout.write(gen_keytab(decoded_keys, service, host)) if __name__ == "__main__": if len(sys.argv) != 4: print("USAGE: ./get_service_key.py <user> <service> <host>") sys.exit(1) user, service, host = sys.argv[1:] if not re.match(r"[ a-zA-Z0-9_-]+$", user): print("User name validation failed") sys.exit(1) if len(user) > 16384: print("User name is too long") sys.exit(1) main(user, service, host)
3. Specify the service to use Kerberos and provide generated keytab file(the default path is usually /etc/krb5.keytab)
4. Set up additional service-specific things. For example, in the case of NFS, make users resolvable via LDAP
NFS Specifics
NFS With Kerberos
Linux uses rpc.gssd process to connect kernel part of NFS client with userspace generic security serivces logic. GSS-API is a common API to many security mechanisms. Each implementation of Kerberos should be able to work with this API. So this process should be launched on a client host. There was another proccess to be run on standard nfs server - rpc.svcgssd, but now it is deprecated.
By default, the special type of credentials, host credentials are used to mount the nfs share. They are in form host/host123.kit.edu@TEST.KIT.EDU. These host credential are hard to support in federated environment, so this functionallity should be disabled. This can be done by '-n' option to rpc.gssd daemon.
Also, to access the mounted share each user should have the valid ticket. Each RPC call contains this ticket. NFS server use it to authenticate user.
Identity mapping
NFSv4 server sees the Kerberos credentials in each RPC request. It should be able to map those credentials into uids and gids. The way how to do it is defined in /etc/idmapd.conf. Both Linux kernel NFS server and Ganesha-nfs server use this file. The mapping can be defined statically in this file, or using nsswitch functionality. Nsswitch mechanism allow to store database not only in files, but also in other places, for example in LDAP. This mechanism can be configured with /etc/nsswitch.conf file. Ldap backend configures with /etc/ldap.conf or /etc/nslcd.conf files depending on distro.
The server side sends string-like usernames to nfs-client. The client side also uses /etc/idmapd.conf file to map username to ids.
The mechanism of mapping is overengineered a bit :). To make a mapping kernel executes /sbin/request-key. This program reads the /etc/request-key.conf and /etc/request-key.d/*.conf to find a program to launch for mapping. This program is /usr/sbin/nfsidmap by default. It makes a mapping(using /etc/idmapd.conf) and caches a result for 10 minutes by default. To clean the cache, the command /usr/sbin/nfsidmap -c is useful. Also Linux itself caches an inode and file data information. To drop these caches too use echo 3 > /proc/sys/vm/drop_caches command.
To debug this system, it is useful to temporary rename /usr/sbin/nfsidmap to something else, and in this case the kernel will use rpc.idmapd userspace daemon to make a mapping(of course the daemon should be run in this case).
The static mapping started to work on the client side starting with version 0.26 of libnfsidmap. It was released at 9 Oct 2014, and only supported in RHEL7(no Debian, Ubuntu or Gentoo yet).
Also it is quite simple to write a custom mapping logic. Example(map names to uids, using sum of character codes in names):
#include <string.h> #include <sys/types.h> #include "nfsidmap.h" #include "nfsidmap_internal.h" static int name_to_num(char *name) { int sum = 0; int i; for(i=0; name[i]; i++) { sum += name[i]; } return 10000 + sum; } static int mapdynamic_name_to_uid(char *name, uid_t *uid) { *uid = name_to_num(name); return 0; } static int mapdynamic_name_to_gid(char *name, gid_t *gid) { *gid = name_to_num(name); return 0; } struct trans_func static_trans = { .name = "mapdynamic", .init = NULL, .name_to_uid = mapdynamic_name_to_uid, .name_to_gid = mapdynamic_name_to_gid, .uid_to_name = NULL, .gid_to_name = NULL, .princ_to_ids = NULL, .gss_princ_to_grouplist = NULL, }; struct trans_func *libnfsidmap_plugin_init() { return (&static_trans); }