Jenkins Security
Introduction
Jenkins security is critical because it has access to:
- Source code
- Credentials (passwords, tokens, keys)
- Production infrastructure
- Deployment pipelines
Authentication
Internal database
# 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"
Authorization
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"
Managing Credentials
Credential types
| Type | Usage |
|---|---|
| Username/Password | Git, Docker, API |
| Secret text | API tokens |
| Secret file | Kubeconfig, certificates |
| SSH Username with key | Server connections |
| Certificate | mTLS |
Create 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}"
Secure usage
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
'''
}
}
}
}
}
Credential scopes
| Scope | Visibility |
|---|---|
| GLOBAL | All of Jenkins |
| SYSTEM | Controller only |
| Folder | A folder and its children |
Securing with HTTPS
With a reverse proxy (recommended)
# /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 directly
# 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
Pipeline security
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)
}
}
}
}
}
Limiting pipeline permissions
// Désactiver des fonctionnalités dangereuses
properties([
[$class: 'BuildDiscarderProperty',
strategy: [$class: 'LogRotator', numToKeepStr: '10']],
disableConcurrentBuilds()
])
Validating Jenkinsfiles
# Valider un Jenkinsfile avant commit
curl -X POST \
-u admin:token \
-F "jenkinsfile=<Jenkinsfile" \
http://jenkins:8080/pipeline-model-converter/validate
Audit and 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"
Access logs
# Dans jenkins.yaml
jenkins:
log:
recorders:
- name: "security-log"
loggers:
- name: "jenkins.security"
level: "FINE"
- name: "hudson.security"
level: "FINE"
Security checklist
□ 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é
Complete secure configuration
# 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]"