pipeline { agent { kubernetes { // Definieren des Pod mit 3 Containern als Build Agent, Trivy und Kaniko yaml ''' apiVersion: v1 kind: Pod spec: containers: - name: dotnet8 image: mcr.microsoft.com/dotnet/sdk:8.0 command: - cat tty: true - name: trivy image: aquasec/trivy:latest command: - cat tty: true - name: kaniko image: gcr.io/kaniko-project/executor:v1.23.1-debug command: ["sleep"] args: ["99d"] ''' } } options { // Aktivieren von Log Rotation: // *) Letzten 10 Builds werden aufbewahrt // ** Builds, die älter als 7 Tage sind, werden gelöscht buildDiscarder(logRotator(numToKeepStr: '10', artifactNumToKeepStr: '10', daysToKeepStr: '7')) } stages { // Auschecken der Sourcen zum App Projekt stage('Checkout Source') { steps { git url: 'http://130.61.26.230:30080/dev-master/secdevops-csharp-app.git', branch: 'master' } } stage('Security: Trivy Scan') { steps { // Ausführen des 'dotnet restore' für den nachfolgenden Trivy Scan, der diese wiederhergestellte Dateien/NuGet Pakete und Abhängigkeiten scanned // Generiert automatisch das obj/ mit der project.assets.json // Alle Container eines Pods teilen sich das Jenkins Arbeitsverzeichnis container('dotnet8') { sh 'dotnet restore' } // Ausführen des Trivy Scans // // Wichtig: trivy ersetzt -> dotnet list package --vulnerable --include-transitive container('trivy') { // Erzeugen des Directory zum Speichern des Reports sh 'mkdir -p reports' // Ausführen des Scans hinsichtlich Vulnerabilities, Miskonfigurationen, Secrets und Licences im Jenkins Arbeitsverzeichnis // Abbruch bei bei kritischen Fehlern (--exit-code 1 --severity HIGH,CRITICAL) sh 'trivy fs --scanners vuln,misconfig,secret,license --exit-code 1 --severity HIGH,CRITICAL --format template --template "html" -o reports/trivy-fs-report.html .' } } } // Kompilieren der Anwendung (DLLs werden erzeugt) // Schritt muss gar nicht durchgeführt werden, da kaniko das Image erzeugt stage('Build with .NET 8') { steps { // Führt den Build-Befehl im spezialisierten Container aus container('dotnet8') { sh 'dotnet --version' // Zur Bestätigung der Version sh 'dotnet build --configuration Release' // optimierter Build Prozess ohne Debug und ungenutzt Pfade } } } //stage('Unit Tests') { // steps { // container('dotnet8') { // Erstellt eine XML-Datei im Format 'junit', die Jenkins lesen kann // sh 'dotnet test --configuration Release' // sh 'dotnet test --configuration Release --logger "junit;LogFileName=results.xml"' // } //} //} stage('Set Build Name') { steps { script { // Setzen des Namen des aktuellen Laufs auf die Version + Build-Nummer currentBuild.displayName = "v1.0.0-build-${env.BUILD_NUMBER}" } } } stage('Docker Build & Push') { when { branch 'master' } steps { container('kaniko') { // Stellt die Informationen aus dem Token in Form von Umgebungsvariablen der Jenkins Pipeline zur Verfügung // Nachfolgend werden diese Credentials im JSON Format in config.json geschrieben // Vorgehen ist zwar nicht extrem sicher, aber die Lebenszeit im Container ist kurz, dass diese base64 kodierten Daten zurückverwandelt werden könnten // Erzeugen des Directory zum Speichern des Reports, falls das bei einem vorigen Schritt nicht durchgeführt wurde sh 'mkdir -p reports' withCredentials([usernamePassword(credentialsId: 'gitea-registry-token', usernameVariable: 'GITEA_USER', passwordVariable: 'GITEA_TOKEN')]) { sh ''' # WORKAROUND: Dem Container beibringen, wer git.example.com ist, ansonsten funktioniert das Übertragen des Images an Git nicht!!! echo "130.61.26.230 git.example.com" >> /etc/hosts # Erstellt die Docker-Konfiguration für Kaniko # Das $(echo ...) Kommando kombiniert User und Token für den Login echo "{\\"auths\\":{\\"130.61.26.230:30080\\":{\\"auth\\":\\"\$(echo -n \${GITEA_USER}:\${GITEA_TOKEN} | base64)\\"}}}" > /kaniko/.docker/config.json # Der Bau- und Push-Befehl # Wir taggen das Image mit 'latest' UND der Build-Nummer zur Sicherheit /kaniko/executor --context `pwd` \ --dockerfile `pwd`/Dockerfile \ --insecure \ --skip-tls-verify \ --destination 130.61.26.230:30080/dev-master/secdevops-csharp-app:latest \ --destination 130.61.26.230:30080/dev-master/secdevops-csharp-app:${BUILD_NUMBER} ''' } } } } stage('Security: Trivy Image Scan') { when { branch 'master' } steps { // Trivy Scan wird auf das Image im Git Repository angewendet. Das Image wird heruntergeladen. container('trivy') { // 1. Scan ausführen und als HTML-Report speichern (Achte auf den neuen Dateinamen) sh ''' trivy image --insecure \ --severity HIGH,CRITICAL \ --format template \ --template "html" \ --exit-code 1 \ -o reports/trivy-image-report.html \ 130.61.26.230:30080/dev-master/secdevops-csharp-app:latest ''' // Den Scan ein zweites Mal kurz ohne Report ausführen, damit die Pipeline bei Lücken blockiert // sh 'trivy image --insecure --exit-code 1 --severity HIGH,CRITICAL 130.61.26.230:30080/dev-master/secdevops-csharp-app:latest' } } } } post { failure { script { currentBuild.description = "Build Fehler. Ev. Sicherheits-Check fehlgeschlagen! Details im Trivy Security Report." } } always { // Suchen und Einelen von etwaigen Testreports von JUnit. Keine Vorhanden? -auch ok junit testResults: '**/*.xml', allowEmptyResults: true // Verwenden des HTML Publisher Modules zum Schreiben der gefundenen Testreports in das Build Menu und speichert den HTML Bericht dann historisch ab (keepAll) publishHTML([ allowMissing: false, alwaysLinkToLastBuild: true, keepAll: true, reportDir: 'reports', reportFiles: 'trivy-fs-report.html,trivy-image-report.html', reportName: 'Trivy Security Report' ]) // Schreiben des Build Status in das Build Log echo "Pipeline beendet: ${currentBuild.result}" } } }