Le code présenté a été modifié enfin 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 goose 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: "goose" }
L’utilisateur goose est un stagiaire dont on souhaite limiter au maximum les interactions avec le serveur ssh. On souhaite également qu’il n’ai accès qu’aux commandes de bases.
Pour ces raisons, nous avons fait le choix de lui créer un environnement dédié sur la machine.
En préambules, voici les variables du rôle chroot nécessaires à la compréhension du script ansible.
---
# vars file for roles/chroot
# Utilisateur de l'environnement
user: "goose"
# Localisation de l'environnement dans l'architecture de fichiers globale
root_dir: "/home"
chroot_dir: "{{ root_dir }}/SSH_JAIL"
# Fichiers d'environnement
dev_dir: "{{ chroot_dir }}/dev"
bin_dir: "{{ chroot_dir }}/bin"
etc_dir: "{{ chroot_dir }}/etc"
home_dir: "{{ chroot_dir }}/home"
user_dir: "{{ home_dir }}/{{ user }}"
# Fichiers nodes
node_nul: "{{ dev_dir }}/null"
node_tty: "{{ dev_dir }}/tty"
node_zero: "{{ dev_dir }}/zero"
node_random: "{{ dev_dir }}/random"
# Commandes qui seront importées dans l'environnement (dépendences incluses)
base_cmd: [bash, ls, cat, mkdir, touch, echo, ls, rm]
On commence tout d’abord par créer l’architecture de fichiers nécessaire au fonctionnement autonome de l’environnement. L’option recurse: yes permet de construire l’intégralité du chemin renseigné s’il n’existe pas:
# Creation des fichiers d'environnement
- name: Change file ownership, group and permissions
file:
path: "{{ item.path }}"
recurse: yes
state: directory
group: "{{ item.group }}"
owner: "{{ item.owner }}"
mode: "{{ item.mode }}"
with_items:
# Les fichiers d'environnement doivent avoir des droits d'accès spécifiques (root)
- { path: "{{ chroot_dir }}", group: "root", owner: "root", mode: "0755" }
- { path: "{{ dev_dir }}", group: "root", owner: "root", mode: "0755" }
- { path: "{{ bin_dir }}", group: "root", owner: "root", mode: "0755" }
- { path: "{{ etc_dir }}", group: "root", owner: "root", mode: "0755" }
- { path: "{{ user_dir }}", group: "goose", owner: "goose", mode: "0700" }
# Création de fichier spéciaux en mode FIFO, et caractères sans tampon
- name: Creation des nodes
command: "mknod -m 666 {{ item.file_path }} c {{ item.param1 }} {{ item.param2 }}"
ignore_errors: yes
with_items:
- { file_path: "{{ node_nul }}", param1: "1", param2: "3" }
- { file_path: "{{ node_tty }}", param1: "5", param2: "0" }
- { file_path: "{{ node_zero }}", param1: "1", param2: "5" }
- { file_path: "{{ node_random }}", param1: "1", param2: "8" }
# Ajout des données de groupes et de mots de passe dans l'environnement
- name: Import des groupes et utilisateurs
copy:
src: "/etc/{{ item }}"
dest: "{{etc_dir}}/{{ item }}"
backup: yes
with_items:
- "passwd"
- "group"
Une fois les fichiers d’environnement créés, il suffit d’ajouter les commandes qui seront utilisables dans celui-ci. Pour cela, il faut importer les commandes présentes dans le dossier /bin/ de la machine d’origine, et les bibliothèques dont elles dépendent obtenables via la commande ldd:
$ ldd /bin/ls | egrep -o '/lib.*\.[0-9]'
/lib/x86_64-linux-gnu/libselinux.so.1
/lib/x86_64-linux-gnu/libc.so.6
/lib/x86_64-linux-gnu/libpcre2-8.so.0
/lib/x86_64-linux-gnu/libdl.so.2
/lib64/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/libpthread.so.0
Comme vous l’avez probablement remarqué, la variable base_cmd est présente dans l’environnement. Celle-ci correspond à la liste des commandes à installer dans l’environnement dédié à goose:
---
# vars file for roles/chroot
# [...]
# Commandes qui seront importées dans l'environnement (dépendences incluses)
base_cmd: [bash, ls, cat, mkdir, touch, echo, ls, rm]
Chacune des commandes contenues dans la variable base_cmd va être appelée en paramètre du script import_lib.sh suivant:
#!bin/sh
ChrootPath="/home/SSH_JAIL"
BinPath="/bin"
# Import cmd (bin + dependencies) to environment
for cmd in "$@"
do
# Get all the dependencies of the current command
list="$(ldd ${BinPath}/${cmd} | egrep -o '/lib.*\.[0-9]')"
# Var for binaries
SRC="${BinPath}/${cmd}"
DEST="${ChrootPath}${BinPath}/${cmd}"
# Import binary
cp -rv "${SRC}" "${DEST}"
for lib in ${list}
do
# Var for libs
SRC="${lib}"
DEST="${ChrootPath}${lib}"
# Import libs (dependencies)
mkdir -p `dirname ${DEST}`
cp -v "${SRC}" "${DEST}"
done
done
Ce script est dans un premier temps importé. Puis appelé successivement sur chacune des commandes de base_cmd via la task ansible. Enfin, il est supprimé de la machine distante:
# Ajout du script qui permet l'ajout de commandes dans l'environnement (binaires + dépendences)
- name: Import script import_lib.sh
copy:
src: "import_lib.sh"
dest: "{{ root_dir }}"
backup: yes
# Toutes les commandes renseignées sont importées via le script précédemment importé
- name: Import des commandes dans l'environnement
command: "sh {{ root_dir }}/import_lib.sh {{ item }}"
loop:
"{{ base_cmd }}"
# On enlève le script, car uniquement utile lors de la création de l'environnement
- name: Effacement du script import_lib.sh
file:
path: "{{ root_dir }}/import_lib.sh"
state: absent
Voici le code complet détaillé dans cette partie:
---
# tasks file for roles/chroot
###########################################################################
# CHROOT GOOSE
###########################################################################
# Creation des fichiers d'environnement
- name: Change file ownership, group and permissions
file:
path: "{{ item.path }}"
recurse: yes
state: directory
group: "{{ item.group }}"
owner: "{{ item.owner }}"
mode: "{{ item.mode }}"
with_items:
# Les fichiers d'environnement doivent avoir des droits d'accès spécifiques (root)
- { path: "{{ chroot_dir }}", group: "root", owner: "root", mode: "0755" }
- { path: "{{ dev_dir }}", group: "root", owner: "root", mode: "0755" }
- { path: "{{ bin_dir }}", group: "root", owner: "root", mode: "0755" }
- { path: "{{ etc_dir }}", group: "root", owner: "root", mode: "0755" }
- { path: "{{ user_dir }}", group: "goose", owner: "goose", mode: "0700" }
# Création de fichier spéciaux en mode FIFO, et caractères sans tampon
- name: Creation des nodes
command: "mknod -m 666 {{ item.file_path }} c {{ item.param1 }} {{ item.param2 }}"
ignore_errors: yes
with_items:
- { file_path: "{{ node_nul }}", param1: "1", param2: "3" }
- { file_path: "{{ node_tty }}", param1: "5", param2: "0" }
- { file_path: "{{ node_zero }}", param1: "1", param2: "5" }
- { file_path: "{{ node_random }}", param1: "1", param2: "8" }
# Ajout des données de groupes et de mots de passe dans l'environnement
- name: Import des groupes et utilisateurs
copy:
src: "/etc/{{ item }}"
dest: "{{etc_dir}}/{{ item }}"
backup: yes
with_items:
- "passwd"
- "group"
# Ajout du script qui permet l'ajout de commandes dans l'environnement (binaires + dépendences)
- name: Import script import_lib.sh
copy:
src: "import_lib.sh"
dest: "{{ root_dir }}"
backup: yes
# Toutes les commandes renseignées sont importées via le script précédemment importé
- name: Import des commandes dans l'environnement
command: "sh {{ root_dir }}/import_lib.sh {{ item }}"
loop:
"{{ base_cmd }}"
# On enlève le script, car uniquement utile lors de la création de l'environnement
- name: Effacement du script import_lib.sh
file:
path: "{{ root_dir }}/import_lib.sh"
state: absent
Voici l’architecture de l’environnement ainsi créée:
/home/SSH_JAIL/
├── bin
│  ├── bash
│  ├── cat
│  ├── echo
│  ├── ls
│  ├── mkdir
│  ├── rm
│  └── touch
├── dev
│  ├── null
│  ├── random
│  ├── tty
│  └── zero
├── etc
│  ├── group
│  └── passwd
├── home
│  └── goose
├── lib
│  └── x86_64-linux-gnu
│  ├── libc.so.6
│  ├── libdl.so.2
│  ├── libpcre2-8.so.0
│  ├── libpthread.so.0
│  ├── libselinux.so.1
│  └── libtinfo.so.6
└── lib64
└── ld-linux-x86-64.so.2
8 directories, 20 files
L’utilisateur goose est au régime d’authentification par défaut. Il est donc possible de créer un groupe global, qui couvrira tous les utilisateurs et groupes de la machine, à l’exception des spécifications retenues précédemment.
De la même façon que précédemment, un bloc Match peut être créé dans le fichier sshd_config, comprenant tuos les utilisateurs sauf les exceptions.
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 reconnaître avant d’ajouter le bloc de texte au fichier. S’il reconnaît les balises, il vérifie les différences de texte entre le bloc du fichier, et celui du script. S’il sont 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’ensemble des utilisateurs (sauf le groupe root, l’utilisateur maverik et l’utilisateur charlie) les moyens d’authentification requis.
La ligne suivante permet de spécifier la configuration par défaut:
Match Group *,!root User *,!maverik,!charlie
La ligne suivante permet de contraindre l’authentification par défaut à ssh aux (ici, clé publique):
AuthenticationMethods publickey
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 fichiers 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
En plus des règles d’authentification, il faut indiquer au service ssh l’environnement par défaut qui sera utilisé lors de la création d’une session. Ceci est rendu possible grâce au paramètre ChrootDirectory dans le fichier de configuration sshd_config:
ChrootDirectory /home/SSH_JAIL
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 générale
- { name: "Global", header: "Group *,!root User *,!maverik,!charlie", config_lines: "ChrootDirectory /home/SSH_JAIL\n\tAuthenticationMethods publickey"}
notify:
# On relance le service ssh (sshd)
- Handler_restart_ssh
Lancer la commande:
$ ssh -v goose@<IP>
L’authentification par clé publique a été faite avec succès (lignes 2,6,7):

Il s’agit d’un environnement “virtuel” (chroot) dont voici quelques commandes ajouté précédemment avec le script ansible:

