Mon CV

Ajouter un mot de passe au trousseau en ligne de commande avec addKeychainPassword.sh

25 mai 2015

On continue la petite série de billets au sujet de la gestion d’un parc de Mac. Comme je l’ai expliqué à plusieurs reprises ici ou , j’ai intégré Outset sur l’ensemble du parc de Mac de ma structure. Cela permet de lancer des scripts de manière récurrente dans des contextes différents : au premier boot, à chaque boot, à la première ouverture de session d’un utilisateur, à chaque connexion d’un utilisateur.

Outset est assez simple à utiliser et, du coup, je commence à le charger de pas mal de petits scripts qui viennent améliorer la gestion des Mac.

Le dernier problème en date auquel j’ai été confronté est lié à l’utilisation de l’application FreeboxFax développée par Eric Reboux.

Cette application n’est utile que pour les utilisateurs Mac qui disposent d’un compte ADSL Freebox. Elle permet d’envoyer des fax de manière simple, sans devoir passer par l’interface en ligne proposée par Free. Glissez votre document, mettez le numéro du destinataire, et hop … le fax est parti.

Nous avons supprimé tous les fax “hardware” dans notre structure depuis pas mal de temps au profit de FreeboxFax.

Le seul problème, c’est que, pour fonctionner, FreeboxFax a besoin de connaître vos identifiants Free ADSL. Dans mon cas, chaque utilisateur était invité, lors de la première utilisation, à rentrer ceux-ci. Mais autant être franc, si on s’embête à mettre en place une structure de déploiement des logiciels avec Munki et si on tente de gérer l’environnement de ses utilisateurs avec les Préférences Gérées (MCX) ou le déploiement de profils, si l’on se casse le bol pour déployer les mises à jours et autres packages de manière transparente, ce n’est pas pour leur demander de rentrer manuellement ce type d’informations. Entre les fautes de frappe, ceux qui ne se rappellent plus (malgré les guides utilisateur rédigés avec amour) où trouver ces informations, et tous les cas les plus improbables les uns que les autres, ce petit point restait dans la liste des choses à améliorer.

Je m’y suis collé récemment. J’ai découvert la commande security, qui permet d’interagir en ligne de commande avec le trousseau. Il n’en fallait pas moins pour que j’ouvre mon éditeur favori et que je code rapidement un petit outil.

 

Le fonctionnement de addKeychainPassword.sh

L’outil que j’ai rapidement mis en place a pour simple fonction d’ajouter au trousseau de l’utilisateur un mot de passe pour une application ou un service donné. Il doit rester quelques bugs et pleins de développements sont possibles. N’hésitez pas à commenter pour cela.

Vous pouvez le récupérer sur GitHub (https://github.com/yvangodard/addKeychainPassword) ou bien le consulter ici :

#!/bin/bash

# Variables initialisation
version="addKeychainPassword v0.1 - 2015, Yvan Godard [godardyvan@gmail.com]"
versionOSX=$(sw_vers -productVersion | awk -F '.' '{print $(NF-1)}')
scriptDir=$(dirname "${0}")
scriptName=$(basename "${0}")
scriptNameWithoutExt=$(echo "${scriptName}" | cut -f1 -d '.')
logActive=0
modeErase=""
userUid=$(whoami)
homeDir=$(echo ~)
log=${homeDir%/}/Library/logs/${scriptNameWithoutExt}.log
logTemp=$(mktemp /tmp/${scriptNameWithoutExt}_LogTemp.XXXXX)
help="no"

help () {
	echo -e "\n$version\n"
	echo -e "Cet outil est destiné à ajouter un mot de passe dans le trousseau du Mac d'un utilisateur."
	echo -e "\nAvertissement :"
	echo -e "Cet outil est mis à disposition sans aucune garantie ni support."
	echo -e "\nUtilisation :"
	echo -e "./$scriptName [-h] | -m <mode> -a <account> -s <service> -p <password>" 
	echo -e "                         [-e <supprimer>] [-j <fichier log>]"
	echo -e "\nPour afficher l'aide :"
	echo -e "\t-h:                  affiche cette aide et quitte"
	echo -e "\nParamètres obligatoires :"
	echo -e "\t-m <mode> :          type de mot de passe dans le trousseau. Doit être 'generic' ou 'internet'"
	echo -e "\t-a <account> :       contient le nom d'utilisateur (champ 'Compte' dans le trousseau)"
	echo -e "\t-s <service> :       nom du service (champ 'Nom' dans le trousseau)"
	echo -e "\t-p <password> :      mot de passe pour le Compte et le Service à ajouter"
	echo -e "\nParamètres optionnels :"
	echo -e "\t-e <supprimer> :     cette option permet de supprimer toutes les entrées correspondantes au même"
	echo -e "\t                     service dans le trousseau avant d'ajouter le mot de passe."
	echo -e "\t                     Utiliser '-e all' pour supprimer toutes les entrées du"
	echo -e "\t                     trousseau qui correspondent au service ou utiliser"
	echo -e "\t                     '-e thisaccount' pour supprimer uniquement les entrée du trousseau"
	echo -e "\t                     qui correspondent à la fois au même Compte et même Service."
	echo -e "\t-j <fichier log> :   active la journalisation à la place de la sortie standard."
	echo -e "\t                     Mettre en argument le chemin complet du fichier de log à utiliser"
	echo -e "\t                     (ex. : '/var/log/LDAP-rename.log') ou utilisez 'default' pour le chemin par défaut (${log})"
	exit 0
}

function error () {
	# Erreur 1 : problème d'ajout au trousseau
	# Erreur 2 : problème dans le remplissage des paramètres
	echo -e "\n*** Erreur ${1} ***"
	echo -e ${2}
	alldone ${1}
}

function alldone () {
	# Journalisation si nécessaire et redirection de la sortie standard
	[ ${1} -eq 0 ] && echo "" && echo "[${scriptName}] Processus terminé OK !"
	if [ ${logActive} -eq 1 ]; then
		exec 1>&6 6>&-
		[[ ! -f ${log} ]] && touch ${log}
		cat ${logTemp} >> ${log}
		cat ${logTemp}
	fi
	# Suppression des fichiers et répertoires temporaires
	[[ -f ${logTemp} ]] && rm -r ${logTemp}
	exit ${1}
}

# Vérification des options/paramètres du script 
optsCount=0
while getopts "hm:a:s:p:e:j:" option
do
	case "$option" in
		h)	help="yes"
						;;
	    s) 	service=${OPTARG}
			let optsCount=$optsCount+1
						;;
		a)	account=${OPTARG}
			let optsCount=$optsCount+1
						;;
	    m) 	mode=${OPTARG}
			[[ ${mode} != "generic" ]] && [[ ${mode} != "internet" ]] && error 2 "Le mode n'a pas été renseigné correctement : utiliser '-m generic' ou '-m internet'."
			let optsCount=$optsCount+1
						;;
		p)	password=${OPTARG}
						;;
		e)	[[ ${OPTARG} != "" ]] && [[ ${OPTARG} != "all" ]] && [[ ${OPTARG} != "thisaccount" ]] && error 2 "Le paramètre '-e' n'a pas été renseigné correctement : utiliser '-e all' ou '-e thisaccount'."
			modeErase=${OPTARG}
						;;
        j)	[[ ${OPTARG} != "default" ]] && log=${OPTARG}
			logActive=1
                        ;;
	esac
done

if [[ ${optsCount} != "3" ]]
	then
        help
        error 7 "Les paramètres obligatoires n'ont pas été renseignés."
fi

if [[ ${help} = "yes" ]]
	then
	help
fi

if [[ ${password} = "" ]]
	then
	echo "Entrez le mot de passe :" 
	read -s password
fi

# Redirection de la sortie strandard vers le fichier de log
if [ $logActive -eq 1 ]; then
	echo -e "\nMerci de patienter ..."
	exec 6>&1
	exec >> ${logTemp}
fi

echo ""
echo "****************************** `date` ******************************"
echo "${scriptName} démarré..."
echo "sur Mac OSX version $(sw_vers -productVersion)"

# Suppression des mots de passe
[[ ${modeErase} = "all" ]] && suiteCommandeSecurity="-s ${service}"
[[ ${modeErase} = "thisaccount" ]] && suiteCommandeSecurity="-s ${service} -a ${account}"

if [[ ${modeErase} = "all" ]] || [[ ${modeErase} = "thisaccount" ]] ; then
	security find-${mode}-password ${suiteCommandeSecurity} > /dev/null 2>&1
	arreterSuppression=$(echo $?)
	if [[ ${arreterSuppression} = "0" ]]; then
		suppCount=0
		[[ ${modeErase} = "all" ]] && echo "> Suppression de tous les mots de passe du service '${service}' :"
		[[ ${modeErase} = "thisaccount" ]] && echo "> Suppression de tous les mots de passe du service '${service}' et du compte '${account}' :"
		until [[ ${arreterSuppression} != "0" ]] 
			do 
			security delete-${mode}-password ${suiteCommandeSecurity} > /dev/null 2>&1
			codeRetour=${?}
			[[ ${codeRetour} -ne "0" ]] && echo "  - Attention, un problème a été rencontré lors de la suppression !"
			security find-${mode}-password ${suiteCommandeSecurity} > /dev/null 2>&1
			arreterSuppression=$(echo $?)
			let suppCount=$suppCount+1
		done
		[[ ${suppCount} = "1" ]] && echo "  - ${suppCount} entrée dans le trousseau a été supprimée."
		[[ ${suppCount} -gt "1" ]] && echo "  - ${suppCount} entrées dans le trousseau ont été supprimées."
	else
		[[ ${modeErase} = "all" ]] && echo "> Aucun mot de passe à supprimer pour le service '${service}'."
		[[ ${modeErase} = "thisaccount" ]] && echo "> Aucun mot de passe à supprimer pour le service '${service}' et le compte '${account}'."
	fi
fi

# Ajout du mot de passe
# Test si il y un mot de passe équivalent
pwline=$(security 2>&1 >/dev/null find-${mode}-password -a ${account} -s ${service} -g)
pwpart=${pwline#*\"}
if [[ ${pwpart%\"} = ${password} ]]; then
	echo "> Le mot de passe existe déjà dans le trousseau et est correct. Rien à faire de plus."
	alldone 0
elif [[ ${pwpart%\"} != ${password} ]]; then
	echo "> Suppression préalable des entrées équivalentes pour le service et le compte."
	suiteCommandeSecurity="-s ${service} -a ${account}"
	security find-${mode}-password ${suiteCommandeSecurity} > /dev/null 2>&1
	arreterSuppression=$(echo $?)
	if [[ ${arreterSuppression} = "0" ]]; then
		suppCount=0
		until [[ ${arreterSuppression} != "0" ]] 
			do 
			security delete-${mode}-password ${suiteCommandeSecurity} > /dev/null 2>&1
			codeRetour=${?}
			[[ ${codeRetour} -ne "0" ]] && echo "  - Attention, un problème a été rencontré lors de la suppression !"
			security find-${mode}-password ${suiteCommandeSecurity} > /dev/null 2>&1
			arreterSuppression=$(echo $?)
			let suppCount=$suppCount+1
		done
		[[ ${suppCount} = "1" ]] && echo "  - ${suppCount} entrée dans le trousseau a été supprimée."
		[[ ${suppCount} -gt "1" ]] && echo "  - ${suppCount} entrées dans le trousseau ont été supprimées."
	else
		echo -e "  - Aucun mot de passe à supprimer pour le service '${service}' et le compte '${account}'."
	fi
	# Ajout du mot de passe
	security add-${mode}-password -a ${account} -s ${service} -w ${password} > /dev/null 2>&1
	codeRetour=${?}
	[[ ${codeRetour} -ne "0" ]] && error 1 "Problème lors de l'ajout du mot de passe dans le trousseau."
	[[ ${codeRetour} -eq "0" ]] && echo "> Ajout du mot de passe réalisé avec succès !"
	alldone 0
fi
addKeychainPassword.shview rawview file on GitHub

 

Pour l’utilisation :
./addKeychainPassword.sh [-h] | -m <mode> -a <account> -s <service> -p <password> [-e <supprimer>] [-j <fichier log>]

Les paramètres obligatoires à renseigner :

  • -m <mode> : type de mot de passe dans le trousseau. Doit être generic ou internet
  • -a <account> : contient le nom d’utilisateur (champ ‘Compte’ dans le trousseau)
  • -s <service> : nom du service (champ ‘Nom’ dans le trousseau)
  • -p <password> : mot de passe pour le Compte et le Service à ajouter

Et quelques options facultatives :

  • -e <supprimer> : cette option permet de supprimer toutes les entrées correspondantes au même service dans le trousseau avant d’ajouter le mot de passe. Utiliser -e all pour supprimer toutes les entrées du trousseau qui correspondent au service ou utiliser -e thisaccount pour supprimer uniquement les entrée du trousseau qui correspondent à la fois au même Compte et même Service.
  • -j <fichier log> : active la journalisation à la place de la sortie standard. Mettre en argument le chemin complet du fichier de log à utiliser (ex. : /var/log/LDAP-rename.log) ou utilisez default pour le chemin par défaut (~/Library/logs/addKeychainPassword.log)

 

Dans notre cas, j’ai donc déployé ce script sur tous les postes. Et j’ai ajouté dans le répertoire login-every d’Outset un script très court qui appelle cet outil avec les informations du compte Freebox ADSL :

#! /bin/bash

# Variables initialisation
version="addFreeboxFaxAppPassword v0.1 - 2015, Yvan Godard [godardyvan@gmail.com]"
scriptName=$(basename "${0}")
addKeychainPasswordTool=/user/local/addKeychainPassword/addKeychainPassword.sh
loginFreeboxFax="0467361236"
passwordFreeboxFax="xxxxxxxxxxxxxx"

${addKeychainPasswordTool} -m generic -e all -a ${loginFreeboxFax} -s FreeboxFax -p ${passwordFreeboxFax} -j default
[[ ${?} -ne 0 ]] && echo "[${scriptName}] Un problème a été rencontré lors de l'ajout au trousseau du mot de passe de l'application FreeboxFax.app."

exit 0

Il vous suffit de modifier les informations loginFreeboxFax et passwordFreeboxFax et c’est gagné.

 

Sécuriser un peu l’ensemble … en ne mettant pas en clair le mot de passe dans le script

Evidemment, dans la commande qui sera lancée par Outset on passe le mot de passe en clair, et il est écrit dans un fichier … pas top top côté sécurité …

Je me suis amusé à sécuriser un peu plus. L’idée que j’ai suivie est la suivante :

  • Je ne souhaite pas que le mot de passe du compte Free soit écrit en clair dans un script. Du coup, je vais le chiffrer et le stocker ailleurs.
  • J’ai donc choisi de le mettre à disposition, dans un fichier html (qui ne contient que ça). Ce fichier html sera accessible sur un serveur web mais dans un répertoire dont l’accès sera limité. En fait, j’ai mis une directive Apache qui limite l’accès au dossier où est stocké ce mot de passe uniquement à certaines adresses IP. Si vous avez besoin d’un peu d’aide pour cela, regardez ici (avant de commenter). Avec cette technique simple on limite déjà bien le risque que le mot de passe circule.
  • Pour ce qui est de chiffrer le mot de passe j’ai créé un tout petit script (que m’a inspiré Guillaume Bougard).

Voici donc l’outil qui va encoder et décodé le mot de passe (que j’ai pompeusement intitulé encfsPassword.sh)

#! /bin/bash

# Variables initialisation
version="encfsPassword v0.1 - 2015, Yvan Godard [godardyvan@gmail.com]"
versionOSX=$(sw_vers -productVersion | awk -F '.' '{print $(NF-1)}')
scriptDir=$(dirname "${0}")
scriptName=$(basename "${0}")
scriptNameWithoutExt=$(echo "${scriptName}" | cut -f1 -d '.')

export LOCALPASS="0hKdG07Ym907q7icImkkRIzC"

case "$1" in
	"--enc" )
		read -s -p "Quel mot de passe voulez-vous chiffrer ? " password
		echo
		echo -n "Mot de passe chiffré et encodé : "
		echo ${password} | openssl enc -aes-128-ecb -pass env:LOCALPASS -a -S deadbeef -A
		unset password
		echo
		exit 0
	;;
	"--FreeboxFax" )
		[[ $(curl -s -o /dev/null -I -w "%{http_code}" https://url.du.serveur.ou.est.depose.le.mot.de.passe/mon_mot_de_passe_encode.html) -ne 200 ]] && exit 1
		curl -k --silent https://url.du.serveur.ou.est.depose.le.mot.de.passe/mon_mot_de_passe_encode.html | openssl enc -aes-128-ecb -d -pass env:LOCALPASS -a -A
		exit 0
	;;
	* )
		echo "[${scriptName}] Paramètre inconnu : utilisez '--enc' ou '--FreeboxFax'."
		exit 2
	;;
esac

exit 0

Avant d’utiliser cet outil, pensez à changer la clé qui sera utilisé pour chiffrer vos mots de passe, c’est la ligne export LOCALPASS=.

Comme vous le voyez, si vous lancez cet outil avec l’option --enc, vous allez pouvoir encoder un mot de passe. Vous n’avez ensuite qu’à le mettre en ligne sur le web comme évoqué plus haut.

Pensez évidemment à modifier l’URL correspondante (ici https://url.du.serveur.ou.est.depose.le.mot.de.passe/mon_mot_de_passe_encode.html). Vous devrez sans doute supprimer le paramètre -k si vous n’utilisez pas un certificat auto-signé ou si vous mettez en ligne votre mot de passe à travers le protocole http.

Enfin, si vous avez plusieurs mots de passe, vous pouvez dupliquer la structure du script, construite sur case.

Du coup, j’ai donc besoin maintenant pour mettre tout cela en production :

  1. de déployer l’outil addKeychainPassword.sh (dans mon cas j’ai choisi de le mettre dans /usr/local/addKeychainPassword/)
  2. de déployer l’outil encfsPassword.sh (dans mon cas j’ai choisi de le mettre aussi dans /usr/local/addKeychainPassword/)
  3. de mettre dans le répertoire login-every d’Outset un script qui va lancer l’ensemble. J’ai un peu modifié addFreeboxFaxAppPassword.sh en ce sens :
#! /bin/bash

# Variables initialisation
version="addFreeboxFaxAppPassword v0.1 - 2015, Yvan Godard [godardyvan@gmail.com]"
versionOSX=$(sw_vers -productVersion | awk -F '.' '{print $(NF-1)}')
scriptDir=$(dirname "${0}")
scriptName=$(basename "${0}")
scriptNameWithoutExt=$(echo "${scriptName}" | cut -f1 -d '.')
encfsPasswordTool=/user/local/addKeychainPassword/encfsPassword.sh
addKeychainPasswordTool=/user/local/addKeychainPassword/addKeychainPassword.sh
loginFreeboxFax="0467361236"

${encfsPasswordTool} --FreeboxFax > /dev/null 2>&1
if [[ ${?} -eq 0 ]]; then
	passwordFreeboxFax=$(${encfsPasswordTool} --FreeboxFax) && ${addKeychainPasswordTool} -m generic -e all -a ${loginFreeboxFax} -s FreeboxFax -p ${passwordFreeboxFax} -j default
	[[ ${?} -ne 0 ]] && echo "[${scriptName}] Un problème a été rencontré lors de l'ajout au trousseau du mot de passe de l'application FreeboxFax.app."
else
	echo "[${scriptName}] Un problème a été rencontré lors de l'ajout au trousseau du mot de passe de l'application FreeboxFax.app."
	exit 1
fi
exit 0

Ce n’est peut-être pas ce qu’il y a de plus académique mais ça fonctionne, et je trouve que ça limite bien le risque de voir votre compte Free se balader partout !

Comme d’habitude, si vous avez des idées complémentaires, des corrections, des alternatives, n’hésitez pas !

 

 

Posted in Apple et Macintosh, Unix et LinuxTags: