Aller au contenu principal

Module 10 — Déploiement et contrôle d'accès

Une application qui tourne sur un portable ne sert qu'à son auteur. Ce dernier module ferme la boucle : mettre l'application en ligne sur Community Cloud pour un test rapide ou en conteneur Docker pour un usage interne, gérer les secrets sans les inscrire dans le dépôt, protéger l'accès par mot de passe ou par authentification déléguée, et connaître les limites de concurrence que Streamlit impose. Après ce module, le tableau de bord de résiliation est réellement utilisable par l'équipe commerciale.

Community Cloud, la voie la plus rapide

Streamlit Community Cloud héberge gratuitement les applications dont le code réside dans un dépôt GitHub public. Trois étapes suffisent : connecter son compte GitHub, désigner le dépôt et le fichier principal, lancer le déploiement. L'URL publique est prête en trois à cinq minutes.

Le dépôt doit contenir un fichier requirements.txt (ou pyproject.toml) qui liste les dépendances, et un fichier runtime.txt optionnel pour épingler la version de Python :

# requirements.txt
streamlit==1.38.0
pandas==2.2.2
scikit-learn==1.5.1
joblib==1.4.2
plotly==5.24.0
requests==2.32.3
# runtime.txt
python-3.11

Community Cloud impose deux limites qu'il faut connaître. Un giga de mémoire vive par application, ce qui suffit pour un tableau de bord modeste mais interdit les modèles profonds volumineux. Et la mise en sommeil après plusieurs heures sans consultation, avec un redémarrage de dix à vingt secondes au retour. Pour une démonstration ou un outil interne peu chargé, ces limites sont acceptables ; pour un usage sérieux, on bascule sur un conteneur.

Un conteneur Docker, pour un usage interne

Un Dockerfile minimal tient en dix lignes et donne un contrôle complet sur l'environnement d'exécution :

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8501

# --server.address 0.0.0.0 pour accepter les connexions extérieures au conteneur
CMD ["streamlit", "run", "app.py", \
"--server.address=0.0.0.0", \
"--server.port=8501", \
"--server.headless=true"]

Le paramètre --server.headless=true empêche Streamlit d'essayer d'ouvrir un navigateur au démarrage, ce qui n'a pas de sens dans un conteneur. Sans ce paramètre, un journal Docker se remplit de traces d'erreurs sans qu'aucune fonctionnalité soit affectée.

La construction et le lancement suivent le motif habituel :

docker build -t resiliation:latest .
docker run --rm -p 8501:8501 --name resiliation resiliation:latest

Pour un déploiement interne derrière un serveur web comme nginx ou Traefik, on ajoute un point d'accueil /healthz qui répond 200 dès que Streamlit est prêt ; c'est ce point que le serveur web sondera pour savoir si le conteneur est vivant. Streamlit expose _stcore/health nativement, qui joue ce rôle.

Gérer les secrets sans les inscrire dans le dépôt

Une application appelle presque toujours une API, une base ou un service qui demande une clé. Cette clé ne doit jamais figurer dans le code source, ni même dans un fichier committé. Streamlit fournit st.secrets, alimenté par un fichier .streamlit/secrets.toml ajouté au .gitignore :

# .streamlit/secrets.toml — NE PAS COMMITTER
[api_resiliation]
cle = "sk_abcd1234efgh5678"

[base_donnees]
url = "postgresql://user:motdepasse@hote:5432/base"

[admin]
motdepasse = "phrase_complexe_à_remplacer"

L'accès dans le code se fait par attributs ou par indexation :

cle_api = st.secrets["api_resiliation"]["cle"]
url_bd = st.secrets.base_donnees.url

Sur Community Cloud, les secrets se configurent dans l'interface web du déploiement ; en Docker, on les passe par des variables d'environnement ou par un volume monté sur /app/.streamlit/secrets.toml. La règle absolue : un secret qui a fuité par un git push est un secret compromis, à remplacer immédiatement, même s'il a été effacé du commit suivant. Le journal Git est indélébile.

Un mot de passe simple

Pour une application interne consultée par une dizaine de collaborateurs, un mot de passe unique suffit souvent. Le motif de référence tient en vingt lignes.

import streamlit as st
import hmac

def verifier_motdepasse() -> bool:
"""Retourne True si le mot de passe saisi correspond au secret."""
def valider():
saisi = st.session_state.get("motdepasse", "")
attendu = st.secrets["admin"]["motdepasse"]
# Comparaison à temps constant pour éviter les attaques par timing.
st.session_state.authentifie = hmac.compare_digest(saisi, attendu)
del st.session_state["motdepasse"] # n'affiche pas le champ à nouveau

if st.session_state.get("authentifie"):
return True

st.text_input(
"Mot de passe d'accès",
type="password",
on_change=valider,
key="motdepasse",
)
if st.session_state.get("authentifie") is False:
st.error("Mot de passe invalide.")
return False

if not verifier_motdepasse():
st.stop()

# --- Ici commence l'application réelle. ---
st.title("Tableau de bord — Résiliation clients")

Trois précautions à noter. hmac.compare_digest compare deux chaînes en temps constant, ce qui interdit à un attaquant d'inférer le mot de passe caractère par caractère à partir du temps de réponse. Le mot de passe saisi est immédiatement effacé de session_state pour ne pas rester en mémoire. Et st.stop() empêche toute exécution du reste du script tant que l'authentification n'est pas validée.

Ce motif convient à un cercle restreint ; pour dix mille utilisateurs, il devient tout à fait insuffisant.

L'authentification déléguée

Dès qu'on parle d'accès individualisé — traçabilité des consultations, révocation, rôles — on délègue l'authentification à un fournisseur d'identité tiers : Google Workspace, Microsoft Entra, Okta, Auth0. La méthode moderne repose sur OpenID Connect, et Streamlit 1.42 introduit st.user et une intégration directe qui simplifie considérablement le code.

# .streamlit/secrets.toml
[auth]
client_id = "..."
client_secret = "..."
server_metadata_url = "https://accounts.google.com/.well-known/openid-configuration"
redirect_uri = "https://scoring.exemple.fr/oauth2callback"
cookie_secret = "un_secret_long_et_aleatoire"
import streamlit as st

if not st.experimental_user.is_logged_in:
st.login() # bascule vers le fournisseur
st.stop()

email = st.experimental_user.email
st.caption(f"Connecté en tant que {email}.")

if not email.endswith("@exemple.fr"):
st.error("Accès réservé aux collaborateurs de l'entreprise.")
st.stop()

Deux règles importent. On délègue l'authentification (qui est cet utilisateur ?) au fournisseur, mais l'autorisation (a-t-il le droit d'accéder ?) reste dans l'application, souvent sous forme d'une vérification de domaine, d'un rôle stocké en base, ou d'un annuaire consulté. Et le bouton « déconnexion » n'est pas facultatif : sans lui, un utilisateur qui prête son poste n'a aucun moyen de couper la session.

Les limites de concurrence

Streamlit exécute chaque session dans son propre fil d'exécution, mais partage un même processus Python. Le verrou global de l'interpréteur limite le parallélisme des calculs intensifs en CPU : deux utilisateurs qui lancent en même temps un scoring lourd s'attendent effectivement l'un l'autre. Pour un tableau de bord de consultation, ce n'est pas un problème ; pour un service exposé à un pic de charge, il faut lancer plusieurs processus Streamlit derrière un équilibreur de charge, ou pré-calculer les résultats en tâche de fond.

Le cache @st.cache_resource est partagé entre sessions, ce qui multiplie l'efficacité : un modèle chargé une seule fois sert dix utilisateurs simultanés sans surcoût mémoire. À l'inverse, st.session_state est bien indépendant par session, sans contamination possible.

La liste de contrôle avant partage

Avant de partager une URL, on vérifie systématiquement six points. Un : secrets.toml figure dans .gitignore. Deux : aucun mot de passe, aucune clé, aucun jeton n'apparaît dans le code source. Trois : le timeout est présent sur chaque appel HTTP externe. Quatre : les fichiers téléversés sont validés avant traitement. Cinq : les erreurs affichent un message clair, pas de trace brute. Six : la version du modèle et de l'application est affichée quelque part. Une seule case non cochée transforme une démonstration en incident.

En résumé

  • Community Cloud héberge gratuitement les applications de dépôt public, avec une limite d'un gigaoctet de mémoire et une mise en sommeil après inactivité.
  • Un Dockerfile de dix lignes suffit pour un déploiement maîtrisé ; --server.headless=true et --server.address=0.0.0.0 sont deux paramètres à ne pas oublier.
  • Les secrets vivent dans .streamlit/secrets.toml, jamais dans le code ; hmac.compare_digest compare un mot de passe en temps constant pour se prémunir des attaques par timing.
  • L'authentification déléguée via OpenID Connect est la voie recommandée dès qu'on a besoin d'individualiser l'accès ; l'autorisation reste toujours du ressort de l'application.

Module suivant : la récapitulation du cours et l'examen final de 40 questions.