Guide DevSecOps
Guide de developpement et d'operations oriente securite pour MyTelevision API.
Principes DevSecOps
+---------------------------------------------------------------+
| DevSecOps Pipeline |
+---------------------------------------------------------------+
| |
| Plan -> Code -> Build -> Test -> Deploy |
| | | | | | |
| v v v v v |
| Threat Secret SAST DAST Runtime |
| Modeling Detection Scan Scan Protection |
| |
+---------------------------------------------------------------+
Security Gates
| Etape | Verification securite | Bloquant |
|---|---|---|
| Pre-commit | Detection de secrets | Oui |
| CI - Lint | Regles ESLint securite | Oui |
| CI - Build | npm audit (high/critical) | Oui |
| CI - Test | Tests unitaires securite | Oui |
| CI - Scan | CodeQL SAST | Oui |
| CI - License | Conformite licences | Oui |
| Deploy | Validation configuration | Oui |
| Quality Gate | Gate finale (tout passe) | Oui |
Pipeline CI/CD securise
GitHub Actions Security Pipeline
# .github/workflows/ci.yml (jobs securite)
# Job 1: Scan de vulnerabilites des dependances
security:
name: Security Scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
# Bloquer sur les vulnerabilites high/critical en production
- name: npm audit (production)
run: npm audit --audit-level=high --omit=dev
# Avertir sur toutes les dependances
- name: npm audit (all)
run: npm audit --audit-level=critical
continue-on-error: true
# Generer le rapport d'audit
- name: Security report
run: |
npm audit --json > audit-report.json || true
CRITICAL=$(cat audit-report.json | jq '.metadata.vulnerabilities.critical // 0')
if [ "$CRITICAL" -gt 0 ]; then
echo "::error::Critical vulnerabilities found!"
exit 1
fi
- uses: actions/upload-artifact@v4
with:
name: npm-audit-report
path: audit-report.json
# Job 2: Analyse statique CodeQL
codeql:
name: CodeQL Analysis
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- uses: actions/checkout@v4
- uses: github/codeql-action/init@v3
with:
languages: javascript-typescript
queries: security-and-quality
- run: npm ci && npm run build
- uses: github/codeql-action/analyze@v3
# Job 3: Conformite des licences
license-check:
name: License Compliance
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- name: Check licenses
run: |
npx license-checker --production \
--onlyAllow "MIT;Apache-2.0;BSD-2-Clause;BSD-3-Clause;ISC;CC0-1.0;Unlicense;0BSD" \
--excludePrivatePackages
Resume des jobs CI
| Job | Objectif | Bloquant |
|---|---|---|
| lint | ESLint + Prettier check | Oui |
| test | Tests unitaires + couverture | Oui |
| build | Compilation TypeScript | Oui |
| security | npm audit (high/critical) | Oui |
| monitoring | Validation config Prometheus/Grafana | Oui |
| codeql | Analyse SAST CodeQL | Oui |
| license-check | Conformite licences | Oui |
| quality-gate | Gate finale de verification | Oui |
Pre-commit Security Hooks
Configuration Husky
# .husky/pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# Lint-staged (inclut les regles ESLint securite)
npx lint-staged
# Verification des types TypeScript
npx tsc --noEmit
# Detection de secrets
echo "Running security checks..."
# Verifier les cles AWS
if git diff --cached --name-only | xargs grep -l 'AKIA[0-9A-Z]{16}' 2>/dev/null; then
echo "::error::AWS access key detected in staged files!"
exit 1
fi
# Verifier les cles privees
if git diff --cached --name-only | xargs grep -l '-----BEGIN.*PRIVATE KEY-----' 2>/dev/null; then
echo "::error::Private key detected in staged files!"
exit 1
fi
# Verifier les patterns de secrets courants
PATTERNS=(
'password\s*[:=]\s*["\x27][^"\x27]+["\x27]'
'api[_-]?key\s*[:=]\s*["\x27][^"\x27]+["\x27]'
'secret\s*[:=]\s*["\x27][^"\x27]+["\x27]'
)
for pattern in "${PATTERNS[@]}"; do
if git diff --cached --name-only | xargs grep -iE "$pattern" 2>/dev/null | grep -v '.example' | grep -v '.md'; then
echo "::warning::Potential secret detected - please verify before committing"
fi
done
echo "Pre-commit checks passed!"
Hook commit-msg
# .husky/commit-msg
# Validation Conventional Commits (feat, fix, docs, etc.)
Resume des hooks
| Hook | Description |
|---|---|
pre-commit | lint-staged + tsc --noEmit + detection de secrets |
commit-msg | Validation Conventional Commits (feat, fix, docs, etc.) |
Regles ESLint de securite
// .eslintrc.js (regles securite)
module.exports = {
rules: {
// Prevenir l'injection de code
'no-eval': 'error',
'no-implied-eval': 'error',
'no-new-func': 'error',
// Prevenir la pollution de prototype
'no-proto': 'error',
'no-extend-native': 'error',
// Prevenir les bugs de coercion de type
eqeqeq: ['error', 'always'],
// Prevenir les vulnerabilites Buffer
'no-buffer-constructor': 'error',
// Prevenir le path traversal
'no-path-concat': 'error',
// Gestion d'erreur
'prefer-promise-reject-errors': 'error',
// Prevenir le ReDoS
'no-control-regex': 'error',
},
};
Table des regles
| Regle | Protection |
|---|---|
no-eval | Injection de code |
no-implied-eval | Injection de code indirect |
no-new-func | Injection via Function constructor |
no-proto | Pollution de prototype |
no-extend-native | Pollution de prototype |
eqeqeq | Bugs de coercion de type |
no-buffer-constructor | Vulnerabilites Buffer |
no-path-concat | Path traversal |
prefer-promise-reject-errors | Gestion d'erreur |
Gestion des secrets
Types de secrets et stockage
| Type de secret | Developpement | Staging | Production |
|---|---|---|---|
| JWT Secrets | .env file | GitHub Secrets | Docker Secrets |
| DB Credentials | .env file | GitHub Secrets | Docker Secrets |
| API Keys | .env file | GitHub Secrets | Docker Secrets |
| Firebase Keys | .env file | GitHub Secrets | Docker Secrets |
Generation de secrets
# Generer des secrets forts
# JWT Secret (256-bit)
openssl rand -base64 32
# Mot de passe base de donnees
openssl rand -base64 24
# Cle API
openssl rand -hex 32
# Cle de chiffrement (AES-256)
openssl rand -hex 32
Docker Secrets (Production)
# docker-compose.production.yml
version: '3.8'
secrets:
jwt_secret:
external: true
jwt_refresh_secret:
external: true
db_password:
external: true
redis_password:
external: true
firebase_private_key:
external: true
services:
api:
image: mytelevision/api:${VERSION}
secrets:
- jwt_secret
- jwt_refresh_secret
- db_password
- redis_password
- firebase_private_key
environment:
- JWT_SECRET_FILE=/run/secrets/jwt_secret
- JWT_REFRESH_SECRET_FILE=/run/secrets/jwt_refresh_secret
- DB_PASSWORD_FILE=/run/secrets/db_password
- REDIS_PASSWORD_FILE=/run/secrets/redis_password
- FIREBASE_PRIVATE_KEY_FILE=/run/secrets/firebase_private_key
Correction SEC-003
Le docker-compose.production.yml a ete cree pour utiliser Docker secrets au lieu de variables d'environnement, empechant l'exposition de DATABASE_URL et autres credentials dans les logs Docker.
Rotation des secrets
| Secret | Periode de rotation | Processus |
|---|---|---|
| JWT_SECRET | 90 jours | Rolling update |
| JWT_REFRESH_SECRET | 90 jours | Avec JWT_SECRET |
| DB_PASSWORD | 365 jours | Coordonner avec DBA |
| API Keys | 180 jours | Creer nouveau, deprecier ancien |
| Firebase Key | Si necessaire | Via Firebase Console |
Procedure de rotation
# 1. Generer le nouveau secret
NEW_SECRET=$(openssl rand -base64 32)
# 2. Mettre a jour dans le secret store
docker secret create jwt_secret_v2 - <<< "$NEW_SECRET"
# 3. Mettre a jour le service
docker service update \
--secret-rm jwt_secret \
--secret-add source=jwt_secret_v2,target=jwt_secret \
mytelevision_api
# 4. Verifier la sante du service
docker service ps mytelevision_api
# 5. Supprimer l'ancien secret (apres periode de grace)
docker secret rm jwt_secret
Findings ouverts
SEC-004 (rotation automatique des secrets) et SEC-005 (vault centralise HashiCorp/AWS Secrets Manager) sont des findings critiques ouverts necessitant de l'infrastructure.
Securite des dependances
Configuration Dependabot
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: 'npm'
directory: '/'
schedule:
interval: 'weekly'
day: 'monday'
open-pull-requests-limit: 10
groups:
production-deps:
patterns:
- '@nestjs/*'
- '@prisma/*'
- 'prisma'
dev-deps:
patterns:
- '@types/*'
- 'eslint*'
- 'jest*'
labels:
- 'dependencies'
- 'security'
reviewers:
- 'security-team'
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'weekly'
- package-ecosystem: 'docker'
directory: '/'
schedule:
interval: 'weekly'
Resume Dependabot
| Ecosysteme | Frequence | Groupes |
|---|---|---|
| npm | Weekly (Lundi) | security-critical, nestjs, prisma, typescript |
| GitHub Actions | Weekly | N/A |
| Docker | Weekly | N/A |
Commandes d'audit
# Verifier les vulnerabilites
npm audit
# Corriger automatiquement
npm audit fix
# Forcer la correction (peut inclure des breaking changes)
npm audit fix --force
# Generer un rapport
npm audit --json > audit-report.json
# Dependances de production uniquement
npm audit --omit=dev
Licences autorisees
MIT
Apache-2.0
BSD-2-Clause
BSD-3-Clause
ISC
CC0-1.0
Unlicense
0BSD
Python-2.0
CC-BY-3.0
CC-BY-4.0
Securite des containers
Dockerfile durci
# Dockerfile (securite renforcee)
# Utiliser une version specifique, jamais 'latest'
FROM node:20-alpine AS builder
# Ne pas executer en root
USER node
WORKDIR /app
# Copier les fichiers package en premier (cache de couches)
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# Image de production
FROM node:20-alpine AS production
# Mises a jour de securite
RUN apk update && apk upgrade && rm -rf /var/cache/apk/*
# Creer un utilisateur non-root
RUN addgroup -g 1001 -S nodejs && \
adduser -S nestjs -u 1001
WORKDIR /app
# Copier uniquement les fichiers necessaires
COPY /app/dist ./dist
COPY /app/node_modules ./node_modules
COPY /app/package.json ./
# Filesystem en lecture seule si possible
RUN chmod -R 555 /app
USER nestjs
# Health check
HEALTHCHECK \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/v2/health || exit 1
EXPOSE 3000
CMD ["node", "dist/main"]
Scan des containers
# Scan avec Trivy
trivy image mytelevision/api:latest
# Scan avec Snyk
snyk container test mytelevision/api:latest
# Scan en CI
docker scan mytelevision/api:latest --severity high
Securite de l'infrastructure
Kubernetes Network Policies
# k8s/network-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-network-policy
namespace: mytelevision
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
- Egress
ingress:
# Autoriser depuis l'ingress controller
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- port: 3000
# Autoriser depuis Prometheus
- from:
- namespaceSelector:
matchLabels:
name: monitoring
ports:
- port: 3000
egress:
# Autoriser vers PostgreSQL
- to:
- podSelector:
matchLabels:
app: postgresql
ports:
- port: 5432
# Autoriser vers Redis
- to:
- podSelector:
matchLabels:
app: redis
ports:
- port: 6379
# Autoriser DNS
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- port: 53
protocol: UDP
# Autoriser HTTPS externe (TMDb, Firebase)
- to:
- ipBlock:
cidr: 0.0.0.0/0
ports:
- port: 443
Terraform -- WAF Configuration
# iac/modules/security/main.tf
# WAF Web ACL
resource "aws_wafv2_web_acl" "api" {
name = "mytelevision-api-waf"
scope = "REGIONAL"
default_action {
allow {}
}
# Regle de rate limiting
rule {
name = "rate-limit"
priority = 1
action {
block {}
}
statement {
rate_based_statement {
limit = 2000
aggregate_key_type = "IP"
}
}
visibility_config {
sampled_requests_enabled = true
cloudwatch_metrics_enabled = true
metric_name = "RateLimitRule"
}
}
# AWS Managed Rules - Common
rule {
name = "aws-common"
priority = 2
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
sampled_requests_enabled = true
cloudwatch_metrics_enabled = true
metric_name = "AWSCommonRules"
}
}
# Protection SQL Injection
rule {
name = "sql-injection"
priority = 3
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesSQLiRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
sampled_requests_enabled = true
cloudwatch_metrics_enabled = true
metric_name = "SQLInjectionRules"
}
}
visibility_config {
sampled_requests_enabled = true
cloudwatch_metrics_enabled = true
metric_name = "MyTelevisionAPIWAF"
}
}
Securite applicative
Validation des entrees
// DTOs avec validation stricte
import {
IsString,
IsEmail,
MinLength,
MaxLength,
Matches,
} from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class CreateUserDto {
@ApiProperty()
@IsEmail()
email: string;
@ApiProperty()
@IsString()
@MinLength(8)
@MaxLength(128)
@Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/, {
message: 'Password too weak',
})
password: string;
@ApiProperty()
@IsString()
@MinLength(1)
@MaxLength(50)
@Matches(/^[a-zA-Z\s\-']+$/, {
message: 'Name contains invalid characters',
})
firstName: string;
}
Prevention de l'injection SQL
// TOUJOURS utiliser les requetes parametrees de Prisma
// BON
const user = await prisma.user.findUnique({
where: { email: userInput },
});
// MAUVAIS - Ne jamais faire cela !
// const user = await prisma.$queryRaw`SELECT * FROM users WHERE email = '${userInput}'`;
// Pour les requetes brutes, utiliser des parametres
const users = await prisma.$queryRaw`
SELECT * FROM users WHERE email = ${userInput}
`;
Rate Limiting applicatif
// Rate limiting par profil
@Injectable()
export class ProfileRateLimitGuard implements CanActivate {
constructor(private readonly redis: RedisService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const { tenantId, profileId } = request.session;
const tier = this.getTier(context);
const key = `ratelimit:${tenantId}:${profileId}:${tier}`;
const current = await this.redis.incr(key);
if (current === 1) {
await this.redis.expire(key, this.getTTL(tier));
}
const limit = this.getLimit(tier);
if (current > limit) {
throw new TooManyRequestsException();
}
return true;
}
}
Monitoring et alertes
Metriques de securite
// src/infrastructure/metrics/security.metrics.ts
import { Counter, Histogram } from 'prom-client';
// Metriques d'authentification
export const authAttempts = new Counter({
name: 'mytelevision_auth_attempts_total',
help: 'Total authentication attempts',
labelNames: ['provider', 'status', 'error_type'],
});
// Violations de rate limit
export const rateLimitViolations = new Counter({
name: 'mytelevision_rate_limit_violations_total',
help: 'Total rate limit violations',
labelNames: ['tenant_id', 'profile_id', 'endpoint'],
});
// Activite suspecte
export const suspiciousActivity = new Counter({
name: 'mytelevision_suspicious_activity_total',
help: 'Total suspicious activity detected',
labelNames: ['type', 'severity'],
});
Alertes de securite
# monitoring/prometheus/rules/security-alerts.yml
groups:
- name: security
rules:
- alert: HighAuthFailureRate
expr: |
rate(mytelevision_auth_attempts_total{status="failure"}[5m])
/ rate(mytelevision_auth_attempts_total[5m]) > 0.3
for: 5m
labels:
severity: warning
annotations:
summary: High authentication failure rate
description: >-
{{ $value | humanizePercentage }} of auth attempts failing
- alert: BruteForceDetected
expr: |
sum(rate(mytelevision_auth_attempts_total{status="failure"}[1m])) by (ip) > 10
for: 1m
labels:
severity: critical
annotations:
summary: Possible brute force attack
description: >-
IP {{ $labels.ip }} has {{ $value }} failed attempts/min
- alert: RateLimitExceeded
expr: |
rate(mytelevision_rate_limit_violations_total[5m]) > 100
for: 5m
labels:
severity: warning
annotations:
summary: High rate of rate limit violations
Reponse aux incidents
Runbook d'incident de securite
1. Detection
- Alerte du monitoring
- Rapport utilisateur
- Analyse des logs d'audit
2. Confinement
- Identifier les systemes affectes
- Isoler les ressources compromises
- Preserver les preuves
- Bloquer l'IP de l'attaquant (si applicable)
3. Eradication
- Supprimer le code/donnees malveillantes
- Patcher la vulnerabilite
- Rotater les credentials compromis
- Mettre a jour les regles firewall
4. Recuperation
- Restaurer a partir d'une sauvegarde propre
- Verifier l'integrite du systeme
- Surveiller pour re-compromission
- Restauration graduelle du service
5. Post-Incident
- Documenter la chronologie
- Analyse de la cause racine
- Mettre a jour les procedures
- Reunion de lessons learned
Contacts d'urgence
| Role | Contact | Escalation |
|---|---|---|
| Security Lead | [email protected] | Immediat |
| DevOps On-call | [email protected] | 15 min |
| CTO | [email protected] | 30 min |
| Legal | [email protected] | Si requis |
Checklist de conformite DevSecOps
Checklist pre-deploiement
- Toutes les dependances scannees (npm audit)
- Aucune vulnerabilite high/critical
- Aucun secret dans le code
- Variables d'environnement documentees
- HTTPS force
- CORS correctement configure
- Rate limiting active
- Validation des entrees sur tous les endpoints
- Authentification requise la ou necessaire
- Verifications d'autorisation en place
- Logging configure
- Messages d'erreur ne fuient pas d'info
- Headers de securite configures
- Acces base de donnees restreint
Revue de securite periodique
| Tache | Frequence |
|---|---|
| Audit des dependances | Hebdomadaire |
| Verification rotation secrets | Mensuel |
| Revue des acces | Trimestriel |
| Test de penetration | Annuel |
| Formation securite | Annuel |
| Exercice reponse incident | Semestriel |