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
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
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 :
- 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
- 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 :
-kou–keytab: le fichierkeytabà utiliser pour une nouvelle authentification-oou–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 (viasudo). Si le paramètre n'est pas fourni, le propriétaire sera automatiquement déterminer à partir du fichierkeytabexistant ou à défaut du fichierccacheexistant.-cou–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.-dou–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
/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));