diff --git a/Dockerfile b/Dockerfile index c322909..5126afc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,36 +1,37 @@ -# --- Stage 1: Build (Die Bau-Umgebung) --- +# Basis Image das für die Build Umgebung verwendet wird FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build-env +# Wechseln ins Arbeitsverzeichnis WORKDIR /app -# 1. Nur Projektdatei kopieren und Abhängigkeiten laden (Nutzt Docker-Caching) +# Kopieren der Projektdatei in Arbeitsverzeichnis COPY *.csproj ./ -#COPY *.sln ./ -#COPY MyHelloWorld/*.csproj ./MyHelloWorld/ -#COPY MyHelloWorld.Tests/*.csproj ./MyHelloWorld.Tests/ +# Laden der Abhängigkeiten RUN dotnet restore -# 2. Den restlichen Quellcode kopieren und die App kompilieren +# Kopieren des restlichen Quellcodes COPY . ./ -#RUN dotnet publish MyHelloWorld/*.csproj -c Release -o out +# Kompilieren eds Quellcodes (Projektdatei muss nicht zwingend angegeben werden) RUN dotnet publish "SecDevOpsLab.csproj" -c Release -o out -# --- Stage 2: Runtime (Das fertige, schlanke Image) --- +# Noch schlangere Basis Image für die Runtime Umgebung FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine +# Wechseln ins Arbeitsverzeichnis WORKDIR /app -# 3. Port für die Web-App öffnen (Standard bei .NET 8 Web-Apps ist 8080) +# Öffnen des Ports für die Web-App (Standard bei .NET 8 Web-Apps ist 8080) EXPOSE 8080 +# Konfigurieren des integrierten Kestrel-Webserver einer ASP.NET Core App ENV ASPNETCORE_URLS=http://+:8080 -# 3. Den vorinstallierten User 'app' von Microsoft nutzen -# Erlaube dem User 'app' in das Verzeichnis zu schreiben (für SQLite) +# Wechseln auf root (nur kurz) USER root +# Ändern des File Owner (wichtig dass die Sqlite DB geschrieben werden kann) RUN mkdir -p /app/data && chown -R app:app /app/data && chmod -R 775 /app/data +# Wechsel auf non-root User (app ist ein vorinstallierter User von Microsoft) USER app -# 4. Nur die fertigen Binärdateien aus der Bau-Umgebung rüberschieben +# Kopieren der fertigen Binärdateien aus der Build Umgebung COPY --from=build-env /app/out . -# 5. Startbefehl festlegen -# WICHTIG: Falls deine DLL anders heißt (z.B. MyHelloWorld.dll), passe den Namen hier an! +# Festlegen des Start Befehls ENTRYPOINT ["dotnet", "SecDevOpsLab.dll"] \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile index 3609094..3f90876 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,7 +1,7 @@ pipeline { agent { kubernetes { - // Definiert den Pod mit dem .NET 8 SDK Image + // Definieren des Pod mit 3 Containern als Build Agent, Trivy und Kaniko yaml ''' apiVersion: v1 kind: Pod @@ -26,15 +26,16 @@ pipeline { } options { - // Log-Rotation: Bewahre nur die letzten 10 Builds auf - // und lösche Berichte/Artefakte von Builds, die älter als 7 Tage sind. + // 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 { - // Ersetze 'dein-user' und 'dein-repo' durch die Namen aus Gitea git url: 'http://130.61.26.230:30080/dev-master/secdevops-csharp-app.git', branch: 'master' } @@ -42,27 +43,29 @@ pipeline { stage('Security: Trivy Scan') { steps { - // Wiederherstellen der project.assets.json in obj/ für Trivy zum finden der transtiven Abhängigkeiten + // 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') { - // Wir erstellen ein Verzeichnis für den Report + // Erzeugen des Directory zum Speichern des Reports sh 'mkdir -p reports' - - // Wichtig: trivy ersetzt -> dotnet list package --vulnerable --include-transitive - // Der Befehl erzeugt die HTML-Datei - // --format template: Nutzt ein Layout - // --template "@/contrib/html.tpl": Das Standard-Trivy-Layout - // Scannt das Dateisystem auf Schwachstellen (NuGet) und Secrets - // --exit-code 1 lässt die Pipeline bei kritischen Fehlern abbrechen - sh 'trivy fs --scanners vuln,misconfig,secret,license --exit-code 1 --severity HIGH,CRITICAL --format template --template "@/contrib/html.tpl" -o reports/trivy-fs-report.html .' + // 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 @@ -87,7 +90,7 @@ pipeline { stage('Set Build Name') { steps { script { - // Setzt den Namen des aktuellen Laufs auf die Version + Build-Nummer + // Setzen des Namen des aktuellen Laufs auf die Version + Build-Nummer currentBuild.displayName = "v1.0.0-build-${env.BUILD_NUMBER}" } } @@ -100,12 +103,18 @@ pipeline { steps { container('kaniko') { - // Nutze die ID, die du in Jenkins für den Token vergeben hast + // 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 + # 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 @@ -126,26 +135,27 @@ pipeline { } } - - 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 "@/contrib/html.tpl" \ + --template "html" \ + --exit-code 1 \ -o reports/trivy-image-report.html \ 130.61.26.230:30080/dev-master/secdevops-csharp-app:latest ''' - // 2. 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' + // 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' } } } @@ -158,11 +168,10 @@ pipeline { } } always { - // Sammelt die Testergebnisse ein (die wir im Test-Schritt erzeugen) - // Das **/ bedeutet: Suche in allen Unterordnern nach .xml Dateien + // Suchen und Einelen von etwaigen Testreports von JUnit. Keine Vorhanden? -auch ok junit testResults: '**/*.xml', allowEmptyResults: true - // Dieser Schritt macht den Report im Jenkins-Menü links sichtbar + // 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, @@ -172,7 +181,7 @@ pipeline { reportName: 'Trivy Security Report' ]) - // Meldet den Status zurück, wenn das Gitea-Plugin korrekt konfiguriert ist + // Schreiben des Build Status in das Build Log echo "Pipeline beendet: ${currentBuild.result}" } }