Sécurité Jenkins
Introduction
La sécurité de Jenkins est critique car il a accès à :
- Code source
- Credentials (mots de passe, tokens, clés)
- Infrastructure de production
- Pipelines de déploiement
Authentification
Base de données interne
# jenkins.yaml
jenkins:
securityRealm:
local:
allowsSignup: false # Désactiver l'inscription
users:
- id: "admin"
name: "Administrator"
password: "${JENKINS_ADMIN_PASSWORD}"
- id: "developer"
name: "Developer User"
password: "${DEV_PASSWORD}"
LDAP
jenkins:
securityRealm:
ldap:
configurations:
- server: "ldap.example.com"
rootDN: "dc=example,dc=com"
userSearchBase: "ou=users"
userSearch: "uid={0}"
groupSearchBase: "ou=groups"
groupMembershipStrategy:
fromGroupSearch:
filter: "member={0}"
managerDN: "cn=admin,dc=example,dc=com"
managerPasswordSecret: "${LDAP_PASSWORD}"
Active Directory
jenkins:
securityRealm:
activeDirectory:
domains:
- name: "example.com"
servers: "ad.example.com"
bindName: "[email protected]"
bindPassword: "${AD_PASSWORD}"
groupLookupStrategy: RECURSIVE
removeIrrelevantGroups: true
SAML (SSO)
jenkins:
securityRealm:
saml:
idpMetadataConfiguration:
url: "https://idp.example.com/metadata"
displayNameAttributeName: "displayName"
emailAttributeName: "email"
groupsAttributeName: "groups"
usernameAttributeName: "username"
GitHub OAuth
jenkins:
securityRealm:
github:
githubWebUri: "https://github.com"
githubApiUri: "https://api.github.com"
clientID: "${GITHUB_CLIENT_ID}"
clientSecret: "${GITHUB_CLIENT_SECRET}"
oauthScopes: "read:org,user:email"
Autorisation
Matrix Authorization
jenkins:
authorizationStrategy:
globalMatrix:
permissions:
# Administrateurs
- "Overall/Administer:admin"
- "Overall/Administer:admin-group"
# Développeurs
- "Overall/Read:developers"
- "Job/Build:developers"
- "Job/Read:developers"
- "Job/Workspace:developers"
# Ops
- "Overall/Read:ops"
- "Job/Build:ops"
- "Job/Read:ops"
- "Job/Configure:ops"
# Lecture seule pour authenticated
- "Overall/Read:authenticated"
- "Job/Read:authenticated"
Project-based Matrix
jenkins:
authorizationStrategy:
projectMatrix:
permissions:
# Permissions globales
- "Overall/Administer:admin"
- "Overall/Read:authenticated"
# Permissions par projet définies dans le job
Role-based Strategy
jenkins:
authorizationStrategy:
roleBased:
roles:
global:
- name: "admin"
permissions:
- "Overall/Administer"
entries:
- user: "admin"
- name: "readonly"
permissions:
- "Overall/Read"
entries:
- group: "authenticated"
items:
- name: "frontend-dev"
pattern: "frontend-.*"
permissions:
- "Job/Build"
- "Job/Read"
- "Job/Configure"
entries:
- group: "frontend-team"
- name: "backend-dev"
pattern: "backend-.*"
permissions:
- "Job/Build"
- "Job/Read"
- "Job/Configure"
entries:
- group: "backend-team"
Gestion des Credentials
Types de credentials
| Type | Usage |
|---|---|
| Username/Password | Git, Docker, API |
| Secret text | Tokens API |
| Secret file | Kubeconfig, certificats |
| SSH Username with key | Connexion serveurs |
| Certificate | mTLS |
Créer via JCasC
credentials:
system:
domainCredentials:
- credentials:
# Username/Password
- usernamePassword:
scope: GLOBAL
id: "github-credentials"
description: "GitHub Access"
username: "${GITHUB_USER}"
password: "${GITHUB_TOKEN}"
# Secret text
- string:
scope: GLOBAL
id: "sonar-token"
description: "SonarQube Token"
secret: "${SONAR_TOKEN}"
# Secret file
- file:
scope: GLOBAL
id: "kubeconfig"
description: "Kubernetes Config"
fileName: "kubeconfig"
secretBytes: "${base64:${KUBECONFIG_CONTENT}}"
# SSH Key
- basicSSHUserPrivateKey:
scope: GLOBAL
id: "ssh-deploy-key"
description: "SSH Deploy Key"
username: "deploy"
privateKeySource:
directEntry:
privateKey: "${SSH_PRIVATE_KEY}"
Utilisation sécurisée
pipeline {
agent any
stages {
stage('Deploy') {
steps {
// Les secrets sont masqués dans les logs
withCredentials([
usernamePassword(
credentialsId: 'docker-hub',
usernameVariable: 'DOCKER_USER',
passwordVariable: 'DOCKER_PASS'
)
]) {
sh '''
# Le mot de passe est masqué
echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin
'''
}
}
}
}
}
Scopes des credentials
| Scope | Visibilité |
|---|---|
| GLOBAL | Tout Jenkins |
| SYSTEM | Controller uniquement |
| Folder | Un dossier et ses enfants |
Sécurisation HTTPS
Avec reverse proxy (recommandé)
# /etc/nginx/sites-available/jenkins
server {
listen 80;
server_name jenkins.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name jenkins.example.com;
ssl_certificate /etc/ssl/certs/jenkins.crt;
ssl_certificate_key /etc/ssl/private/jenkins.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support (pour agents)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Jenkins direct
# Générer un keystore
keytool -genkey -keyalg RSA \
-alias jenkins \
-keystore jenkins.jks \
-validity 365
# Lancer Jenkins avec HTTPS
java -jar jenkins.war \
--httpPort=-1 \
--httpsPort=8443 \
--httpsKeyStore=/path/to/jenkins.jks \
--httpsKeyStorePassword=changeit
Sécurité des pipelines
Script Security
// Les scripts non approuvés sont bloqués
// Nécessite approbation dans:
// Manage Jenkins > In-process Script Approval
// Sandbox Groovy - Limité mais sécurisé
@Library('my-lib') _
pipeline {
agent any
stages {
stage('Build') {
steps {
// Scripts dans la sandbox
script {
def result = sh(script: 'echo test', returnStdout: true)
}
}
}
}
}
Limiter les permissions des pipelines
// Désactiver des fonctionnalités dangereuses
properties([
[$class: 'BuildDiscarderProperty',
strategy: [$class: 'LogRotator', numToKeepStr: '10']],
disableConcurrentBuilds()
])
Validation des Jenkinsfiles
# Valider un Jenkinsfile avant commit
curl -X POST \
-u admin:token \
-F "jenkinsfile=<Jenkinsfile" \
http://jenkins:8080/pipeline-model-converter/validate
Audit et logging
Audit Trail Plugin
unclassified:
auditTrail:
logBuildCause: true
pattern: ".*/(?:configSubmit|doDelete|postBuildResult|enable|disable|cancelQueue).*"
loggers:
- syslog:
syslogServerHostname: "syslog.example.com"
syslogServerPort: 514
appName: "jenkins"
facility: "USER"
Logs d'accès
# Dans jenkins.yaml
jenkins:
log:
recorders:
- name: "security-log"
loggers:
- name: "jenkins.security"
level: "FINE"
- name: "hudson.security"
level: "FINE"
Checklist de sécurité
□ HTTPS activé
□ Inscription désactivée (allowsSignup: false)
□ Authentification configurée (LDAP/OAuth)
□ Autorisations Matrix configurées
□ Credentials chiffrés (pas en clair dans les jobs)
□ Agents sécurisés (SSH ou JNLP avec secret)
□ Script Security activé
□ Mise à jour régulière (Jenkins + plugins)
□ Backup des configurations
□ Audit trail activé
□ CSRF Protection activée (par défaut)
□ Agent-to-Controller Security activé
Configuration sécurisée complète
# jenkins.yaml
jenkins:
systemMessage: "Jenkins - Production Environment"
# Désactiver les builds sur le controller
numExecutors: 0
mode: EXCLUSIVE
# Sécurité
securityRealm:
ldap:
configurations:
- server: "ldap.example.com"
rootDN: "dc=example,dc=com"
authorizationStrategy:
roleBased:
roles:
global:
- name: "admin"
permissions: ["Overall/Administer"]
entries:
- group: "jenkins-admins"
- name: "developer"
permissions: ["Overall/Read", "Job/Build", "Job/Read"]
entries:
- group: "developers"
# Protection CSRF
crumbIssuer:
standard:
excludeClientIPFromCrumb: false
# Sécurité Agent-Controller
remotingSecurity:
enabled: true
security:
scriptApproval:
approvedSignatures: [] # Rien d'approuvé par défaut
globalJobDslSecurityConfiguration:
useScriptSecurity: true
unclassified:
location:
url: "https://jenkins.example.com/"
adminAddress: "[email protected]"