Création — Nicolas THOREZ 2024/03/19 10:29
Modification — Nicolas THOREZ 2024/03/26 16:26
Une infrastructure à clés publiques ou PKI (Public Key Infrastructure) est un ensemble de services permettant de gérer des clés publiques et se faisant, permettant l'authentification d'un service, système ou utilisateur.
Réf : Wikipédia
Dans cette procédure, nous allons voir la mise en place d'une PKI via le logiciel EJBCA (Enterprise JavaBeans Certificate Authority) en version Community.
root. La plus grande prudence est donc requise.Réf : EJBCA
apt install -y ca-certificates curl
install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc chmod a+r /etc/apt/keyrings/docker.asc echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ tee /etc/apt/sources.list.d/docker.list > /dev/null
apt update apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
mkdir -p /opt/ejbca/containers/datadbdir cd /opt/ejbca/containers/
nano docker-compose.yml
version: '3'
networks:
access-bridge:
driver: bridge
application-bridge:
driver: bridge
services:
ejbca-database:
container_name: ejbca-database
image: "library/mariadb:latest"
networks:
- application-bridge
environment:
- MYSQL_ROOT_PASSWORD=foo123
- MYSQL_DATABASE=ejbca
- MYSQL_USER=ejbca
- MYSQL_PASSWORD=ejbca
volumes:
- ./datadbdir:/var/lib/mysql:rw
ejbca-node1:
hostname: ejbca-node1
container_name: ejbca
image: keyfactor/ejbca-ce:latest
depends_on:
- ejbca-database
networks:
- access-bridge
- application-bridge
environment:
- DATABASE_JDBC_URL=jdbc:mariadb://ejbca-database:3306/ejbca?characterEncoding=UTF-8
- LOG_LEVEL_APP=INFO
- LOG_LEVEL_SERVER=INFO
- TLS_SETUP_ENABLED=simple
ports:
- "80:8080"
- "443:8443"
MYSQL_ROOT_PASSWORD (le mot de passe pour la base de données de la PKI).
docker compose up -d
docker compose logs -f
ejbca | 2024-03-19 12:51:19,457+0000 INFO [/opt/keyfactor/bin/start.sh] (process:1) Waiting 5 seconds before signaling application readiness to ensure proper handling of PublicAccessAuthenticationToken. ejbca | 2024-03-19 12:51:24,471+0000 INFO [/opt/keyfactor/bin/start.sh] (process:1) Health check now reports application status at /ejbca/publicweb/healthcheck/ejbcahealth ejbca | 2024-03-19 12:51:24,484+0000 INFO [/opt/keyfactor/bin/start.sh] (process:1) ***************************************************************************************** ejbca | 2024-03-19 12:51:24,484+0000 INFO [/opt/keyfactor/bin/start.sh] (process:1) * * ejbca | 2024-03-19 12:51:24,484+0000 INFO [/opt/keyfactor/bin/start.sh] (process:1) * A fresh installation was detected and a ManagementCA was created for your initial * ejbca | 2024-03-19 12:51:24,484+0000 INFO [/opt/keyfactor/bin/start.sh] (process:1) * access to the system. * ejbca | 2024-03-19 12:51:24,484+0000 INFO [/opt/keyfactor/bin/start.sh] (process:1) * * ejbca | 2024-03-19 12:51:24,484+0000 INFO [/opt/keyfactor/bin/start.sh] (process:1) * URL: https://ejbca-node1:443/ejbca/adminweb/ * ejbca | 2024-03-19 12:51:24,484+0000 INFO [/opt/keyfactor/bin/start.sh] (process:1) * * ejbca | 2024-03-19 12:51:24,484+0000 INFO [/opt/keyfactor/bin/start.sh] (process:1) * If you use different port mapping or a proxy, please adapt the URL above accordingly. * ejbca | 2024-03-19 12:51:24,484+0000 INFO [/opt/keyfactor/bin/start.sh] (process:1) * * ejbca | 2024-03-19 12:51:24,484+0000 INFO [/opt/keyfactor/bin/start.sh] (process:1) *****************************************************************************************
Pour gérer plus facilement l'état de l'instance (démarrage, redémarrage, arrêt, status), j'ai créé le script suivant :
touch /opt/ejbca/manage-ejbca-daemon.sh chmod +x /opt/ejbca/manage-ejbca-daemon.sh nano /opt/ejbca/manage-ejbca-daemon.sh
#!/bin/bash
##########################################
# #
# Script de gestion du service EJBCA #
# #
##########################################
#===========#
# Variables #
#===========#
# Dossier de travail
WRKDIR=/opt/ejbca/containers
# Nom de l'instance ebjca
EJBCA=ejbca
# Nom de l'instance ebjca-database
EJBDB=ejbca-database
#===========#
# Fonctions #
#===========#
# Demande d'aide
get-help() {
echo "Script de gestion du service EJBCA"
echo ""
echo "Usage : ./manage-ejbca-daemon.sh [backup|help|restart|start|status|stop|upgrade]"
echo ""
echo "backup Sauvegarde la base de données."
echo "help Affiche cette aide."
echo "restart Redémarre l'instance."
echo "start Démarre l'instance."
echo "status Affiche l'état de l'instance."
echo "stop Arrête l'instance."
echo "upgrade Met à jour l'instance."
}
#=======================#
# Gestion des arguments #
#=======================#
case $1 in
"start"|"restart"|"stop"|"status"|"upgrade"|"backup")
ACTION="$1"
;;
"help")
get-help
exit 0
;;
*)
echo "Erreur : Argument $1 inconnu"
echo ""
get-help
exit 2
;;
esac
#============#
# Traitement #
#============#
EJBCA_STATUS=$(docker container inspect -f '{{.State.Running}}' $EJBCA 2>&1)
EJBDB_STATUS=$(docker container inspect -f '{{.State.Running}}' $EJBDB 2>&1)
if [ "$EJBCA_STATUS" == "true" -a "$EJBDB_STATUS" == "true" ]; then
STATUS=0
elif [ "$EJBCA_STATUS" == "true" -a "$EJBDB_STATUS" != "true" ]; then
STATUS=1
elif [ "$EJBCA_STATUS" != "true" -a "$EJBDB_STATUS" == "true" ]; then
STATUS=2
else
STATUS=3
fi
case $ACTION in
"start")
# Vérification de l'état et action
case $STATUS in
0)
# Démarré
echo "Instance déjà démarré"
CODE=1
;;
1|2)
# Partiel
echo "Redémarrage de l'instance"
cd $WRKDIR && docker compose restart
CODE=0
;;
3)
# Arrêté
echo "Démarrage de l'instance"
cd $WRKDIR && docker compose up -d
CODE=0
;;
*)
# Autre
echo "Erreur status : $STATUS"
CODE=3
;;
esac
;;
"stop")
# Vérification de l'état et action
case $STATUS in
0|1|2)
# Démarré
echo "Arrêt de l'instance"
cd $WRKDIR && docker compose down
CODE=0
;;
3)
# Arrêté
echo "Instance déjà à l'arrêt"
CODE=1
;;
*)
# Autre
echo "Erreur status : $STATUS"
CODE=3
;;
esac
;;
"restart")
# Vérification de l'état et action
case $STATUS in
0)
# Démarré
echo "Redémarrage de l'instance"
cd $WRKDIR && docker compose restart
CODE=0
;;
1|2)
# Partiel
echo "Instance partiellement démarrée, redémarrage complet..."
cd $WRKDIR && docker compose down && docker compose up -d
CODE=1
;;
3)
# Arrêt
echo "Instance à l'arrêt, démarrage..."
cd $WRKDIR && docker compose up -d
CODE=1
;;
*)
# Autre
echo "Erreur status : $STATUS"
CODE=3
;;
esac
;;
"status")
# Vérification de l'état et action
case $STATUS in
0)
# Démarré
echo "Autorité de certification et base de données actives"
CODE=0
;;
1)
# Base de données à l'arrêt
echo "Autorité de certification active mais la base de données est à l'arrêt"
CODE=1
;;
2)
# Authorité à l'arrêt
echo "Base de données active mais l'autorité de certification est à l'arrêt"
CODE=1
;;
3)
# Arrêt
echo "Autorité de certification et base de données à l'arrêt"
CODE=2
;;
*)
# Autre
echo "Erreur status : $STATUS"
CODE=3
;;
esac
;;
"backup")
# Vérification de l'état et action
case $STATUS in
0)
# Démarré
echo "Sauvegarde en cours..."
Version=$(curl -k -s https://localhost/ejbca/ejbca-rest-api/v1/certificate/status | jq .revision | awk '{print $2}')
UserName=$(grep 'MYSQL_USER' $WRKDIR/docker-compose.yml | cut -d'=' -f2)
PassWord=$(grep 'MYSQL_PASSWORD' $WRKDIR/docker-compose.yml | cut -d'=' -f2)
cd $WRKDIR && docker compose exec $EJBDB mysqldump ejbca -u${UserName} -p${PassWord} > ejbca-${Version}-backup-$(date +%Y%m%d_%H%M_%Z).sql
if [[ $? -eq 0 ]]; then
# Sauvegarde réussie
echo "Sauvegarde réussie"
CODE=0
else
# Sauvegarde raté
echo "Sauvegarde en échec"
CODE=2
fi
;;
*)
# Autres
echo "L'instance n'est pas correctement démarrée. Sauvegarde impossible."
CODE=2
;;
esac
;;
"upgrade")
# On arrête l'instance
echo "Arrêt de l'instance"
cd $WRKDIR && docker compose down
# Mise à jour
echo "Téléchargement de la dernière version"
cd $WRKDIR && docker image pull keyfactor/ejbca-ce:latest
# Redémarrage de l'instance
echo "Redémarrage et mise à jour"
cd $WRKDIR && docker compose up -d
;;
*)
# Autre
echo "Erreur action : $ACTION"
CODE=3
;;
esac
#========#
# Sortie #
#========#
exit $CODE
Pour que l'instance démarre automatiquement au démarrage de la VM, on édite alors la crontab de root :
crontab -e
Et on ajoute la ligne suivante :
@reboot /opt/ejbca/manage-ejbca-daemon.sh start
De mon côté, je pars toujours du fait qu'un administrateur a toujours tous les droits. Lorsque les droits sont limités, on est alors sur un profils d'utilisateur (User) ou d'utilisateur privilégié (PowerUser).
Du coup, dans la suite de cette procédure, j'utiliserais les termes suivant :
Se souvenir de cette décision et refuser cet envoi. Ce message peut revenir plusieurs fois tant que l'authentification n'est pas paramétrée :
RA Web :
Request new certificate et on cliquera sur Make New Request :
Select Request Template, on choisit les options suivantes :ENDUSER (default)By the CA
Select key algorithm, on choisira :RSA 2048 bits
Provide request info, on entre les informations :
Provide User Credentials, on entre les informations :
Confirm request, on trouvera le résumé de nos paramétrages et on pourra télécharger le certificat en cliquant sur Download PKCS#12 :
Firefox en tant que navigateur internet. La procédure est donc basée sur ce dernier mais reste relativement similaire sur les autres navigateurs.Firefox, on ira donc dans le menu Paramètres puis Vie privée et sécurité et enfin Afficher les certificats… :
Vos certificats et on clique sur Importer… :
Connexion :
EJBCA et on clique sur Roles and Access Rules :
Public Access Role et on édite les membres de Super Administrator Role en cliquant sur Members :
X509: CN, Common name.ManagementCA.CN, Common Name lors de la création du certificat. Attention, ce champs est sensible à la casse.Add :
/opt/ejbca/manage-ejbca-daemon.sh restart cd /opt/ejbca/containers/ && docker compose logs -f | grep 'Health ckeck now reports application'
Firefox a enregistré la décision de ne pas en envoyer. Pour réinitialiser cette décision, il suffit de supprimer l'entrée correspondante à l'adresse du serveur dans le gestionnaire de certificats de Firefox (ouvert lors de l'enregistrement du certificat), onglet Décisions d'authentification. Une fois la réinitialisation faîte, on peut retenter la connexion.Roles and Access Rules, et Members de Super Administrator Role.PublicAccessAuthenticationToken :
Si on souhaite interagir avec les autorités de certifications via API (pour de l'automatisation par exemple), il convient de le configurer le service et l'utilisateur au niveau de l'interface d'administration :
System Configuration, Protocol Configuration et on active REST Certificate Management avec le bouton Enable :
script, comme on l'a fait pour l'administrateur.Roles and Access Rules et on crée un rôle en cliquant sur Add :
Add :
Access Rules :
Advanced Mode :
| Catégorie | Droits | Autorisation |
|---|---|---|
| Role Based Access Rules | /administrator/ | Allow |
| Regular Access Rules | /ca_functionality/create_certificate/ | Allow |
| /ca_functionality/use_username/ | Allow | |
| /ra_functionality/edit_end_entity/ | Allow | |
| /ra_functionality/revoke_end_entity/ | Allow | |
| CA Access Rules | /ca/<votre autorité intermédiaire>/ | Allow |
| End Entity Profile Access Rules | /endentityprofilesrules/<votre profil de certificat TLS>/ | Allow |
| /endentityprofilesrules/<votre profil de certificat TLS>/approve_end_entity/ | Deny | |
| /endentityprofilesrules/<votre profil de certificat TLS>/delete_end_entity/ | Deny | |
| /endentityprofilesrules/<votre profil de certificat TLS>/view_end_entity/ | Deny | |
| /endentityprofilesrules/<votre profil de certificat TLS>/view_end_entity_history/ | Deny |
Save en bas de page.Members :
X509: CN, Common name.ManagementCA.script dans notre cas.Add :
Pour la supervision dans Nagios, nous allons contrôler 4 éléments :
docker.Ci-dessous, je dépose les scripts que j'utilise pour les différents contrôles
On se base sur les plugins de nagios avec la commande :
/usr/lib/nagios/plugins/check_procs -w 1:1 -c 0: -C dockerd
J'utilise le script de Tim Laurence (Source : GitHub) pour contrôler l'état des containers. Une fois le script installé, il suffit de lancer la commande suivante :
/usr/lib/nagios/plugins/check_docker.py --no-ok --status running --containers ejbca ejbca-database
La PKI réalise une vérification automatique de son état de santé. Son rapport est affiché à l'URI /ejbca/publicweb/healthcheck/ejbcahealth. Le script suivant interroge cette adresse et renvoie l'information au format NRPE :
#!/bin/bash
# Adresse du serveur
ServerAddress="localhost"
# URI de l'état de santé
Uri="/ejbca/publicweb/healthcheck/ejbcahealth"
# Requête
Request=$(curl -k -s https://${ServerAddress}${Uri})
# Traitement du résultat de la requête
if [ "$Request" == "ALLOK" ]; then
# Tout va bien
Status="GOOD"
ExitCode=0
else
# PKI en erreur
Status="CRIT"
ExitCode=2
fi
# Renvoi des informations
echo "$Status - Health status : $Request"
exit $ExitCode
On vérifie la présence de mise à jour en comparant le hash de la version actuelle de notre PKI avec le hash de la dernière version.
#!/bin/bash
# Adresse du serveur
ServerAddress="localhost"
# Uri du status
ServerUri="/ejbca/ejbca-rest-api/v1/certificate/status"
# Nom du container
Container="keyfactor/ejbca-ce"
# Récupération de la version actuelle
Actual=$(curl -k -s https://${ServerAddress}${ServerUri} | jq .revision | awk '{print $2}')
# Récupération du hash de la version actuelle
ActualHash=$(docker manifest inspect ${Container}:${Actual} | jq .config.digest)
# Récupération du hash de la dernière version
LatestHash=$(docker manifest inspect ${Container}:latest | jq .config.digest)
# Test et retour
if [ "$ActualHash" == "$LatestHash" ]; then
# Les hash sont les mêmes, on est à jour
echo "GOOD - up-to-date"
exit 0
else
# Une mise à jour est disponible
echo "WARN - update available"
exit 1
fi
On peut désormais créer des autorités de certifications : EJBCA - Création de certificats racine et intermédiaire