Integrating kerberised and shibboleth based authentication

From Lsdf
Jump to navigationJump to search

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);
}

Using NFS with kerberos as a client

1. Make sure that rpcsec_gss_krb5 kernel module is loaded(if applicable) 2. (optional) Set up the /etc/krb5.conf. An example:

[libdefaults]
        default_tgs_enctypes = des3-cbc-sha1-kd rc4-hmac aes128-cts-hmac-sha1-96 aes256-cts-hmac-sha1-96
        default_tkt_enctypes = des3-cbc-sha1-kd rc4-hmac aes128-cts-hmac-sha1-96 aes256-cts-hmac-sha1-96
        dns_lookup_realm = false
        dns_lookup_kdc = false
        dns_canonicalize_hostname = true
        rdns = false
        ignore_acceptor_hostname = true
        default_realm = TEST.KIT.EDU

[domain_realmm]
        example.com = EXAMPLE.COM
        .example.com = EXAMPLE.COM

        .kit.edu = TEST.KIT.EDU

[realms]
        TEST.KIT.EDU = {
                kdc = server:60088
        }
[domain_realm]
        .fzk.de = TEST.KIT.EDU

The [realm] section is not necessary if the Kerberos server is specified during installation of krb5_utils, or with DNS(_kerberos._tcp.REALM and _kerberos._UDP.REALM SRV records)

The [domain_realm] is not necessary if the server is in the same domain as realm.

3. Set up the /etc/idmapd.conf for id mapping. Optionally the files /etc/nslcd.conf(/etc/ldap.conf) and /etc/nssconfig.conf can be also edited.

4. Launch rpc.gssd -n.

5. Download keytab file from LDAP-Facade

6. Init Kerberos key with one of following commands:

# kinit -kt .keytab username@TEST.KIT.EDU
# kinit username@TEST.KIT.EDU

7. Mount the share with one of following commands:

# mount.nfs4 host:/dir/ /mnt/nfs -o sec=krb5,tcp,minorversion=0
# mount.nfs4 host:/dir/ /mnt/nfs -o sec=krb5,tcp,minorversion=1

The first command uses NFSv4.0(good for 2.6.32 kernel), the second uses NFSv4.1(for newer kernels)

8. To use the share, each user should have valid ticket.