Charlie

Création et configuration du compte charlie

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é.

Création de l’utilisateur

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" }

Configuration des accès SSH

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

Configuration des permissions

Commandes root

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' }

Durée de vie de la session

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:

  • Vérifier que le nombre de paramètres fournis au script est correct
  • Vérifier que le paramètre entré est bien un int
  • Récupérer ensuite les processus ssh en cours
  • Fermer les processus sshd dont la session appartient à l’utilisateur charlie si la durée de vie de processus est supérieure à la valeur donnée en paramètres (14 mintues)
#!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

Démonstration de fonctionnement

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:

here

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

here

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:

here