Aller au contenu

🛠️ Cas d'usage avec CISO Assistant et Grist

Présentation du cas d'usage

Ciso Assistant m'a permis de supprimer en grande partie la segmentation documentaire en intégrant l'analyse des risques issue du travail formidable de David Soria fondateur d'Astar (il n'a pas besoin de publicité, j'en profite juste pour vous le conseiller dans vos projets Cybersécurité).

J'ai commencé ensuite à intégrer les mesures de sécurité en m'appuyant sur le guide d'hygiène de l'ANSSI. Pas évident de dresser les mesures de sécurité from scratch même si je partais d'un squelette. Entre temps l'équipe de Ciso Assistant a sorti récemment la version détaillée du guide avec les mesures de sécurités correspondantes 🥹😍.

Cela m'a obligé de revoir et repenser ma copie concernant l'approche par la conformité. Concernant la segmentation documentaire, un point noir résidé sur la gestion des preuves : comment suivre l'ensemble des preuves ? Manuellement ? C'est ce que j'ai fait au début, mais le nombre de preuves et les preuves elles-même évoluant puis le fait que j'occupe plusieurs fonctions, je me suis vite rendu compte que ce n'était pas possible. C'est à ce moment-là que j'ai pris pleinement conscience d'utiliser l'API de Ciso Assistant dont voici mon premier cas d'usage.

Dans le cadre de mon travail, je me dois de tenir un tableau des permissions d'accès au serveur de fichiers à jour. Jusqu'à présent cela se faisait sur des fichiers excel. Cependant nous arrivions à une segmentation documentaire des preuves trop importante et ne facilitant pas le suivi pour mes équipes et moi-même. J'ai donc basculé cette gestion sur Grist.

Cette application présente plusieurs fonctionnalités dont celle de fournir une API tout comme Ciso Assistant.

L'idée a donc été d'exploiter les deux API afin de verser automatiquement le tableau des permissions d'accès au serveur de fichiers issu de Grist dans la preuve correspondante sur Ciso Assistant.

Environnement

  • Système GNU/Linux
  • VM Proxmox VE faisant tourner Docker
  • Conteneur docker Grist
  • Conteneur docker Ciso Assistant
  • Python

Principe de fonctionnement

Voici le principe dans les grandes lignes :

  1. Le script pyhton est lancé par une tâche CRON,
  2. il intérroge l'API de Grist afin de récupérer le tableau des permissions au format xlsx,
  3. il nettoie ce fichier en ne conservant que les colonnes et feuilles pertinentes,
  4. il exporte dans Ciso Assistant le fichier sur la preuve concernée.
schema

Contrainte: Le script ne doit exporter le fichier que si des permissions ont été crées ou mises à jour. Pour cela, les permissions sont déclarées dans un tableau sur Grist contenant une colonne nommée Export_Ciso_Assistant de type booléen (True/False). Si ce champs est à False, cela indiquera au script de mettre à jour le fichier sur Ciso Assistant. Une fois le fichier importé dans Ciso Assistant, le script mettra alors ce champs à True. Si aucun champs n'est à False, alors le script n'exporte pas le tableau depuis Grist.

Disclaimer

Je ne suis pas dev, il y aura certainement des choses à redire/à améliorer sur le script.

Je débute sur Grist.

Généralement à un problème informatique, plusieurs solutions possibles. Il se peut donc que la mienne ne soit pas la meilleure.

Tableau des permissions sur Grist

Les entêtes du tableau sont celles-ci :

tableau-permissions

Seule la colonne "Export Ciso Assistant" ne sera pas reprise dans le fichier xlsx généré plus tard.

Script Python

preuve-registre-access-srv-file.py
import requests
import json
from openpyxl import load_workbook
import logging

logging.basicConfig(level=logging.INFO)

## FONCTIONS ###################################################

def main():
    try:
        # Étape 1 : on récupère la table contenant les permissions
        liste_permissions = recup_permissions(gristUrl, gristDocument, gristTable, gristToken)
        if not liste_permissions or len(liste_permissions.get('records', [])) == 0:
            logging.info("Pas de nouvelles permissions détectées")
            return

        logging.info(f"Présence de {len(liste_permissions['records'])} permission(s) à traiter.")

        # Étape 2 : On exporte le document au format Excel depuis Grist
        if not export_permissions(gristUrl, gristDocument, gristToken, fichierExcel):
            logging.error("Échec de l'export des permissions vers Grist.")
            return

        logging.info("Export du document au format Excel depuis Grist réussi.")

        # Étape 3 : On nettoie le fichier Excel en supprimant les informations non pertinantes
        if not nettoyage_tableau_excel(fichierExcel):
            logging.error("Échec du nettoyage du fichier Excel.")
            return

        logging.info("Nettoyage du fichier Excel réussi.")

        # Étape 4 : On se connecter à Ciso et on importe le fichier excel nettoyé dans la preuve concernée
        cisoToken = connect_ciso(LoginCisoUrl, cisoUser, cisoPassword)
        if not cisoToken:
            logging.error("Connexion à Ciso échouée.")
            return

        if not import_ciso(cisoEvidenceUrl, cisoToken, fichierExcel):
            logging.error("Échec de l'importation dans Ciso.")
            return

        logging.info("Importation dans Ciso réussie.")

        # Étape 5 : On met à jour le statut de la colonne Export_Ciso_Assistant sur Grist
        for permission in liste_permissions['records']:
            update_ciso_statut(gristUrl, gristDocument, gristTable, gristToken, permission['id'])

        logging.info("Mise à jour de la colonne Export_Ciso_Assistant sur Grist réussie.")

    except Exception as e:
        logging.exception(f"Une erreur inattendue est survenue : {e}")

# Récupération de l'ensemble des enregistrements de la table requêtée
def recup_permissions(url,doc,table,token):
    try:
        url = f"{url}{doc}{table}"
        params = {
            "filter": '{"Export_Ciso_Assistant": [false]}'
        }
        headers = {
            "accept": "application/json",
            "Authorization": f"Bearer {token}"
        }
        reponse = requests.get(url, headers=headers, params=params)
        return reponse.json()
    except ValueError:
        print("Échec de la connexion:", response.status_code, response.text)
        exit()

# Export du tableau excel des permissions
def export_permissions(url,doc,token,fichier):
    url = f"{url}{doc}/download/xlsx"
    headers = {
        "accept": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        "Authorization": f"Bearer {token}",
    }
    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        with open(fichier, "wb") as file:
            file.write(response.content)
        return True
    else:
        return False

def nettoyage_tableau_excel(fichier):
    try:
        # Charger le fichier Excel existant
        wb = load_workbook(fichier)

        # Suppression des onglets non nécessaire du fichier récupéré
        onglets_to_delete = ["📁 Répertoires","Procedure","🛠️Paramètres","Poles","Services","🛡️Comptes avec privilèges"]
        for onglet in onglets_to_delete:
            if onglet in wb.sheetnames:
                ws = wb[onglet]
                wb.remove(ws)

        # Suppresion de la colonne "Export Ciso Assistant" contenue sur la feuille "Permissions"
        if "Permissions" in wb.sheetnames:
            ws = wb["Permissions"]
            # Trouver l'index de la colonne contenant "Export Ciso Assistant"
            for col in ws.iter_cols(min_row=1, max_row=1):  # Parcourir les colonnes de la première ligne
                if col[0].value == "Export Ciso Assistant":
                    ws.delete_cols(col[0].column)  # Supprimer la colonne
                    break
        else:
            print("La feuille 'Permissions' n'existe pas dans le fichier.")

        # Suppresion des colonnes "AgentBis" et "DUP" contenue sur la feuille "👥 Utilisateurs et groupes"
        if "👥 Utilisateurs et groupes" in wb.sheetnames:
            ws = wb["👥 Utilisateurs et groupes"]
            liste_colonnes = ["AgentBis","DUP"]
            for colonne in liste_colonnes:
                for col in ws.iter_cols(min_row=1, max_row=1):  # Parcourir les colonnes de la première ligne
                    if col[0].value == colonne:
                        ws.delete_cols(col[0].column)  # Supprimer la colonne
                        break
        else:
            print("La feuille '👥 Utilisateurs et groupes' n'existe pas dans le fichier.") 

        wb.save(fichier)
        return True
    except ValueError:
        return False

def connect_ciso(url,user,password):
    login_url = url
    login_headers = {
        'accept': 'application/json',
        'Content-Type': 'application/json'
    }
    login_data = {
        "username": user,
        "password": password
    }
    response = requests.post(login_url, headers=login_headers, json=login_data)
    if response.status_code == 200:
        token = response.json().get('token')
        return token
    else:
        print("Échec de la connexion:", response.status_code, response.text)
        exit()

# Connexion à Ciso Assistant pour récupérer le token
def import_ciso(url,token,fichier):
    upload_url = url
    upload_headers = {
        'Authorization': f'Token {token}',
        'accept': 'application/json',
        'Content-Type': 'document',
        'Content-Disposition': f'attachment; filename={fichier}'
    }

    file_path = fichier
    with open(file_path, 'rb') as file:
        response = requests.post(upload_url, headers=upload_headers, data=file)
    if response.status_code == 200:
        return True
    else:
        return False

# Mise à jour du champs Export_Ciso_Assistant dans Grist
def update_ciso_statut(url,doc,table,token,idPermission):
    try:
        url = f"{url}{doc}{table}"
        datas = {"records": [ { "id": idPermission, "fields": { "Export_Ciso_Assistant": "true" } } ]}
        headers = {
            "accept": "*/*",
            "Content-Type": "application/json",
            'Authorization': f'Bearer {token}'
        }
        reponse = requests.patch(url, headers=headers, json=datas)
        return True
    except ValueError:
        return False

## VARIABLES ###################################################

# Grist
gristUrl = "https://grist.domaine.lab/api"
# Token à récupérer dans les paramètres du compte utilisateur
gristToken = "3a2ad19e24gsf6598422d6sdsdsdsd500f5f72a8f468" # 
gristDocument = "/docs/qD6gPyXKtrWvHCKWQbKtCG"
gristTable = "/tables/Permissions/records"
fichierExcel = "grist-acces-srv-fichiers.xlsx"

# CISO Assistant
cisoUrl = "https://ciso.domaine.lab"
LoginCisoUrl = f"{cisoUrl}/api/iam/login/"
cisoUser = "LOGIN@MAIL.COM"
cisoPassword = "PASSWORD"
cisoEvidenceUrl = f"{cisoUrl}/api/evidences/b828ad59-4a2a-4b80-9c0a-a79e5b1d6733/upload/"

## SCRIPT ######################################################

if __name__ == "__main__":
    main()

Les émojis contenus dans le script ne sont pas des interprétations. Grist permet de mettre des emojis rendant l'interface plus sympa.

Exécution du script
python3 getPermissions2.py 
INFO:root:Présence de 10 permission(s) à traiter.
INFO:root:Export du document au format Excel depuis Grist réussi.
INFO:root:Nettoyage du fichier Excel réussi.
INFO:root:Importation dans Ciso réussie.
INFO:root:Mise à jour de la colonne Export_Ciso_Assistant sur Grist réussie.

Une fois le script exécuté, je retrouve bien mon fichier sur CISO Assistant.

CISO Assistant preuve

Le nom du fichier a été modifié par CISO Assistant étant donné que c'était la énième preuve que j'importais dans l'application.

Conclusion

Voilà pour ce premier cas d'usage qui est aussi un premier jet qui j'espère vous inspirera et vous convaincra de passer à CISO Assistant et vous tentera de tester Grist, si ce n'est pas déjà le cas.

Dans ce cas, j'utilise Python, mais j'envisage à termes de basculer sur une application de workflows d'automatisation comme Active Pieces qui est open-source puisqu'il semblerait que n8n ne soit plus open source. Cela éviterai un peu trop de boulot sur la maintenance des scripts. Cela dépendra également de l'intégration de CISO Assistant dans Active Pieces 😉.

J'ai commencé à travailler également sur la conformité des règles d'accès. En effet, le tableau des permissions étant déclaratif, il me semble nécessaire de vérifier l'état des permissions déclarées, là aussi de manière automatique.

J'en profite pour remercier les équipes de Grist et de CISO Assistant de rendre accessible leurs applications à tout le monde. Et merci particulièrement à Abderrahmane Smimite pour ton soutien et ta reconnaissance de l'humble contributeur que je suis (et je dis ça sans fausse modestie).