Réalisation d'un moteur de recherche d'images via la mise en œuvre d'une API Python sécurisée autour du framework Flask

Herve Hoareau

Pour réagir au contenu de cet article, un espace de dialogue vous est proposé sur le forum. Commentez

- Titre : Réalisation d'un moteur de recherche d'images via la mise en œuvre d'une API Python sécurisée autour du framework Flask

- Auteur : Herve Hoareau

- Parution : 11 mars 2020

- Licence : Copyright ® 2020 Herve HOAREAU. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. PSE - Picture Search Engine

- II. Objectif du projet

- II-A. Le backend

- II-B. Le front-end

- III. Configuration du serveur de l'API

- IV. Sécurisation

- IV-A. Sécurisation du serveur : mise en place des certificats SSL

- IV-B. Sécurisation de l'API par gestion de tokens

- V. La base de données

- V-A. Installation

- V-B. Remarques

- V-C. Usage

- VI. Logique de fonctionnement de l'API

- VI-A. Principe

- VI-B. Fichier de configuration

- VI-B-1. Le fonctionnement des API : retourner des photos

- VI-B-2. La gestion des API : app.py

- VI-B-3. Authentification des appels à l'API : tools.py

- VI-B-4. La base de données : dao.py

- VII. Déploiement de l'API

- VII-A. Fabrication de l'image Docker

- VII-B. Installation de l'image

- VIII. Démonstrateur de l'API

- VIII-A. le mini front-end

- VIII-B. L'interface swagger

- IX. Références

- X. Remerciements Developpez.com

I - PSE - Picture Search Engine

Le Picture Search Engine est un métamoteur, open source, de recherche de photos de qualité.

II - Objectif du projet

L'objectif de cet article est de combiner plusieurs technologies éprouvées et reconnues pour illustrer la mise en œuvre d'une architecture client/serveur de type microservice.

La solution peut se décomposer en deux briques.

II-A - Le backend

- Est développé en Python. Un langage moderne dont le nombre de bibliothèques garantit de pouvoir s'attaquer à tout type de domaine d'activité avec une prédisposition pour la data et l'intelligence artificielle.

- L'API repose sur le framework d'architecture microservices Flask. Il est doté de nombreux plugins pour faciliter la réalisation d'API performantes et sécurisées.

- L'usage d'une base de données open source et NoSQL : MongoDB, calibrée pour le big data avec une garantie de performance et de scalabilité.

- La containerisation via Docker simplifiant le déploiement tout en permettant la scalabilité de l'ensemble via des outils comme kubernetes.

- L'utilisation du plugin Flask-Restplus permet de générer automatiquement une documentation de l'API et un outil de test performant et simple.

- La sécurisation, le suivi et l'éventuelle facturation de l'usage de l'API via la gestion de tokens d'accès.

- La sécurisation du serveur via l'usage d'un certificat SSL Let's Encrypt.

II-B - Le front-end

Pour l'instant, sous forme d'une page HTML minimale pour utiliser l'API et d'une interface d'interrogation via Swagger UI, il doit être porté dans un second temps vers un front-end multi-device :

- sous format Progressive Web App sur Angular 8 / Angular Material ;

- en typescript ;

- hébergé gratuitement sur les « GitHub Page ».

L'ensemble est très certainement optimisable, mais l'idée est de proposer une architecture de base pour simplifier sa reprise et adaptation à d'autres problématiques.

III - Configuration du serveur de l'API

A priori l'API peut être installée sur n'importe quel type d'OS supportant Python. Dans cet exemple, on utilise une distribution Linux : Fedora (v29).

Pour faire simple, toute l'installation va se faire en mode root ce qui n'est pas conseillé en environnement de production. Ainsi le répertoire "/root" va héberger :

- un sous-répertoire "/mongodata" destiné à stocker la base MongoDB ;

- un sous-répertoire "/certs" recevant une copie des certificats pour la connexion SSL.

Nous allons principalement utiliser des images sous Docker pour installer les différentes composantes du serveur. il faut donc commencer par installer le fameux gestionnaire de containers.

On trouve beaucoup de tutoriels sur l'installation de Docker suivant le système d'exploitation. Pour Linux, après s'être connecté en mode root, la commande suivante fonctionne le plus souvent sans intervention :

curl -sSL get.docker.com | sh

puis on démarre le démon et on l'installe pour un démarrage automatique :

systemctl start docker && systemctl enable docker

Normalement Docker est maintenant installé. On peut le vérifier par la commande docker ps qui affiche les images Docker présentes.

IV - Sécurisation

L'étape suivante consiste à :

- sécuriser le serveur pour permettre un appel de notre API via « https » ;

- authentifier les utilisateurs de l'API par l'usage d'un token.

IV-A - Sécurisation du serveur : mise en place des certificats SSL

L'usage du https pour héberger les sites est de plus en plus courant. Cela implique que les API utilisées par les front-end sécurisés doivent également utiliser le protocole 'https'. Pour autant, on peut faire fonctionner notre API sur un serveur non sécurisé. Dans ce cas, vous pouvez directement passer au chapitre suivant.

L'usage 'https' nécessite :

- la mise en place de certificats SSL ;

- l'interrogation du serveur via un nom de domaine.

Dans cet exemple, on utilise des certificats « Let's Encrypt ».

Vous devez paramétrer le DNS de votre domaine pour le faire pointer vers l'adresse IP de votre serveur. Le principe consiste à ajouter un « enregistrement A » au DNS du nom de domaine.

La procédure pas à pas dépend de votre fournisseur de noms de domaine et est souvent détaillée dans les FAQ. On peut également consulter les sites suivants :

- https://blog.youdot.io/fr/4-types-enregistrements-dns-a-connaitre/;

- https://docs.icodia.com/general/zones-dns.

Il faut, maintenant, fabriquer les certificats. Sur le serveur qui hébergera l'API, on installe « certbot » en suivant les instructions du site : https://certbot.eff.org/instructions.

Cet outil automatise la procédure d'installation des certificats via la commande :

certbot certonly --standalone -d sub.domaine.com

en remplaçant sub.domain.com par votre domaine.

Lors de son exécution, Let's Encrypt va chercher à joindre votre serveur via le port 80. Il faut donc s'assurer que l'accès est possible depuis l'extérieur (en particulier en regardant du côté des firewalls et/ou des redirections de ports).

Si tout se passe bien, vous récupérez plusieurs fichiers dans le répertoire /etc/letsencrypt/live/<sub.domain.com>.

Pour établir une connexion sécurisée, Flask utilise deux fichiers produits par certbot :

- "fullchain.pem" : contenant l'ensemble des certificats ;

- "privkey.pem" : la clé privée de votre certificat. À garder confidentielle en toutes circonstances et à ne communiquer à personne, quel que soit le prétexte.

Il faut rendre ces fichiers visibles à l'image Docker de notre serveur. On va donc les copier dans le répertoire /root/certs que l'on ouvrira à l'image via la commande "VOLUME"

mkdir /root/certs && cp /etc/letsencrypt/live/sub.domain.com/* /root/certs

Théoriquement, cette manipulation doit être réalisée chaque fois que l'on met à jour les certificats. Let's encrypt impose le renouvellement des certificats tous les trois mois. La procédure est automatisable via (voir https://certbot.eff.org/docs/using.html?highlight=renew#renewing-certificates), mais il faut également automatiser la copie. Vous pouvez également utiliser un certificat, souvent fourni gratuitement par le fournisseur du nom de domaine.

IV-B - Sécurisation de l'API par gestion de tokens

Flask permet l'ajout d'une couche de sécurité directement au niveau de l'API via l'usage de token pour identifier les utilisateurs (développeurs).

Pour aller plus loin, en particulier si l'on souhaite commercialiser l'API, il faudrait envisager l'installation de solutions de gestion d'API (API management). On trouve plusieurs produits pour faire cela. Certains sont propriétaires, d'autres open source comme le détaille cet article : 11 open-source tools to consider.

Dans notre cas, nous allons rester sur une gestion du token intégré au serveur.

La procédure d'obtention du token est réduite à sa plus simple expression, puisqu'il suffit d'appeler l'API "/auth" qui se charge d'enregistrer un username/password dans la base de données et de retourner le token d'authentification.

Dans une version plus industrialisée, on pourrait par exemple exiger un email comme nom d'utilisateur. À chaque appel, l'API se connectera à la base pour retrouver le compte via le token. Ainsi il est possible de vérifier que le développeur a les droits pour utiliser notre API.

Une autre option, non mise en œuvre ici, consiste à n'autoriser les appels à l'API que depuis une adresse spécifique.

V - La base de données

L'objectif de l'article est également de montrer un exemple d'implémentation d'une base de données moderne et courante dans l'univers Python. Pour cette raison, on installe MongoDB via la bibliothèque "pymongo" (https://api.mongodb.com/python/current/).

V-A - Installation

On doit commencer par installer une instance de MongoDB sur le serveur. Pour ne pas compliquer le processus d'installation, on choisit d'utiliser l'image "Docker" : https://hub.docker.com/_/mongo.

Rien de plus simple, puisqu'une seule commande suffit :

docker run --restart=always -v /root/mongodata:/data/db -d -p 27017-27019:27017-27019 --name mongodb -e MONGO_INITDB_ROOT_USERNAME=admin -e MONGO_INITDB_ROOT_PASSWORD=admin_password mongo

V-B - Remarques

Il faut retenir que :

- l'écriture dans la base depuis l'API se fera avec le user/mot de passe : admin/admin_password. Ce couple sera utilisé dans l'API pour se connecter à la base ;

- la base n'est pas nécessairement installée sur le même serveur que l'API ;

- le port de connexion à la base de données est celui par défaut, le 27017. Dans une optique industrielle, il peut être souhaitable de le modifier ;

- il est possible de se connecter via l'outil Mongo Compass (https://www.mongodb.com/products/compass) pour visionner le contenu de notre base depuis n'importe quel poste dès lors qu'on utilise bien les paramètres de connexion ci-dessus.

V-C - Usage

La bibliothèque Python utilisée pour interagir avec MongoDB doit être installée. MongoDB est une base orientée document.

Chaque enregistrement est donc un dictionnaire Python (type dict). Dans notre API, on utilise deux types d'objets :

- l'objet user est un simple dictionnaire (user,mot de passe) ;

- un objet log_entrie contient chaque appel aux API : le token, la date de l'appel et les paramètres.

VI - Logique de fonctionnement de l'API

VI-A - Principe

L'API est avant tout un prétexte pour illustrer l'architecture, pour autant elle pourrait servir de point de départ à un moteur de recherche d'images comme substitut à un Google image. Elle reçoit en paramètre un mot clé et interroge deux plateformes de référencement de photos : PixaBay et Unsplash. Ces deux plateformes exposent des API via leur portail développeurs.

Les résultats sont concaténés et renvoyés au front-end sous forme d'une suite d'URL.

REST repose sur quatre actions possibles sur les ressources manipulées. GET pour les récupérer, POST pour les ajouter, PUT pour mettre à jour une ressource existante, enfin DELETE pour supprimer une ressource. Plus d'infos sur REST se trouvent ici : https://blog.nicolashachet.com/niveaux/confirme/larchitecture-rest-expliquee-en-5-regles/. La bibliothèque RestPlus, implémente l'architecture REST sur flask. Elle consiste à représenter les ressources (au sens REST) par des classes Python. Les verbes, en particulier GET, celui qu'on va utiliser, est implémenté par une méthode du même nom de la classe représentant la ressource.

VI-B - Fichier de configuration

Dans une optique d'industrialisation, les paramètres du serveur sont regroupés dans un fichier YAML.
Même si ce format est moins courant que JSON, on gagne en lisibilité notamment via la possibilité d'ajouter des commentaires.

Il contient les paramètres nécessaires au fonctionnement du serveur, en particulier :

- le point de terminaison (endpoint) de notre API ;

- les paramètres d'accès aux plateformes de photos.

Ce fichier doit rester confidentiel. On aurait pu y ajouter les paramètres d'accès à la base, mais ces paramètres sont passés dans la commande Docker d'installation du serveur (voir rubrique sur l'installation).

##Principale fonction mise en œuvre Le code est assez court et abondamment commenté, donc facilement adaptable/"forkable". Dans la suite on le décrit dans les grandes lignes.

VI-B-1 - Le fonctionnement des API : retourner des photos

- Les fonctions queryUnsplash et queryPixabay ("tools.py") se chargent de l'interrogation des plateformes de photos. Elles nécessitent un enregistrement préalable sur leur portail développeurs afin d'y récupérer des clés d'usage. Voir https://pixabay.com/api/docs/ et https://unsplash.com/developers. Ces clés doivent être inscrites dans le fichier "settings.yaml" du projet.

- La ressource "Image" dont l'API "get" se charge de la consolidation des résultats des deux fonctions précédentes.

VI-B-2 - La gestion des API : app.py

La configuration des API, route et parsing des paramètres en particulier, est assurée par RestPlus via :

- la classe Image qui par, l'héritage de la classe ressource, fonctionne suivant les préceptes REST ;

- les décorateurs app.route, indiquant les routes pour l'appel des API ;

- le parser, se charge de décomposer les paramètres et contribue à produire la documentation pour swagger.

Dans notre serveur on n'a finalement besoin que d'une seule API (plus celle pour l'authentification). On pourrait en ajouter d'autres, mais celles-ci seraient construites sur à peu près le même modèle imposé par Flask-plus : une classe représente la ressource adressée et les méthodes GET, POST de cette classe implémentent la logique REST.

VI-B-3 - Authentification des appels à l'API : tools.py

La gestion des tokens d'authentification, en particulier encodage et décodage, est assuré par les fonctions createToken et decodeToken Le décorateur token_required vérifie la présence du token dans les API. Il reçoit l'instance de la base de données comme paramètre et peut ainsi récupérer le compte développeur et par exemple, vérifier les quotas et/ou les droits avant d'autoriser l'exécution.

VI-B-4 - La base de données : dao.py

La base de données est prise en charge par la classe DAO qui fait l'interface entre MongoDB et l'API, pour :

- ouvrir la connexion avec la base MongoDB via son constructeur ;

- gérer les utilisateurs des API (inscription et lecture dans la base pour confirmer la possibilité d'utiliser l'API) ;

- tracer l'ensemble des transactions (écritures à chaque appel, du token et de la date) ouvrant ainsi la possibilité d'une gestion de quotas et d'une éventuelle facturation.

VII - Déploiement de l'API

Là aussi, l'usage de Docker simplifie le déploiement de notre API.

VII-A - Fabrication de l'image Docker

Avant de fabriquer l'image, il est préférable de s'inscrire sur le hub docker. Après cette inscription vous disposez d'un espace pour stocker les images Docker que vous allez construire.

Une fois le code finalisé, stocké dans le fichier "App.py", le fichier Docker ("Dockerfile") permet la construction d'une image déployable du serveur d'API. Elle repose sur une distribution Linux relativement légère et disposant d'une image Python préinstallée.

Dans le répertoire GitHub, on propose deux fichiers Dockerfile. L'un permet de construire une image Docker installable sur un serveur de type x86. L'autre va permettre de déployer l'API sur un serveur ARM, Raspberry PI par exemple. Les deux images reposent sur la distribution Alpine compatible x86 et ARM.

Le fichier Dockerfile se charge d'installer sur l'image les bibliothèques nécessaires à l'exécution de l'API et d'installer les fichiers Python nécessaires dans le répertoire /app de l'image.

La commande pour construire l'image est simple :

docker build -t <votre_hub>/picturesearchenginex86

.

Elle doit être exécutée depuis le répertoire contenant le fichier "Dockerfile".

Une fois construite, on se connecte à son compte Docker pour pouvoir l'uploader.

docker login

Après connexion, on exécute la ligne suivante pour mettre en ligne notre image :

docker push <votre_hub>/picturesearchenginex86:latest

où <votre_hub> est à remplacer par votre compte, évidemment.

VII-B - Installation de l'image

Maintenant que notre image est disponible, on peut la récupérer et l'installer sur le serveur. Ainsi, l'installation de l'API se fera via la commande "pull".

docker pull <votre hub>/picturesearchenginex86:latest

pour lancer le serveur, il suffit d'utiliser la commande "run" de Docker

docker run --restart=always -v /root/certs:/app/certs -p 5600:5600 --name picturesearchenginex86 -d <votre hub>/picturesearchenginex86:latest localhost admin admin_password 5600 ssl

grâce à cette commande on a :

- téléchargé l'image picturesearchenginex86 fabriquée préalablement ;

- programmé le redémarrage automatique de l'API lorsque le serveur redémarre (--restart) ;

- ouvert l'accès aux certificats pour permettre à Flask de sécuriser les transactions (-v) ;

- ouvert le port 5600 pour la communication à notre API (-p).

Les derniers termes de la commande sont directement « passés » comme paramètres à l'API (récupérés par sys.args en Python) :

- ainsi, les paramètres de connexion à la base de données sont transmis à l'installation de l'image dans la commande Docker. Ici on a passé « localhost » en considérant que l'API et la base sont sur le même serveur et « admin »/« admin_password » comme utilisateur/password pour se connecter à la base ;

- enfin en terminant par « ssl », on configure l'API en mode sécurisé (il est possible de lancer l'API en mode non sécurisé en enlevant le paramètre ssl. Dans ce cas, l'étape de fabrication des certificats n'est pas nécessaire et l'API peut être jointe directement via l'adresse IP du serveur).

VIII - Démonstrateur de l'API

Un démonstrateur, disponible en ligne via une interface minimale, est accessible directement à l'adresse : https://pse.f80.fr.

Il est possible de récupérer l'image utilisée par le démonstrateur en exécutant, sur le serveur :

docker pull f80hub/picturesearchenginex86:latest && docker run --restart=always -v /root/certs:/app/certs -p 5600:5600 --name picturesearchenginex86 -d f80hub/picturesearchenginex86:latest localhost admin admin_password 5600 ssl

VIII-A - le mini front-end

L'interface se charge de :

- demander un token d'utilisation de l'API ;

- demander le mot clé de la recherche ;

- appeler l'API avec le mot clé et le token ;

- afficher le résultat.

Au lancement on peut lui passer trois paramètres pour lui indiquer où se trouvent le serveur et le port ouvert pour l'API : http://pse.f80.fr?server=<addresse_du_server>&port=<port_de_api>&endpoint=<endpoint>

Dans un prochain article, nous remplacerons ce fichier par une web application développée sur Angular.

VIII-B - L'interface swagger

Grâce à RestPlus, on dispose automatiquement d'une interface d'interrogation de l'API accessible via https://server.f80.fr:5800.

Pour l'utiliser il faut :

- obtenir un token par appel de la méthode "auth" ;

- l'inscrire dans la section « authentification » de Swagger UI ;

- puis appeler l'API en passant les paramètres souhaités.

Cette documentation repose sur l'usage de décorateurs au sein de notre code Python :

- @api.doc va être utilisé pour documenter le besoin d'une clé d'accès aux API ;

- @api.expect génère automatiquement une documentation des paramètres reposant sur un parser ;

- @api.param en charge de la documentation des paramètres utilisés par les API (n'utilisant pas un parser) ;

- @api.response en charge de la documentation des réponses retournées par l'API.

IX - Références

En plus des différents liens déjà cités, De nombreux articles disponibles sur le web, traitent des différentes briques impliquées dans l'API.

- L'installation de Docker : https://docs.docker.com/get-started/

- La documentation de l'extension Flask-restplus : https://flask-restplus.readthedocs.io/en/stable/

- L'installation de MongoDB : https://www.thepolyglotdeveloper.com/2019/01/getting-started-mongodb-docker-container-deployment/

- Comprendre les décorateurs sous Python : http://sametmax.com/comprendre-les-decorateurs-python-pas-a-pas-partie-1/

- Un article assez proche sur la mise en place d'une API via RestPlus : https://blog.invivoo.com/designer-des-apis-rest-avec-flask-restplus/

Article rédigé par Herve HOAREAU Société F80, Développement Full-Stack, transformation digitale et analyse de données herve.hoareau@f80.fr

X - Remerciements Developpez.com

Nous tenons à remercier Dourouc05 pour la relecture technique, Winjerome pour la mise au gabarit et Claude Leloup pour la relecture orthographique.