Variables d'Environnement Python : Sécurité et Configuration des Scripts
Maîtrisez la gestion des variables d'environnement en Python pour sécuriser vos scripts, gérer les configurations et éviter les erreurs de déploiement.
Publié le
20 décembre 2024
Lecture
12 min
Vues
0
Auteur
Florian Courouge
Python
Security
Configuration
Environment
DevOps
Best Practices
Table des matières
📋 Vue d'ensemble rapide des sujets traités dans cet article
Cliquez sur les sections ci-dessous pour naviguer rapidement
Variables d'Environnement Python : Sécurité et Configuration des Scripts
La gestion des variables d'environnement est cruciale pour développer des scripts Python robustes, sécurisés et facilement déployables. Ce guide explore les bonnes pratiques pour utiliser les variables d'environnement dans vos projets Python.
💡Pourquoi Utiliser des Variables d'Environnement ?
# ✅ BONNE PRATIQUE - Variables d'environnement
import os
import requests
from typing import Optional
def get_env_var(key: str, default: Optional[str] = None, required: bool = True) -> str:
"""Récupère une variable d'environnement de manière sécurisée"""
value = os.getenv(key, default)
if required and value is None:
raise ValueError(f"Variable d'environnement '{key}' requise mais non définie")
return value
# Configuration sécurisée
API_KEY = get_env_var("API_KEY")
DATABASE_URL = get_env_var("DATABASE_URL")
def call_api():
response = requests.get(
"https://api.example.com/data",
headers={"Authorization": f"Bearer {API_KEY}"}
)
return response.json()
2. Configuration Multi-Environnements
# config/settings.py
import os
from enum import Enum
from dataclasses import dataclass
from typing import Optional
class Environment(Enum):
DEVELOPMENT = "development"
STAGING = "staging"
PRODUCTION = "production"
@dataclass
class DatabaseConfig:
host: str
port: int
name: str
user: str
password: str
ssl_mode: str = "prefer"
@property
def url(self) -> str:
return f"postgresql://{self.user}:{self.password}@{self.host}:{self.port}/{self.name}?sslmode={self.ssl_mode}"
@dataclass
class AppConfig:
environment: Environment
debug: bool
log_level: str
database: DatabaseConfig
redis_url: str
secret_key: str
@classmethod
def from_env(cls) -> 'AppConfig':
"""Crée la configuration à partir des variables d'environnement"""
env = Environment(os.getenv("ENVIRONMENT", "development"))
# Configuration de base selon l'environnement
if env == Environment.DEVELOPMENT:
debug = True
log_level = "DEBUG"
elif env == Environment.STAGING:
debug = False
log_level = "INFO"
else: # PRODUCTION
debug = False
log_level = "WARNING"
# Configuration de la base de données
database = DatabaseConfig(
host=os.getenv("DB_HOST", "localhost"),
port=int(os.getenv("DB_PORT", "5432")),
name=os.getenv("DB_NAME", "myapp"),
user=os.getenv("DB_USER", "postgres"),
password=os.getenv("DB_PASSWORD", ""),
ssl_mode=os.getenv("DB_SSL_MODE", "prefer")
)
return cls(
environment=env,
debug=debug,
log_level=log_level,
database=database,
redis_url=os.getenv("REDIS_URL", "redis://localhost:6379/0"),
secret_key=os.getenv("SECRET_KEY", "dev-secret-key")
)
def validate(self) -> None:
"""Valide la configuration"""
if self.environment == Environment.PRODUCTION:
if self.secret_key == "dev-secret-key":
raise ValueError("SECRET_KEY par défaut en production !")
if not self.database.password:
raise ValueError("Mot de passe DB requis en production")
if self.debug:
raise ValueError("Debug activé en production !")
# Utilisation
config = AppConfig.from_env()
config.validate()
💡Gestion Avancée avec python-dotenv
Installation et Configuration
pip install python-dotenv
# .env (fichier de développement)
# Base de données
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp_dev
DB_USER=postgres
DB_PASSWORD=dev_password
# Cache
REDIS_URL=redis://localhost:6379/0
# API externes
STRIPE_API_KEY=sk_test_1234567890
SENDGRID_API_KEY=SG.test_key
# Configuration app
SECRET_KEY=dev-secret-key-very-long
DEBUG=true
LOG_LEVEL=DEBUG
# Monitoring
SENTRY_DSN=https://your-sentry-dsn@sentry.io/project
# config/env_loader.py
import os
from pathlib import Path
from dotenv import load_dotenv
from typing import Union, List, Dict, Any
import logging
logger = logging.getLogger(__name__)
class EnvironmentLoader:
"""Gestionnaire avancé des variables d'environnement"""
def __init__(self, env_file: Union[str, Path] = None):
self.env_file = env_file or ".env"
self.loaded_vars: Dict[str, str] = {}
def load(self, override: bool = False) -> None:
"""Charge les variables d'environnement"""
# Charger depuis le fichier .env
if Path(self.env_file).exists():
load_dotenv(self.env_file, override=override)
logger.info(f"Variables chargées depuis {self.env_file}")
else:
logger.warning(f"Fichier {self.env_file} non trouvé")
# Stocker les variables chargées
self.loaded_vars = dict(os.environ)
def get(self, key: str, default: Any = None, cast_type: type = str, required: bool = False) -> Any:
"""Récupère une variable avec conversion de type"""
value = os.getenv(key, default)
if required and value is None:
raise ValueError(f"Variable '{key}' requise mais non définie")
if value is None:
return default
# Conversion de type
try:
if cast_type == bool:
return value.lower() in ('true', '1', 'yes', 'on')
elif cast_type == list:
return [item.strip() for item in value.split(',') if item.strip()]
else:
return cast_type(value)
except (ValueError, TypeError) as e:
raise ValueError(f"Impossible de convertir '{key}={value}' en {cast_type.__name__}: {e}")
def get_required(self, key: str, cast_type: type = str) -> Any:
"""Raccourci pour les variables requises"""
return self.get(key, cast_type=cast_type, required=True)
def validate_required(self, required_vars: List[str]) -> None:
"""Valide que toutes les variables requises sont définies"""
missing = [var for var in required_vars if not os.getenv(var)]
if missing:
raise ValueError(f"Variables manquantes: {', '.join(missing)}")
def export_template(self, output_file: str = ".env.template") -> None:
"""Génère un template des variables utilisées"""
template_content = []
template_content.append("# Configuration Template")
template_content.append("# Copiez ce fichier vers .env et remplissez les valeurs")
template_content.append("")
# Grouper par préfixe
groups = {}
for key in sorted(self.loaded_vars.keys()):
prefix = key.split('_')[0]
if prefix not in groups:
groups[prefix] = []
groups[prefix].append(key)
for group, keys in groups.items():
template_content.append(f"# {group.title()} Configuration")
for key in keys:
template_content.append(f"{key}=")
template_content.append("")
Path(output_file).write_text('\n'.join(template_content))
logger.info(f"Template généré: {output_file}")
# Utilisation globale
env_loader = EnvironmentLoader()
env_loader.load()
💡Patterns de Configuration Robustes
1. Configuration par Classes
# config/database.py
from dataclasses import dataclass
from typing import Optional
from .env_loader import env_loader
@dataclass
class DatabaseConfig:
"""Configuration de base de données"""
host: str
port: int
name: str
user: str
password: str
ssl_mode: str = "prefer"
pool_size: int = 5
max_overflow: int = 10
echo: bool = False
@classmethod
def from_env(cls, prefix: str = "DB") -> 'DatabaseConfig':
"""Crée la config depuis les variables d'environnement"""
return cls(
host=env_loader.get_required(f"{prefix}_HOST"),
port=env_loader.get(f"{prefix}_PORT", 5432, int),
name=env_loader.get_required(f"{prefix}_NAME"),
user=env_loader.get_required(f"{prefix}_USER"),
password=env_loader.get_required(f"{prefix}_PASSWORD"),
ssl_mode=env_loader.get(f"{prefix}_SSL_MODE", "prefer"),
pool_size=env_loader.get(f"{prefix}_POOL_SIZE", 5, int),
max_overflow=env_loader.get(f"{prefix}_MAX_OVERFLOW", 10, int),
echo=env_loader.get(f"{prefix}_ECHO", False, bool)
)
@property
def url(self) -> str:
"""URL de connexion"""
return f"postgresql://{self.user}:{self.password}@{self.host}:{self.port}/{self.name}"
def validate(self) -> None:
"""Valide la configuration"""
if self.port < 1 or self.port > 65535:
raise ValueError(f"Port invalide: {self.port}")
if self.pool_size < 1:
raise ValueError(f"Pool size invalide: {self.pool_size}")
# config/cache.py
@dataclass
class CacheConfig:
"""Configuration du cache Redis"""
url: str
ttl: int = 3600
max_connections: int = 10
@classmethod
def from_env(cls) -> 'CacheConfig':
return cls(
url=env_loader.get("REDIS_URL", "redis://localhost:6379/0"),
ttl=env_loader.get("CACHE_TTL", 3600, int),
max_connections=env_loader.get("REDIS_MAX_CONNECTIONS", 10, int)
)
2. Configuration Hiérarchique
# config/main.py
import os
from pathlib import Path
from typing import Dict, Any
from .database import DatabaseConfig
from .cache import CacheConfig
from .env_loader import env_loader
class AppConfig:
"""Configuration principale de l'application"""
def __init__(self):
# Charger les variables d'environnement
env_file = os.getenv("ENV_FILE", ".env")
env_loader.env_file = env_file
env_loader.load()
# Configuration par composants
self.database = DatabaseConfig.from_env()
self.cache = CacheConfig.from_env()
# Configuration générale
self.environment = env_loader.get("ENVIRONMENT", "development")
self.debug = env_loader.get("DEBUG", False, bool)
self.log_level = env_loader.get("LOG_LEVEL", "INFO")
self.secret_key = env_loader.get_required("SECRET_KEY")
# Configuration de sécurité
self.allowed_hosts = env_loader.get("ALLOWED_HOSTS", [], list)
self.cors_origins = env_loader.get("CORS_ORIGINS", [], list)
# APIs externes
self.stripe_api_key = env_loader.get("STRIPE_API_KEY")
self.sendgrid_api_key = env_loader.get("SENDGRID_API_KEY")
# Monitoring
self.sentry_dsn = env_loader.get("SENTRY_DSN")
# Validation
self.validate()
def validate(self) -> None:
"""Valide la configuration complète"""
# Validation par composant
self.database.validate()
# Validation globale
if self.environment == "production":
required_prod_vars = [
"SECRET_KEY", "DB_PASSWORD", "SENTRY_DSN"
]
for var in required_prod_vars:
if not getattr(self, var.lower().replace('_', '_')):
raise ValueError(f"{var} requis en production")
if self.debug:
raise ValueError("DEBUG ne doit pas être activé en production")
def to_dict(self) -> Dict[str, Any]:
"""Exporte la configuration (sans les secrets)"""
return {
"environment": self.environment,
"debug": self.debug,
"log_level": self.log_level,
"database": {
"host": self.database.host,
"port": self.database.port,
"name": self.database.name,
"ssl_mode": self.database.ssl_mode
},
"cache": {
"ttl": self.cache.ttl,
"max_connections": self.cache.max_connections
}
}
# Instance globale
config = AppConfig()
💡Scripts de Déploiement et Variables
1. Script de Déploiement avec Validation
#!/usr/bin/env python3
# deploy.py
import os
import sys
import subprocess
from pathlib import Path
from typing import List, Dict
import argparse
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class DeploymentManager:
"""Gestionnaire de déploiement avec validation des variables"""
def __init__(self, environment: str):
self.environment = environment
self.required_vars = self._get_required_vars()
def _get_required_vars(self) -> Dict[str, List[str]]:
"""Définit les variables requises par environnement"""
base_vars = [
"DB_HOST", "DB_PORT", "DB_NAME", "DB_USER", "DB_PASSWORD",
"REDIS_URL", "SECRET_KEY"
]
return {
"development": base_vars,
"staging": base_vars + [
"SENTRY_DSN", "STRIPE_API_KEY"
],
"production": base_vars + [
"SENTRY_DSN", "STRIPE_API_KEY", "SENDGRID_API_KEY",
"SSL_CERT_PATH", "SSL_KEY_PATH"
]
}
def validate_environment(self) -> None:
"""Valide les variables d'environnement"""
logger.info(f"Validation des variables pour l'environnement: {self.environment}")
required = self.required_vars.get(self.environment, [])
missing = [var for var in required if not os.getenv(var)]
if missing:
logger.error(f"Variables manquantes: {', '.join(missing)}")
sys.exit(1)
logger.info("✓ Toutes les variables requises sont définies")
def load_env_file(self) -> None:
"""Charge le fichier d'environnement approprié"""
env_file = f".env.{self.environment}"
if not Path(env_file).exists():
logger.error(f"Fichier {env_file} non trouvé")
sys.exit(1)
# Charger les variables
with open(env_file) as f:
for line in f:
line = line.strip()
if line and not line.startswith('#'):
key, value = line.split('=', 1)
os.environ[key] = value
logger.info(f"✓ Variables chargées depuis {env_file}")
def deploy(self) -> None:
"""Exécute le déploiement"""
logger.info(f"Déploiement en cours pour {self.environment}...")
# Étapes de déploiement
steps = [
("Installation des dépendances", "pip install -r requirements.txt"),
("Migration de la base", "python manage.py migrate"),
("Collecte des fichiers statiques", "python manage.py collectstatic --noinput"),
("Redémarrage des services", self._restart_services)
]
for step_name, command in steps:
logger.info(f"Exécution: {step_name}")
if callable(command):
command()
else:
result = subprocess.run(command, shell=True, capture_output=True, text=True)
if result.returncode != 0:
logger.error(f"Erreur lors de: {step_name}")
logger.error(result.stderr)
sys.exit(1)
logger.info("✓ Déploiement terminé avec succès")
def _restart_services(self) -> None:
"""Redémarre les services selon l'environnement"""
if self.environment == "production":
subprocess.run("sudo systemctl restart myapp", shell=True)
subprocess.run("sudo systemctl restart nginx", shell=True)
else:
subprocess.run("docker-compose restart", shell=True)
def main():
parser = argparse.ArgumentParser(description="Script de déploiement")
parser.add_argument("environment", choices=["development", "staging", "production"])
parser.add_argument("--validate-only", action="store_true", help="Valider seulement")
args = parser.parse_args()
deployer = DeploymentManager(args.environment)
# Charger les variables
deployer.load_env_file()
# Valider
deployer.validate_environment()
if args.validate_only:
logger.info("Validation terminée")
return
# Déployer
deployer.deploy()
if __name__ == "__main__":
main()
2. Générateur de Fichiers d'Environnement
#!/usr/bin/env python3
# generate_env.py
import secrets
import string
from pathlib import Path
from typing import Dict, Any
import argparse
class EnvironmentGenerator:
"""Générateur de fichiers d'environnement"""
def __init__(self):
self.templates = {
"development": self._dev_template,
"staging": self._staging_template,
"production": self._prod_template
}
def generate_secret_key(self, length: int = 50) -> str:
"""Génère une clé secrète sécurisée"""
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
return ''.join(secrets.choice(alphabet) for _ in range(length))
def generate_password(self, length: int = 16) -> str:
"""Génère un mot de passe sécurisé"""
alphabet = string.ascii_letters + string.digits
return ''.join(secrets.choice(alphabet) for _ in range(length))
def _dev_template(self) -> Dict[str, Any]:
"""Template pour le développement"""
return {
"ENVIRONMENT": "development",
"DEBUG": "true",
"LOG_LEVEL": "DEBUG",
"SECRET_KEY": self.generate_secret_key(),
"DB_HOST": "localhost",
"DB_PORT": "5432",
"DB_NAME": "myapp_dev",
"DB_USER": "postgres",
"DB_PASSWORD": self.generate_password(),
"REDIS_URL": "redis://localhost:6379/0",
"STRIPE_API_KEY": "sk_test_...",
"SENDGRID_API_KEY": "SG.test_...",
}
def _staging_template(self) -> Dict[str, Any]:
"""Template pour le staging"""
return {
"ENVIRONMENT": "staging",
"DEBUG": "false",
"LOG_LEVEL": "INFO",
"SECRET_KEY": self.generate_secret_key(),
"DB_HOST": "staging-db.example.com",
"DB_PORT": "5432",
"DB_NAME": "myapp_staging",
"DB_USER": "myapp_user",
"DB_PASSWORD": self.generate_password(32),
"DB_SSL_MODE": "require",
"REDIS_URL": "redis://staging-redis.example.com:6379/0",
"SENTRY_DSN": "https://your-sentry-dsn@sentry.io/project",
"STRIPE_API_KEY": "sk_test_...",
"SENDGRID_API_KEY": "SG.staging_...",
"ALLOWED_HOSTS": "staging.example.com",
"CORS_ORIGINS": "https://staging-app.example.com"
}
def _prod_template(self) -> Dict[str, Any]:
"""Template pour la production"""
return {
"ENVIRONMENT": "production",
"DEBUG": "false",
"LOG_LEVEL": "WARNING",
"SECRET_KEY": self.generate_secret_key(64),
"DB_HOST": "prod-db.example.com",
"DB_PORT": "5432",
"DB_NAME": "myapp_prod",
"DB_USER": "myapp_user",
"DB_PASSWORD": self.generate_password(64),
"DB_SSL_MODE": "require",
"DB_POOL_SIZE": "20",
"REDIS_URL": "redis://prod-redis.example.com:6379/0",
"SENTRY_DSN": "https://your-prod-sentry-dsn@sentry.io/project",
"STRIPE_API_KEY": "sk_live_...",
"SENDGRID_API_KEY": "SG.prod_...",
"SSL_CERT_PATH": "/etc/ssl/certs/myapp.crt",
"SSL_KEY_PATH": "/etc/ssl/private/myapp.key",
"ALLOWED_HOSTS": "example.com,www.example.com",
"CORS_ORIGINS": "https://app.example.com"
}
def generate(self, environment: str, output_file: str = None) -> None:
"""Génère un fichier d'environnement"""
if environment not in self.templates:
raise ValueError(f"Environnement non supporté: {environment}")
template = self.templates[environment]()
if not output_file:
output_file = f".env.{environment}"
# Générer le contenu
content = []
content.append(f"# Configuration pour l'environnement: {environment}")
content.append(f"# Généré automatiquement - NE PAS COMMITTER")
content.append("")
# Grouper par catégorie
categories = {
"Application": ["ENVIRONMENT", "DEBUG", "LOG_LEVEL", "SECRET_KEY"],
"Base de données": [k for k in template.keys() if k.startswith("DB_")],
"Cache": [k for k in template.keys() if "REDIS" in k],
"APIs externes": [k for k in template.keys() if k.endswith("_API_KEY")],
"Sécurité": [k for k in template.keys() if k in ["ALLOWED_HOSTS", "CORS_ORIGINS", "SSL_CERT_PATH", "SSL_KEY_PATH"]],
"Monitoring": [k for k in template.keys() if "SENTRY" in k]
}
for category, keys in categories.items():
if not keys:
continue
content.append(f"# {category}")
for key in keys:
if key in template:
content.append(f"{key}={template[key]}")
content.append("")
# Écrire le fichier
Path(output_file).write_text('\n'.join(content))
print(f"✓ Fichier généré: {output_file}")
if environment == "production":
print("⚠️ ATTENTION: Fichier de production généré avec des valeurs par défaut")
print(" Modifiez les valeurs avant utilisation !")
def main():
parser = argparse.ArgumentParser(description="Générateur de fichiers d'environnement")
parser.add_argument("environment", choices=["development", "staging", "production"])
parser.add_argument("-o", "--output", help="Fichier de sortie")
args = parser.parse_args()
generator = EnvironmentGenerator()
generator.generate(args.environment, args.output)
if __name__ == "__main__":
main()
💡Bonnes Pratiques et Sécurité
1. Checkliste de Sécurité
# security/env_checker.py
import os
import re
from pathlib import Path
from typing import List, Dict, Tuple
import logging
logger = logging.getLogger(__name__)
class SecurityChecker:
"""Vérificateur de sécurité pour les variables d'environnement"""
WEAK_PATTERNS = [
(r'password.*=.*(password|123|admin)', "Mot de passe faible détecté"),
(r'key.*=.*(test|dev|example)', "Clé de test en production"),
(r'secret.*=.{1,10}$', "Secret trop court"),
(r'token.*=.*(demo|sample)', "Token de démonstration"),
]
REQUIRED_PRODUCTION = [
"SECRET_KEY", "DB_PASSWORD", "SENTRY_DSN"
]
def __init__(self, environment: str = "production"):
self.environment = environment
self.issues: List[Tuple[str, str]] = []
def check_file(self, env_file: str) -> None:
"""Vérifie un fichier d'environnement"""
if not Path(env_file).exists():
self.issues.append(("ERROR", f"Fichier {env_file} non trouvé"))
return
with open(env_file) as f:
content = f.read()
# Vérifier les patterns dangereux
for pattern, message in self.WEAK_PATTERNS:
if re.search(pattern, content, re.IGNORECASE):
self.issues.append(("WARNING", f"{message} dans {env_file}"))
# Vérifier les variables requises en production
if self.environment == "production":
for var in self.REQUIRED_PRODUCTION:
if not re.search(f'^{var}=.+', content, re.MULTILINE):
self.issues.append(("ERROR", f"Variable {var} manquante"))
def check_environment(self) -> None:
"""Vérifie les variables d'environnement actuelles"""
for var in self.REQUIRED_PRODUCTION:
value = os.getenv(var)
if not value:
self.issues.append(("ERROR", f"Variable {var} non définie"))
elif len(value) < 10:
self.issues.append(("WARNING", f"Variable {var} trop courte"))
def report(self) -> bool:
"""Génère un rapport de sécurité"""
if not self.issues:
logger.info("✓ Aucun problème de sécurité détecté")
return True
errors = [issue for issue in self.issues if issue[0] == "ERROR"]
warnings = [issue for issue in self.issues if issue[0] == "WARNING"]
if errors:
logger.error("❌ Erreurs de sécurité critiques:")
for _, message in errors:
logger.error(f" - {message}")
if warnings:
logger.warning("⚠️ Avertissements de sécurité:")
for _, message in warnings:
logger.warning(f" - {message}")
return len(errors) == 0
# Utilisation
checker = SecurityChecker("production")
checker.check_file(".env.production")
checker.check_environment()
if not checker.report():
exit(1)
2. Rotation des Secrets
# security/secret_rotation.py
import os
import secrets
import string
from datetime import datetime, timedelta
from typing import Dict, List
import boto3
from pathlib import Path
class SecretRotator:
"""Gestionnaire de rotation des secrets"""
def __init__(self):
self.aws_client = boto3.client('secretsmanager')
self.rotation_schedule = {
"SECRET_KEY": timedelta(days=90),
"DB_PASSWORD": timedelta(days=30),
"API_KEYS": timedelta(days=60)
}
def generate_secret(self, secret_type: str, length: int = 32) -> str:
"""Génère un nouveau secret"""
if secret_type == "password":
# Mot de passe avec caractères spéciaux
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
elif secret_type == "api_key":
# Clé API alphanumérique
alphabet = string.ascii_letters + string.digits
else:
# Secret générique
alphabet = string.ascii_letters + string.digits + "!@#$%^&*-_"
return ''.join(secrets.choice(alphabet) for _ in range(length))
def rotate_secret(self, secret_name: str, secret_type: str = "generic") -> str:
"""Effectue la rotation d'un secret"""
new_secret = self.generate_secret(secret_type)
try:
# Stocker dans AWS Secrets Manager
self.aws_client.update_secret(
SecretId=secret_name,
SecretString=new_secret
)
# Mettre à jour localement
self._update_env_file(secret_name, new_secret)
return new_secret
except Exception as e:
raise RuntimeError(f"Erreur lors de la rotation de {secret_name}: {e}")
def _update_env_file(self, key: str, value: str) -> None:
"""Met à jour un fichier .env"""
env_file = ".env.production"
if not Path(env_file).exists():
return
lines = []
updated = False
with open(env_file) as f:
for line in f:
if line.startswith(f"{key}="):
lines.append(f"{key}={value}\n")
updated = True
else:
lines.append(line)
if not updated:
lines.append(f"{key}={value}\n")
with open(env_file, 'w') as f:
f.writelines(lines)
💡Conclusion
Les variables d'environnement sont essentielles pour :
•Sécuriser vos applications en évitant les credentials en dur
•Simplifier les déploiements multi-environnements
•Centraliser la configuration
•Faciliter la maintenance et les mises à jour
Points Clés à Retenir
•Jamais de secrets dans le code - Utilisez toujours des variables d'environnement
•Validation stricte - Vérifiez la présence et le format des variables critiques
•Configuration par environnement - Adaptez les valeurs selon dev/staging/prod
•Sécurité renforcée - Auditez et faites la rotation des secrets régulièrement
•Documentation - Maintenez des templates et de la documentation à jour
En suivant ces pratiques, vos scripts Python seront plus sécurisés, maintenables et déployables dans tous les environnements.
À propos de l'auteur
Florian Courouge - Expert DevOps et Apache Kafka avec plus de 5 ans d'expérience dans l'architecture de systèmes distribués et l'automatisation d'infrastructures.
Cet article vous a été utile ?
Découvrez mes autres articles techniques ou contactez-moi pour discuter de vos projets DevOps et Kafka.