Aller au contenu principal

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

EtapeVerification securiteBloquant
Pre-commitDetection de secretsOui
CI - LintRegles ESLint securiteOui
CI - Buildnpm audit (high/critical)Oui
CI - TestTests unitaires securiteOui
CI - ScanCodeQL SASTOui
CI - LicenseConformite licencesOui
DeployValidation configurationOui
Quality GateGate 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

JobObjectifBloquant
lintESLint + Prettier checkOui
testTests unitaires + couvertureOui
buildCompilation TypeScriptOui
securitynpm audit (high/critical)Oui
monitoringValidation config Prometheus/GrafanaOui
codeqlAnalyse SAST CodeQLOui
license-checkConformite licencesOui
quality-gateGate finale de verificationOui

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

HookDescription
pre-commitlint-staged + tsc --noEmit + detection de secrets
commit-msgValidation 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

RegleProtection
no-evalInjection de code
no-implied-evalInjection de code indirect
no-new-funcInjection via Function constructor
no-protoPollution de prototype
no-extend-nativePollution de prototype
eqeqeqBugs de coercion de type
no-buffer-constructorVulnerabilites Buffer
no-path-concatPath traversal
prefer-promise-reject-errorsGestion d'erreur

Gestion des secrets

Types de secrets et stockage

Type de secretDeveloppementStagingProduction
JWT Secrets.env fileGitHub SecretsDocker Secrets
DB Credentials.env fileGitHub SecretsDocker Secrets
API Keys.env fileGitHub SecretsDocker Secrets
Firebase Keys.env fileGitHub SecretsDocker 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

SecretPeriode de rotationProcessus
JWT_SECRET90 joursRolling update
JWT_REFRESH_SECRET90 joursAvec JWT_SECRET
DB_PASSWORD365 joursCoordonner avec DBA
API Keys180 joursCreer nouveau, deprecier ancien
Firebase KeySi necessaireVia 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

EcosystemeFrequenceGroupes
npmWeekly (Lundi)security-critical, nestjs, prisma, typescript
GitHub ActionsWeeklyN/A
DockerWeeklyN/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 --chown=node:node package*.json ./
RUN npm ci --only=production

COPY --chown=node:node . .
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 --from=builder --chown=nestjs:nodejs /app/dist ./dist
COPY --from=builder --chown=nestjs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nestjs:nodejs /app/package.json ./

# Filesystem en lecture seule si possible
RUN chmod -R 555 /app

USER nestjs

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
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

RoleContactEscalation
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

TacheFrequence
Audit des dependancesHebdomadaire
Verification rotation secretsMensuel
Revue des accesTrimestriel
Test de penetrationAnnuel
Formation securiteAnnuel
Exercice reponse incidentSemestriel