Le code présenté a été modifié afin de faciliter la lecture et d’éviter une redondance trop importante entre les différentes phases de la configuration.
Le sens du code n’est nullement changé.
Lorsque l’utilisateur charlie est créé, il est directement inclue dans le groupe TOPGUN précédemment créé. On indique ensuite que le terminal de ce compte correspond au binaire bash déjà présent sur la machine. On génère ensuite un clé rsa de 4096 bits. Enfin, on lui donne un mot de passe d’utilisateur par défaut. Ce mot de passe subit un hash de façon à ce qu’il n’apparaisse pas en clair dans les fichiers de configuration de la machine debian.
---
# tasks file for users
- name: Creation groupe TOPGUN
group:
name: TOPGUN
state: present
- name: Creation utilisateurs + generation de clef rsa
user:
name: "{{ item.name }}"
groups: TOPGUN
append: yes
shell: /bin/bash
generate_ssh_key: yes
ssh_key_bits: 4096
ssh_key_file: .ssh/id_rsa
password: "{{ 'tmp' | password_hash('sha512') }}"
with_items:
- { name: "charlie" }
Afin de personnaliser les moyens d’authentifications au service ssh du serveur, un bloc Match est créé. Celui permet de donner à un ou plusieurs groupes/utilisateurs des règles spécifiques de configuration ssh. Ici, seul le cas des moyens d’authentification est traité.
Grâce au module ansible blockinfile, il est possible d’ajouter dans un fichier (ici sshd_config) un bloc de text, éventuellement indenté, identifié par ses balises de début, et de fin. Ces balises doivent être uniques de façon à ce qu’ansible puisse les reconnaitre avant d’ajouter le bloc de texte au fichier. S’il reconnait les balises, il vérifie les différences de texte entre le bloc du fichier, et celui du script. S’ils sont identiques identiques, aucune modification n’est apportée, sinon le bloc est remplacé. De cette façon, ansible garantit la non-duplication des blocs de texte.
De ce cas précis, on ajoute un bloc dans le fichier sshd_config, qui va permettre de spécifier pour l’utilisateur charlie les moyens d’authentification requis.
La ligne suivante permet de spécifier la configuration du compte charlie:
Match User charlie
La ligne suivante permet de contraindre l’authentification de l’utilisateur charlie par ssh aux services PAM (ici, clé publique + google-authenticator):
AuthenticationMethods publickey,keyboard-interactive:pam
Une fois la modification du fichier sshd_config effectuée, il est conseillé de s’assurer directement de la validité de la modification apportée avec le paramètre de blockinfile:
validate: "/usr/sbin/sshd -T -f %s"
Enfin, comme des fichier de configuration sshd on été modifiés, on s’assure de bien redémarrer le service en fin d’exécution du script ansible grâce au handler:
notify:
# On relance le service ssh (sshd)
- Handler_restart_ssh
Voici le code complet détaillé dans cette partie:
# Définition des règles d'accès ssh au serveur par utilisateur et/ou groupe
- name: Configuration ssh specifique - "{{ item.name }}"
blockinfile:
path: /etc/ssh/sshd_config
marker: "# {mark} ANSIBLE MANAGED BLOCK - {{ item.name }}"
block: |
Match {{ item.header }}
{{ item.config_lines }}
backup: yes
validate: "/usr/sbin/sshd -T -f %s"
with_items:
# Configuration spécifique à l'utilisateur charlie
- { name: "charlie", header: "User charlie", config_lines: "PasswordAuthentication no\n\tPubkeyAuthentication yes\n\tAuthenticationMethods publickey,keyboard-interactive:pam"}
notify:
# On relance le service ssh (sshd)
- Handler_restart_ssh
L’utilisateur charlie a accès à toutes les commandes en tant que root. Afin qu’il puisse exécuter ces commandes convenablement, il faut l’ajouter aux fichiers des sudoers (ici /etc/sudoers) avec les permissions maximales:
charlie ALL=(ALL:ALL) ALL
Voici le code complet détaillé dans cette partie:
- name: Ajout de permissions sudo
lineinfile:
path: /etc/sudoers
line: "{{ item.line }}"
backup: yes
state: present
with_items:
# Permettre à l'utilisateur charlie de sudo n'importe quelle commande.
- { line: 'charlie ALL=(ALL:ALL) ALL' }
Pour des raisons de sécurité, la session ssh associée à l’utilisateur charlie ne doit pas être ouvert plus de 15 minutes sans que le client se soit login.
Le déroulement du script bash est simple:
#!bin/bash
# Check that there is a single param given to this script
if [ $# -ne "1" ]
then
echo "Lifetime in second is the only expected param."
exit 1
fi
# Regex to match integers
regex='^[0-9]+$'
#Â Check if the given param is an integer
if ! [[ ${1} =~ ${regex} ]]
then
echo "Lifetime in second as to be an Integer."
exit 1
fi
# Get all the ssh (sshd) running process, filtred with @pts to get ssh sessions, formated as such:
# "<timestamp> <pid> sshd: <session>"
process_list=`ps -eo etimes,pid,cmd --sort=etimes | grep sshd | grep '@pts' | sed -r "s/[[:space:]]+/\\ /g"`
# Check if the process list is empty, if so, there is no sshd session openned, we can exit safely
if [ -z "${process_list}" ]
then
exit 0
fi
# Variable for seconds conversion to minutes
minute=60
# Split the process list line by line to process it
while IFS=' ' read -r timestamp pid app user
do
if [ "${timestamp}" -gt ${1} ]; then
if [[ ${user} == charlie@* ]]; then
kill -HUP ${pid}
echo "Killed: ${timestamp} ${pid} ${app} ${user}"
fi
fi
done <<< $process_list
Ce script est exécuté grâce à une tâche cron répétée toutes les 14 minutes (840 secondes) dont le fichier est placé dans /etc/cron.d/timeout_ssh_charlie:
* * * * * root /bin/bash /home/timeout_ssh_charlie.sh 840
La modification des fichiers et l’import du script est réalisé par ansible:
# Ajout des fichiers nécessaires à la tâche cron
- name: Creation du cron job
copy:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
backup: yes
with_items:
- {src: "timeout_ssh_charlie.sh", dest: "/home"}
- {src: "timeout_ssh_charlie", dest: "/etc/cron.d"}
Afin de rendre compte de l’utilisateur charlie de la durée de vie de sa session ssh, nous avons modifié sont prompt pour les connexions ssh via le fichier /home/charlie/.bashrc en y ajoutant les lignes suivantes:
# Get the current epoch time in seconds
start_date=$((`date "+%s"` / 60))
# Prompt for remote ssh clients
if [ -n "${SSH_CLIENT}" ]; then
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\][lifetime: $((14 - (`date "+%s"` / 60 - $start_date))) mins]\$ '
elif [ -n "${SSH_TTY}" ]; then
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\][lifetime: $((14 - (`date "+%s"` / 60 - $start_date))) mins]\$ '
fi
unset color_prompt force_color_prompt
Celles-ci consistent en la différence entre l’epoch en secondes à la l’exécution du .bashrc et l’epoch en secondes récupéré lors de la lecture du script .bashrc. Le prompt est modifié uniquement si les variables d’environnement SSH_CLIENT ou SSH_TTY sont définies.
L’ensemble de ces fichiers est géré par ansible:
# Pour des raisons de sécurité, l'utilisateur charlie est déconnecté du serveur toutes les 15 minutes:
# 840 secondes = 14 minutes (script)
# [0,1] minute (cron job)
# ------------------------------------
# Total: [14, 15] minutes
- name: Timeout charlie after 15 mins
blockinfile:
path: /home/charlie/.bashrc
block: |
# Get the current epoch time in seconds
start_date=$((`date "+%s"` / 60))
# Prompt for remote ssh clients
if [ -n "${SSH_CLIENT}" ]; then
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\][lifetime: $((14 - (`date "+%s"` / 60 - $start_date))) mins]\$ '
elif [ -n "${SSH_TTY}" ]; then
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\][lifetime: $((14 - (`date "+%s"` / 60 - $start_date))) mins]\$ '
fi
unset color_prompt force_color_prompt
backup: yes
Lancer la commande:
$ ssh -v charlie@<IP>
On peut voir que la connexion est correctement établie.
La première phase de connexion (clé publique) a fonctionné (ligne 2 et ligne 7).
L’authentification se poursuit donc avec google authenticator (ligne 8)
Un code de vérification est attendu. Il s’agit du code google authenticator qui a été récupéré plus tôt sur l’application.
Entrer le code de vérification:

La vérification a alors réussi (ligne 2) et la connexion est établie (ligne 3) avec le compte charlie:

Un prompt personnalisé apparaît donc avec le décompte de la durée de connexion au compte charlie avant la fermeture de la session:
