Vous êtes ici : index » informatique » reseau » ldap » auth_kerberos
Piste : auth_kerberos

Authentification Kerberos (SASL GSSAPI)

Vous n'êtes pas autorisé à effectuer cette action.

Authentification Kerberos (SASL GSSAPI)

Installation

On installe le paquet krb5-user pour avoir les commandes utiles :

apt-get install krb5-user

On crée ensuite le fichier /etc/krb5.conf :

[libdefaults]
default_realm = DOMAIN.TLD
dns_lookup_realm = false
dns_lookup_kdc = false
ticket_lifetime = 24h
renew_lifetime = 7d
forwardable = true
[realms]
DOMAIN.TLD = {
kdc = AD1.DOMAIN.TLD
admin_server = AD1.DOMAIN.TLD
}
[domain_realm]
.DOMAIN.TLD = DOMAIN.TLD
DOMAIN.TLD = DOMAIN.TLD
Le domaine doit être en majuscule partout pour que cela fonctionne.

On peut alors tenter une première authentification :

kinit myuser@DOMAIN.TLD

On vérifie ensuite le ticket obtenu :

klist

Pour la suite, on supprime le ticket obtenu :

kdestroy

Les fichiers keytab & ccache

Fichier Keytab (Key Table)

Un fichier Keytab est un fichier binaire contenant une ou plusieurs clés secrètes dérivées du mot de passe d'un utilisateur ou d'un service (Principal dans le vocable Kerberos). Il sert de “coffre-fort de clés” permettant une authentification automatisée sans interaction humaine et sans stockage du mot de passe en clair.

  • Rôle : Il remplace la saisie manuelle du mot de passe lors de la commande kinit.
  • Persistance : Permanent ⇒ Tant que le mot de passe du compte n'est pas modifié sur le contrôleur de domaine (KDC), le fichier reste valide.
  • Sécurité : Critique ⇒ Toute personne possédant un accès en lecture à ce fichier peut usurper l'identité du Principal associé. Il doit être protégé par des permissions restreintes (ex: chmod 600).

Fichier ccache (Credential Cache)

Un fichier ccache (Credential Cache) est un espace de stockage temporaire utilisé par Kerberos pour conserver les tickets de session (TGT - Ticket Granting Ticket) et les tickets de service obtenus après une authentification réussie. Il est généralement référencé par la variable d'environnement $KRB5CCNAME et vaut par défaut /tmp/krb5cc_[uidnumber de l'utilisateur].

  • Rôle : Il sert de “badge d'accès” temporaire. Une fois le ticket présent dans le cache, les applications l'utilisent directement pour prouver leur identité sans solliciter à nouveau le mot de passe ou le fichier keytab.
  • Persistance : Temporaire ⇒ Sa durée de vie est limitée par celle du ticket Kerberos (généralement 10 heures).
  • Sécurité : Moindre que le keytab, mais sensible. Il permet d'accéder aux ressources tant que les tickets ne sont pas expirés. Il doit également être protégé par des permissions restreintes (ex: chmod 600).

Création d'un fichier keytab & ccache

On crée le fichier keytab (par exemple /etc/myuser.keytab) en lançant la commande ktutil (mode interactif) et on entre les commandes suivantes :

ktutil:  addent -password -p myuser@DOMAIN.TLD -k 1 -e RC4-HMAC
Password for myuser@DOMAIN.TLD:
ktutil:  wkt /etc/myuser.keytab
ktutil:  q

On récupère ensuite un ticket en utilisant le fichier keytab et on le stocke dans un fichier ccache (par exemple /etc/myuser.ccache):

kinit -c /etc/myuser.ccache -k -t /etc/myuser.keytab myuser@DOMAIN.TLD

On vérifie ticket obtenu :

klist -c /etc/myuser.ccache
Pour un génération automatisé du fichier keytab :
PRINCIPAL=myuser@DOMAIN.TLD
KEYTAB=/etc/myuser.keytab
PASSWORD=secret
printf "addent -password -p %s -k 1 -e RC4-HMAC\n%s\nwkt %s\nq\n" "$PRINCIPAL" "$PASSWORD" "$KEYTAB" | ktutil

Renouvellement automatique

En cas de besoin de renouvellement automatique (=token utilisé par un cron/service par exemple), un cron doit être mis en place :

  1. Créer le fichier /usr/local/bin/renew-keytab:
    #!/bin/bash
     
    DEBUG=0
    CREATE=0
    OWNER=
    KEYTAB=
    DROP_KEYTAB=1
    KRB5CCNAME=
    PRINCIPAL=
     
    usage() {
    	[[ "$#" -gt 0 ]] && echo "$*" >&2
    	echo "Usage: $(realname "$0") krb5ccname principal [-k KEYTAB] [-o owner] [-c] [-d]"
    	echo "  [krb5ccname]	Path of the KRB5 credential cache file (REQUIRED)"
    	echo "  [principal]	Principal of the user (REQUIRED)"
    	echo "  -k keytab	Path of the keytab file"
    	echo "  -o/--owner	Owner of the manipulated files (defaut: the owner of the existing KRB5 credential cache or keytab file)"
    	echo "  -d/--debug	Enable debug log"
    	echo "  -c/--create	Enable create mode (do not expect that keytab & krb5ccname files already exists)"
    	[[ "$#" -gt 0 ]] && exit 1
    	exit 0
    }
     
    idx=1
    while [[ $idx -le $# ]]; do
            opt=${!idx}
    	case "$opt" in
    		-d|--debug)
    			DEBUG=1
    			;;
    		-c|--create)
    			[[ -t 1 ]] || usage "Create mode is only possible in interactive mode"
    			CREATE=1
    			;;
    		-k|--keytab)
    			((idx++))
    			KEYTAB="${!idx}"
    			DROP_KEYTAB=0
    			;;
    		-o|--owner)
    			((idx++))
    			OWNER="${!idx}"
    			;;
    		*)
    			if [[ -z "$KRB5CCNAME" ]]; then
    				KRB5CCNAME=$opt
    			elif [[ -z "$PRINCIPAL" ]]; then
    				PRINCIPAL=$opt
    			else
    				usage "Invalid argument '$opt'"
    			fi
    	esac
    	((idx++))
    done
     
    [[ -z "$KRB5CCNAME" ]] && usage "Keytab file not specified"
    [[ -z "$PRINCIPAL" ]] && usage "Principal not specified"
    debug() { [[ $DEBUG -eq 1 ]] && echo -e "[DEBUG] $*"; }
    info() { [[ -t 1 ]] && echo -e "[INFO] $*" && return; debug "$@"; }
    error() { echo -e "$*" >&2; }
     
    if [[ -z "$OWNER" ]]; then
    	if [[ -n "$KEYTAB" ]] && [[ -e "$KEYTAB" ]]; then
    		OWNER="$(stat -c %U "$KEYTAB")"
    	elif [[ -e "$KRB5CCNAME" ]]; then
    		OWNER="$(stat -c %U "$KRB5CCNAME")"
    	else
    		OWNER=$( id -un )
    	fi
    	debug "Auto-detected owner: $OWNER"
    fi
     
    if [[ "$( whoami )" != "$OWNER" ]]; then
            info "This script must be run as $OWNER: rerun it as $OWNER"
    	debug "Run: sudo -u $OWNER $(realpath "$0") $*"
            sudo -u "$OWNER" "$(realpath "$0")" "$@"
            exit $?
    fi
     
    [[ -z "$KEYTAB" ]] && \
    	KEYTAB=$(mktemp -u "/tmp/$(basename "$0")_keytab_of_${OWNER}_for_${PRINCIPAL}.XXX")
     
    function create_keytab() {
    	local password
    	NEW_KEYTAB=$(mktemp -u "/tmp/$(basename "$0")_temp_keytab_of_${OWNER}_for_${PRINCIPAL}.XXX")
            debug "Temporary file: $NEW_KEYTAB"
            while [[ "${password:-null}" == "null" ]]; do
                    read -srp "Please enter password of $PRINCIPAL: " password
                    echo
                    [[ "${password:-null}" == "null" ]] && error "Password required!"
            done
            debug "Password: '$password'"
            if ! printf "addent -password -p %s -k 1 -e RC4-HMAC\n%s\nwkt %s\nq\n" "$PRINCIPAL" "$password" "$NEW_KEYTAB" | ktutil; then
                    rm -f "$NEW_KEYTAB"
                    error "Failed to create a keytab for $PRINCIPAL"
                    return 1
            fi
            debug "Keytab file created, install it"
    	if cat "$NEW_KEYTAB" > "$KEYTAB"; then
    		if [[ "$DROP_KEYTAB" -eq 0 ]]; then
    			info "Keytab file installed in $KEYTAB"
    		else
    			debug "Keytab file installed in $KEYTAB"
    		fi
    		rm -f "$NEW_KEYTAB"
    		return 0
    	fi
    	rm -f "$NEW_KEYTAB"
    	error "Failed to install keytab file in $KEYTAB"
    	return 1
    }
     
    function init_kbr5cc() {
    	local new_keytab=0 a
    	if [[ ! -e "$KEYTAB" ]]; then
    		info "No existing keytab, create it"
    		create_keytab || return 1
    		new_keytab=1
    	fi
    	if ! kinit -c "$KRB5CCNAME" -k -t "$KEYTAB" "$PRINCIPAL"; then
                    error "Failed to initialize a KRB5 credential cache for $PRINCIPAL"
    		while [[ "${a^}" != "Y" ]] && [[ "${a^}" != "N" ]]; do
    			if [[ $new_keytab -eq 1 ]]; then
    				echo "May be the entered password is incorrect?"
    				read -rp "Retry [Y/n]? " a
    			else
    				echo "May be the password of $PRINCIPAL have be changed (or is expired?)."
    				read -rp "Drop existing keytab file & retry with a new password [Y/n]? " a
    			fi
    			[[ -z "$a" ]] && a=Y
    		done
    		if [[ "${a^}" == "Y" ]]; then
    			rm -f "$KEYTAB"
    			init_kbr5cc && return 0
    			return 1
    		elif [[ "$DROP_KEYTAB" -eq 1 ]]; then
    			rm -f "$KEYTAB"
    			debug "Keytab file removed ($KEYTAB)"
    		else
    			debug "Keytab file keep ($KEYTAB)"
    		fi
                    return 1
            fi
    	info "Keytab initialized for $PRINCIPAL:\n$( klist -c "$KRB5CCNAME" 2>&1 )"
    	return 0
    }
     
    [[ "$CREATE" -eq 1 ]] && { init_kbr5cc; exit $?; }
     
    if [[ -e "$KEYTAB" ]]; then
    	info "Keytab file available, obtain a new ticket for $PRINCIPAL using it"
    	if kinit -c "$KRB5CCNAME" -k -t "$KEYTAB" "$PRINCIPAL"; then
    		info "New ticket obtainned for $PRINCIPAL:\n$( klist -c "$KRB5CCNAME" 2>&1 )"
    		exit 0
    	fi
    	error "Failed to obtain new ticket for $PRINCIPAL using keytab file" \
    		"($KEYTAB). May be the password of $PRINCIPAL have been changed?"
    	[[ -e "$KRB5CCNAME" ]] || exit 1
    	info "Renew existing ticket for $PRINCIPAL in KRB5 credential cache file ($KRB5CCNAME)"
    fi
    if [[ -e "$KRB5CCNAME" ]]; then
    	[[ -e "$KEYTAB" ]] || \
    		info "No keytab file available, renew existing ticket for $PRINCIPAL in KRB5 credential cache file ($KRB5CCNAME)"
    	if kinit -c "$KRB5CCNAME" -R "$PRINCIPAL"; then
    		info "Ticket for $PRINCIPAL renewed:\n$( klist -c "$KRB5CCNAME" 2>&1 )"
    		exit 0
    	fi
    	error "Failed to renew existing ticket for $PRINCIPAL in KRB5 credential cache file" \
    		"($KRB5CCNAME). Probably its renewing period expired."
    else
    	info "KRB5 credential cache file not found ($KRB5CCNAME)." \
    		"No existing ticket for $PRINCIPAL to renew."
    fi
    if [[ -t 1 ]]; then
    	echo "Try to obtain a ticket for $PRINCIPAL interactively"
    	init_kbr5cc && exit 0
    else
            error "Please rerun this script interactively to reobtain a ticket for $PRINCIPAL:\n  sudo -u '$OWNER' $*"
    fi
    exit 1
  2. rendez le exécutable :
    chmod 750 /usr/local/bin/renew-keytab

Exemple d'utilisation :

# Renouveller un TGT existant
renew-keytab /etc/myuser.ccname myuser@DOMAIN.TLD
# Renouvellement avec un fichier keytab (nouvelle authentification)
renew-keytab /etc/myuser.ccname myuser@DOMAIN.TLD -k /etc/myuser.keytab

Arguments optionnels :

  • -k ou –keytab : le fichier keytab à utiliser pour une nouvelle authentification
  • -o ou –owner : préciser le propriétaire des fichiers manipulés. Cela permet de s'assurer que le script est lancé en tant que le bon utilisateur. Si ce n'est pas le cas, le script sera automatiquement réappeler en tant que le bon utilisateur (via sudo). Si le paramètre n'est pas fourni, le propriétaire sera automatiquement déterminer à partir du fichier keytab existant ou à défaut du fichier ccache existant.
  • -c ou –create : active le mode création. Dans ce mode, un fichier ccache sera recréé via une authentification à partir d'un fichier keytab et ce dernier sera créé s'il n'existe pas. Ce mode peut-être utilisé pour faciliter la création d'un couple de fichiers keytab / ccache.
  • -d ou –debug : active le mode debug

Connexion via ldapsearch

On installe pour commencer les dépendances pour l'authentification SASL GSSAPI :

apt-get install sasl2-bin libsasl2-2 libsasl2-modules libsasl2-modules-gssapi-mit

On peut ensuite interroger l'annuaire LDAP avec ldapsearch :

KRB5CCNAME=/path/to/file.ccname ldapsearch -H ldaps://AD1.domain.tld -Y GSSAPI -U 'myuser@DOMAIN.TLD' -b DC=domain,DC=tld -s base dc
En cas de soucis d'authentification sur un AD via LDAPS (mais pas en LDAP), tenter d'ajouter la ligne suivante dans le fichier /etc/ldap/ldap.conf :
sasl_secprops minssf=0,maxssf=0

Source : https://bugs.launchpad.net/ubuntu/+source/cyrus-sasl2/+bug/1015819

Connexion en python (avec python-ldap)

import os
import ldap
 
# Auth using keytab file
# os.environ['KRB5_CLIENT_KTNAME'] = '/etc/myuser.keytab'
# Auth using ccache file
os.environ['KRB5CCNAME'] = '/etc/myuser.ccache'
ldap_conn = ldap.initialize('ldaps://AD1.domain.tld')
ldap_conn.sasl_non_interactive_bind_s('GSSAPI')
print(ldap_conn.whoami_s())

Connexion en PHP

<?php
$conn = ldap_connect("ldaps://AD1.domain.tld", 636) or die("Failed to connect to LDAP host");
ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($conn, LDAP_OPT_REFERRALS, 0);
// Auth using keytab file
// putenv("KRB5_KTNAME=/path/to/file.keytab");
// Auth using ccache file
putenv("KRB5CCNAME=/path/to/file.ccname");
ldap_sasl_bind($conn, null, null, 'GSSAPI') or die("Kerberos auth failure: ".ldap_error($conn));
printf("Connected as %s\n", ldap_exop_whoami($conn));