Secure Software Development durch CI

Teil I - Abhängigkeiten

Posted on July 17, 2018

Einleitung

Heutige Software baut zum großen Teil auf Bibliotheken auf die frei verfügbar sind. Neue Funktionen müssen nicht immer komplett neu implementiert werden, wenn sie schon öffentlich verfügbar vorhanden vorliegen. Oftmals kann ein Entwicklungsteam also auf Bibliotheken der Open-Source Welt zurückgreifen. Das ist kostengünstig und bringt zudem den Vorteil mit sich, dass etwas schon von vielen Augen überdacht und getestet wurde, bevor es in das eigenen Projekt wandert. Man kann sogar sagen, dass die heutige Software aufgrund der Komplexität kaum mehr ohne diese Abhängigkeiten aus der Open-Source-Welt auskommt.

Größere Projekte haben da schonmal unzählige Abhängigkeiten, die sie für verschiedene Features benötigen.

Sicherheitslücken

Bei der zum Teil riesigen Menge ist es nur eine Frage der Zeit bis eine der Abhängigkeiten eine Sicherheitslücke aufweist. Das führt dazu das ein System nach und nach für Angriffe anfällig wird, wenn die Lücken nicht beseitigt werden.

Es ist nicht einfach Lücken in Abhängigkeiten manuell zu finden. Das liegt an verschiedenen Gründen:

  1. Der Code kommt von extern, wird also nicht im einbindenden Projekt gewartet.
  2. Die externe Bibliothek ist zumeist gezippt und kompiliert eingebunden, wodurch eine Analyse erschwert wird.
  3. Bestehende Abhängigkeiten werden zumeist nicht automatisch geliftet, da dadurch entstehende Seiteneffekte nicht transparent in einem Projekt einsehbar sind, sodass selbst wenn die Entwickler der Abhängigkeit etwas beheben, es unter Umständen nicht direkt in das Projekt hineinwandert.

Das heißt im Umkehrschluss: Das einbindendende Projekt muss anderweitig mitbekommen, dass neue Lücken in Abhängigkeiten vorhanden sind und das zeitnah. Doch wie können diese fehlerhaften Bibliotheken erkannt und frühzeitig sichtbar gemacht werden?

Dieser erste Teil der Reihe Secure Software Development beschreibt wie man ein Java-Programm auf Abhängigkeiten prüfen kann.

Konkret behandele ich:

  • … wie man Abhängigkeiten auf Kommandozeile prüft (Abschnitt Prüfung auf der Kommandozeile).
  • … die gleiche Prüfung in einem nachgelagerten System genannt Jenkins durchführen kann. Der Hauptteil bezieht sich dabei auf dieses System (Abschnitt Jenkins).
  • … wie man die gleichen Abhängigkeiten über ein Tool namens Sonarqube prüfen kann. Sonarqube dient dazu Metriken in einem Projekt zu berechnen (Abschnitt Sonarqube).
  • … schließlich gibt es eine kleine Zusammenfassung dieses Blogs (Abschnitt Zusammenfassung).

Zunächst schauen wir uns das eigentlich zugrundeliegende Werkzeug dieses gesamten Blogartikels an:

Prüfung auf der Kommandozeile

OWASP Dependency Check

OWASP Dependency Check ist ein Tool, mit welchem man die Abhängigkeiten in seinem Projekt auf Sicherheitslücken prüfen kann:
Es nimmt vorhandenene Bibliotheken eines Projektes und prüft sie gegen eine Datenbank auf Sicherheitslücken. Als Datenbank zieht das Tool hierbei die National Vulnerability Database, kurz NVD heran. In dieser Datenbank stehen zu jeder Bibliotheksversion etwaige bereits bekannte Meldungen.

nvd

Im Java-Universum kann Dependency Check auf zwei Varianten eingesetzt werden:

  1. Eingebaut in ein Build-Management-System
  2. Per Kommandozeile

Die folgende Beschreibung zeigt, wie man es per Kommandozeile einsetzt und wie man ein Continuous Integration und Inspection-System verwenden kann.

Ein Continuous Integration System ermöglicht automatisiert über aktuellen Code zu gehen. Das wird im einfachsten Fall verwendet um zu sehen, ob der aktuelle Stand durchgebaut bzw. sich beim Einchecken von neuem Code Fehler ergeben haben.

Ein Continous Inspection System auf der anderen Seite zeigt direkt nach Meldung an das System, ob bestimmte Metriken des Codes erfüllt sind.

Sowohl Continuous Integration als auch Inspection melden Fehler und Warnungen z.B. direkt über E-Mail. Sie können nun mit Dependency Check ergänzt werden, sodass nach dem Einbau einer neuen Bibliothek die Entwickler mitbekommen ob es hier zu Problemen kommt.

Zur Veranschaulichung werden wir in diesem Artikel eine Beispiel-Infrastruktur per Docker verwenden, sodass der Leser die einzelnen Schritte direkt selbst nachvollziehen kann und vielleicht vor Einführung in sein Projekt, das Ganze selbst schonmal antesten kann.

Die Schritte hier sind für einen Mac beschrieben, sollten aber ähnlich auch anderen Betriebssystemen funktionieren

Benötigte Software

Zunächst wird benötigt:

  1. OWASP Dependency Checker
  2. Apache maven
  3. Docker

Die Installation von 1 und 2 kann mittels folgendem Befehl durchgeführt werden:

brew update && brew install maven dependency-check

Docker kann entsprechend seiner Homepage installiert werden.

Wir werden nun im Verlauf dieser Anleitung diese Werkzeuge verwenden.

Container IDs

In den nachfolgenden Beschreibung zeigt <containerid_namedescontainer> die Docker ID des jeweiligen Containers an. Diese ID kann über den Befehl docker ps -a bei laufendem Docker ermittelt werden.

Beispielinfrastruktur

Als nächstes bauen wir eine Infrastruktur mit einem Continuous Integration Server auf. Hierfür verwenden wir Jenkins und Docker.

Zunächst erstellen wir die nötige Verzeichnisstruktur und bauen einen lokalen Git-Server. Wichtig zu beachten: Das Wurzelverzeichnis secureci ist Ausgangsbasis für viele der nachfolgenden Befehle. Der Leser sollte also darauf achten, dass er aus diesem Verzeichnis heraus agiert.

# Clone repo with branch java
git clone -b java https://github.com/secf00tprint/secureci.git 
cd secureci
# Create mount directories for docker
./init.sh

Danach sollte das Verzeichnis secureci so aussehen:

secureciinit

Ein Java-Projekt scannen

Um ein Java-Projekt zu mit OWASP Dependency Check zu scannen können folgenden Schritte im Wurzelverzeichnis des secureci Projektes ausgeführt werden:

1 Aufsetzen eines Java-Beispiel-Projektes:

cd localproject

mvn archetype:generate \
-DarchetypeGroupId=org.apache.maven.archetypes \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DarchetypeVersion=1.3 \
-DgroupId=de.mydomain \
-DartifactId=old_depproject \
-Dversion=1.0-SNAPSHOT \
-Dpackage=de.mydomain -B

cd old_depproject

2 Alte Abhängigkeiten löschen und Herunterladen der aktuellen Abhängigkeiten in das Projekt:

mvn clean dependency:copy-dependencies

3 Kopieren aller Abhängigkeiten in ein Unterverzeichnis alllibs:

if [ -d alllibs ]; then; rm -rf ./alllibs; fi;\
mkdir ./alllibs;\
find . -iname '*.jar' -exec cp {} ./alllibs/ \; 2> /dev/null;\
find . -iname '*.class' -exec cp {} ./alllibs/ \; 2> /dev/null

4 Starten des Scanners:

dependency-check \
--project "Example Project" \
-s ./alllibs \
-l dependency.log

depcheckout

5 Auswertung der Ergebnisse:

open dependency-check-report.html

Um nun ein Ergebnis mit Lücken zu provozieren ergänzen wir die vom Projekt genutzten Abhängigkeiten mit einer Bibliothek die eine Sicherheitslücke beinhaltet.

Hierzu hängen wir einen dependencies Abschnitt, in der Datei pom.xml an, wodurch Maven die neue Library beim erneuten Bauen miteinbinden wird:

<dependencies>
 ...
      <dependency>
         <groupId>commons-fileupload</groupId>
         <artifactId>commons-fileupload</artifactId>
         <version>1.2.2</version>
     </dependency>
</dependencies>

und führen die Schritte 2 bis 5 nochmal aus.

Jetzt sollte im Ergebnis-Report eine Lücke vorhanden sein:

open dependency-check-report.html

reportwithfinding

Gitserver erstellen

Nun können wir den Gitserver bauen (Der Server basiert auf dem Code von dem Projekt jkarlosb Gitserver - vielen Dank für dieses wirklich coole Docker Image):

cd gitserver
docker build -t gitserver .
cd ..

Wir erzeuge einen Schlüsselpaar (jeweils Enter/Enter für die Passphrase):

cd mykeys
ssh-keygen -t rsa -f gitkey
cp gitkey.pub ../mnt/gitserver/keys
cd ..

Nun kann man den Gitserver laufen lassen: Wir müssen darauf achten, dass wir uns im Wurzelverzeichnis befinden.

docker run -d -p 127.0.0.1:22:22 \
-v `pwd`/mnt/gitserver/keys:/git-server/keys \
-v `pwd`/mnt/gitserver/repos:/git-server/repos \
gitserver

Eine Prüfung ob man den Server erreichen kann, kann man machen (Wurzelverzeichnis!) mit:

ssh git@127.0.0.1 -i mykeys/gitkey

Anmeldung möglich

Als Rückmeldung sollte kommen:

Welcome to git-server-docker!
You've successfully authenticated, but I do not
provide interactive shell access.
Connection to 127.0.0.1 closed.

Keine Anmeldung möglich

Folgende Fehlermeldung kann erscheinen, sollte man die IP schonmal vergeben haben:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!

Zur Lösung die Datei ~/.ssh/known_hosts öffnen und die entsprechende Zeile mit Server entfernen bzw. mit # auskommentieren.

Erstellen eines externen Git-Repos

Nun erstellen wir lokal ein Git-Projekt und kopieren das Repo in unseren Gitserver:

cd localproject 
git init --shared=true 
git add . 
git commit -m "my first commit" 
cd .. git clone --bare localproject mnt/gitserver/repos/project.git
docker restart <containerid_gitserver>

Man kann nun testen ob der Code committed wurde mittels:

ssh-add mykeys/gitkey
mkdir temp && cd temp
git clone ssh://git@127.0.0.1/git-server/repos/project.git
cd ..

Danach kann das Temp-Verzeichnis mittels

rm -rf temp

wieder gelöscht werden.

Jenkins

Im folgenden Kapitel bauen und verwenden wir nun das Continuous Integration System namens Jenkins.

jenkins

Jenkins zum Laufen bringen

Als nächstes starten wir Jenkins.

Hierfür gehen wir in das Verzeichnis conintserver:

cd conintserver

und bauen das Image:

docker build -t conintserver .
cd ..

Danach starten wir den Server aus dem Wurzelverzeichnis mit:

docker run -p 127.0.0.1:8080:8080 -p 127.0.0.1:50000:50000 -v `pwd`/mnt/conintserver/jkhome:/var/jenkins_home -v `pwd`/mnt/conintserver/project/:/home conintserver

Und öffnen den Server im Browser

open http://127.0.0.1:8080

In der Docker-Konsole findet sich ein Eintrag:

*************************************************************
*************************************************************
*************************************************************

Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:

XXXXXXXXX

This may also be found at: /var/jenkins_home/secrets/initialAdminPassword

*************************************************************
*************************************************************
*************************************************************

Wir kopieren den Code XXXXXXXXX aus den Logs in das Browsereingabefeld.

Falls die Ausgabe von Docker gerade nicht verfügbar ist, kann sie auch mittels

docker logs <containerid_jenkins> --tail 30

angezeigt werden.

unlock_jenkins

unlock_jenkins2

Als nächstes klicken wir auf ‘Continue’

und auf ‘Install suggested plugins’:

installsuggestedplugins

installsuggestedplugins2

Im nachfolgenden Formular tragen wir einen Namen, Passwort und E-Mail ein und merken uns diese Daten:

installsuggestedplugins2

createadmin

Also zB:

Username Password Fullname E-Mail
secf00tprint zB mittels pwgen -ync 40 auf der Kommandozeile secf00tprint  youraccount@yourdomain.tld

(pwgen kann installiert werden mit brew install pwgen)

Klick auf Speichern und Weiter.

Wir setzen die Jenkins URL:

jenkinsurl

und zuletzt speichern wir und beenden.

startusingjenkins

Dependency Check als Plugin installieren

  1. Zunächst wählen wir Jenkins managen aus: installdepcheck1
  2. Dann Managen von Plugins: installdepcheck2
  3. Verfügbare Plugins: installdepcheck3
  4. OWASP Dependency Check: installdepcheck4 installdepcheck5
  5. Wir wählen Herunterladen und nach Neustart installieren: installdepcheck6
  6. Wir klicken auf ‘Jenkins neustarten, wenn die Installation vollständig ist’ installdepcheck7 installdepcheck8

Wir schauen in die Docker Logs bis INFO: Jenkins is fully up and running dort steht.

Am Ende gehen wir auf http://127.0.0.1:8080 und loggen uns mit den vorhandenen Credentials ein.

Git Credentials hinterlegen

Zunächst müssen wir für unseren Git Docker die entsprechenden Credentials in Jenkins hinterlegen:

Hierfür gehen wir im Hauptmenü auf ‘Credentials’:

addcredentials1

Dann auf ‘Global Credentials’, ‘Add Credentials’ anklicken:

addcredentials2

Wählen ‘SSH Username with private key’:

addcredentials3

Tragen als Nutzer ‘git’ ein und den privaten Schlüssel:

addcredentials4

Den privaten Schlüssel kann man sich zum Kopieren mit

cat mykeys/gitkey

auf der Konsole ausgeben lassen.

Klicken auf ‘Ok’, damit sind die Credentials für unser Git in Jenkins hinterlegt.

Varianten OWASP Dependency Check Jenkins

Nun gibt es 2 Möglichkeiten OWASP Dependency Check über sein Projekt laufen zu lassen:

Entweder man führt es als Teil eines Pipeline-Builds aus oder als Teil eines Standard GUI-Builds.

Im folgenden werden beide Varianten erklärt.

Die beiden Build-Varianten unterscheiden sich auf folgende Weise:

Beim Jenkins Pipeline-Build beschreibt das Projekt-Team die Schritte des Builds in einer Textdatei. Dies kann entweder über eine Groovy-ähnliche Skript-Sprache erfolgen oder in einer deklarativen Schreibweise. Die nachfolgenden Beispiele verwenden die deklarative Schreibweise.

Bei einem Standard GUI Build klickt das Projekt-Team die entsprechenden Punkte über die GUI zusammen.

Vorteil der GUI:

Die GUI ist zunächst beim Erstellen der Vorgänge durch Visualisierung mit Grafiken leichter verständlich. Die Syntax und Befehle der Pipeline-Definitionen müssen nicht bekannt sein.

Vorteil der Pipeline:

Beim Build werden die definierten Einzelschritte schön grafisch dargestellt. Die Logs der einzelnen Schritte können angeklickt werden und der Programmierer kann, wenn es so konfiguriert ist, das Script lokal bearbeiten und einsehen ohne auf den Jenkins per Browser zugreifen zu müssen.

Jenkins Pipeline einrichten

Globale Tools definieren

Wir wählen im Hauptmenü unter ‘Manage Jenkins’, ‘Global Tool Configuration’ aus:

globaltoolconfiguration1

Die hier definierten Namen werden später von der Pipeline verwendet.

So setzen wir unter Maven als Name ‘Maven 3.3.9’ und wählen Maven 3.3.9 aus:

globaltoolconfiguration2

und klicken auf ‘Save’.

NVD Update Pipeline Item

Um nicht jedes Mal die NVD-Datenbank komplett neu herunterziehen zu müssen, sollte man zunächst ein periodischer Job definieren, der, unabhängig von der eigentlichen Analyse, die Datenbank aktualisiert, sodass diese von dem Prüfungsjob schnell herangezogen werden kann.

Hierfür erzeugen wir ein Pipeline-Projekt:

Wir klicken ‘New-Item’:

createnewnvdupdatepipeline

wählen Pipeline-Projekt aus, vergeben einen Namen (hier ‘depcheck-nvdupdate’) und klicken auf ‘Ok’:

createnewnvdupdatepipeline2

Als Build Triggers wählen wir ‘Build periodically’ aus und tragen ‘@daily’ ein, dann wird die Datenbank täglich aktualisiert:

createnewnvdupdatepipeline3

Unter Pipeline tragen wir folgende Deklaration ein und klicken auf ‘Save’:

pipeline {
    agent any
    stages {
        stage ('Dependency Check Update') {
            steps {
                dependencyCheckUpdateOnly '/var/jenkins_home/depcheck/nvdupdates'
            }
        }
    }
}

createnewnvdupdatepipeline4

Damit wird definiert, dass auf dem Jenkins das Update in dem Verzeichnis /var/jenkins_home/depcheck/nvdupdates landet.

Danach bauen wir die Datenbank über ‘Build Now’:

runnvdupdate1

Die Ausgabe sollte wie folgt aussehen:

runnvdupdate2

Die Konsolenausgabe kann man sich über den blauen Ball anzeigen lassen:

runnvdupdate3

und könnte dann so aussehen:

runnvdupdate4

OWASP Dependency Check Pipeline Item

Nun hinterlegen wir ein Pipeline-Projekt, dass unseren Code prüft:

Wir klicken wie im vorherigen Kapitel im Hauptmenü auf ‘New Item’, wählen ‘Pipeline’ aus und als Name z.B. ‘projectpipeline_depcheck’.

Nun wählen wir unter ‘Pipeline’, ‘Pipeline script from SCM’. Das bedeutet wir werden das Pipeline-Script aus unserem Repository ziehen, sodass wir es lokal definieren und ändern können:

depcheckpipeline1

Hier setzen wir die Repository-URL für unseren Docker-Gitserver.

Um die interne IP des Docker-Servers zu bestimmen, geben wir folgendes auf dem Host ein:

docker inspect <containerid_gitserver> |grep -i "\"ipaddress"

Diese verwenden wir und geben als Repository-URL ein:

ssh://git@<ip_gitserver_docker>/git-server/repos/project.git

zum Beispiel:

ssh://git@172.17.0.4/git-server/repos/project.git

Als nächstes wählen wir die zuvor definierten Credentials aus - in dem Fall die mit ‘git’ gekennzeichnete Auswahl im Dropdown-Menü:

depcheckpipeline2

Hiernach sollte das Repository erkannt werden.

Als nächstes setzen wir Jenkinsfile als ein Pipeline-Script aus unserem Repository.

Danach klicken wir auf ‘Save’.

Lokales Jenkinsfile definieren - Einleitung

Wir gehen in unser lokales Git-Projekt:

cd localproject

und erstellen im Wurzelverzeichnis die Datei Jenkinsfile mit folgendem Inhalt:

pipeline {
    agent any
    tools {
        maven 'Maven 3.3.9'
    }
    stages {
        /*
        stage ('Initialize') {
            steps {
                sh 'echo === init stage ==='
                // e.g. "M2_HOME = ${M2_HOME}"
            }
        }

        stage ('Build') {
            steps {
                sh 'echo === build stage ==='
                sh 'cd old_depproject; mvn -Dmaven.test.failure.ignore=true install'
            }
        }

        stage ('Test') {
            steps {
                sh 'echo === test stage ==='
            }
        }
        */

        stage ('Dependency Check') {
            steps {
                sh 'echo === security stage ==='
                sh 'echo === OWASP dependency check ==='
                sh 'cd old_depproject; mvn clean dependency:copy-dependencies'
                sh 'cd old_depproject; ./aggregatefordepcheck.sh'
                dependencyCheckAnalyzer scanpath: 'old_depproject', \
                  outdir: 'depcheck/report', \
                  datadir: '/var/jenkins_home/depcheck/nvdupdates', \
                  hintsFile: '', \
                  includeVulnReports: true, \
                  includeCsvReports: true, \
                  includeHtmlReports: true, \
                  includeJsonReports: true, \
                  isAutoupdateDisabled: true, \
                  skipOnScmChange: false, \
                  skipOnUpstreamChange: false, \
                  suppressionFile: '', \
                  zipExtensions: ''

               dependencyCheckPublisher pattern: 'depchec/report/dependency-check-report.xml', \
                  failedTotalAll: '0', \
                  usePreviousBuildAsReference: false
            }
        }
    }
}
Lokales Jenkinsfile definieren - Mögliche Parameter

Die hier definierten Parameter für das OWASP Dependency Check Plugin können auch nachgeschlagen werden:

https://jenkins.io/doc/pipeline/steps/dependency-check-jenkins-plugin/

Im Wesentlichen kann gesteuert werden:

DependencyCheckAnalyzer:

Parameter Beschreibung Typ Beispiel
scanpath Pfad zum Scannen String 'old_depproject'
outdir Ausgabeverzeichnis String 'depcheck/report'
datadir Datenverzeichnis String '/var/jenkins_home/depcheck/nvdupdates'
suppressionFile Suppression File (mehr dazu weiter unten) String 'suppression.xml'
hintsFile Genutzt um False Negatives zu bestimmen String 'hintsfile.xml'
zipExtensions Spezifiziert, welche Dateiendungen als Zip behandelt werden String 'jar'
isAutoupdateDisabled Deaktiviert das automatische NVD Update beim einem Lauf Boolean 'true'
includeHtmlReports Erzeugt einen optionalen HTML Report Boolean 'false'
includeVulnReports Erzeugt einen optionalen Schwachstellen Report Boolean 'true'
includeJsonReports Erzeugt einen optionalen JSON Report Boolean 'false'
includeCsvReports Erzeugt einen optionalen CSV Report Boolean 'true'
skipOnScmChange Überspringe, falls ausgelöst durch SCM Veränderungen Boolean 'false'
skipOnUpstreamChange Überspringe, falls ausgelöst durch Upstream Veränderungen Boolean 'true'

DependencyCheckPublisher:

Parameter Beschreibung Typ Beispiel
pattern Dependency Check Ergebnis Datei(en) String ''**/dependency-check-report.xml'
usePreviousBuildAsReference Nutze den vorherigen Build Boolean 'false'

Über bestimmte Parameter kann man dem DependencyCheckPublisher-Aufruf mitgeben, welche Rückmeldungen er bei verschiedenen Findings geben soll. Wir nehmen hier erstmal folgenden Parameter:

failedTotalAll: '0' 

das heißt der Build schlägt fehl bzw wird rot, sobald mindestens ein Finding gefunden wurde.

Man könnte auch

unstableTotalAll: '0' 

setzen.

Dann würde der Build gelb bzw instabil, sobald mindestens ein Finding gefunden wird.

Neben diesen beiden relativ grundlegenden Grenzwerten können auch detailliertere Angaben gemacht werden, wann ein Build gelb oder rot markiert werden soll.

Folgende Parameter lassen sich Stand Juli 2018 definieren:

Parameter
failedNewAll
failedNewHigh
failedNewLow
failedNewNormal
failedTotalAll
failedTotalHigh
failedTotalLow
failedTotalNormal
unstableNewAll
unstableNewHigh
unstableNewLow
unstableNewNormal
unstableTotalAll
unstableTotalHigh
unstableTotalLow
unstableTotalNormal


Lokales Jenkinsfile definieren - Aggregieren der Abhängigkeiten

Zum Bauen der Abhängigkeiten erstellen wir noch unter localproject/olddeproject folgendes Shell-Skript aggregatefordepcheck.sh:

#! /bin/bash
[[ -d alllibs ]] || mkdir ./alllibs; find . -iname '*.jar' -exec cp {} ./alllibs/ 2>/dev/null \; ; find . -iname '*.class' -exec cp {} ./alllibs/ 2>/dev/null \;
Lokales Jenkinsfile definieren - Finales Pipeline Skript

Alles zusammen pushen wir in unser Docker-Repo:

git add .
git commit -m "Jenkinsfile and Maven Aggregate Script"
git push origin master

Wenn wir nun in Jenkins unser Item projectpipeline_depcheck auswählen und auf ‘Build Now’ klicken, sollte folgende Ausgabe erscheinen:

rundepcheck1

bzw als Konsolenausgabe:

rundepcheck2

Aus den Logs kann auch der Pfad des Reports auf dem Jenkins-Server entnommen werden:

open ../mnt/conintserver/jkhome/workspace/projectpipeline_depcheck/depcheck/report/dependency-check-report.html

rundepcheck3

Prüfung, ob es funktioniert

Als nächstes fügen wir eine Abhängigkeit ein, welche problematisch ist.

Wir ergänzen unsere pom.xml mit:


<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.2.2</version>
</dependency>

dazu gehen wir in localproject/old_depproject und erweitern den dependencies-Abschnitt:

checkdepcheckpipeline1

Danach pushen wir die neue pom.xml und bauen das Item neu.

git add pom.xml
git commit -m "add old commons fileupload"
git push origin master

checkdepcheckpipeline2

Ergebnis - der Build sollte fehlschlagen:

checkdepcheckpipeline3 checkdepcheckpipeline4

und das entsprechende HTML entnommen aus den Jenkins-Logs spiegelt das Issue auch wieder:

open ../mnt/conintserver/jkhome/workspace/projectpipeline_depcheck/depcheck/report/dependency-check-report.html

checkdepcheckpipeline5

Testauswertung modifizieren

Wenn man nicht gleich eine rote Ampel erzeugen möchte, kann man auch anstatt

failedTotalAll ein unstableTotalAll im Jenkinsfile verwenden:

> cat Jenkinsfile                                                                     
pipeline {
    agent any
    tools {
        maven 'Maven 3.3.9'
    }
    stages {
        /*
        stage ('Initialize') {
            steps {
                sh 'echo === init stage ==='
                // e.g. "M2_HOME = ${M2_HOME}"
            }
        }

        stage ('Build') {
            steps {
                sh 'echo === build stage ==='
                sh 'cd old_depproject; mvn -Dmaven.test.failure.ignore=true install'
            }
        }

        stage ('Test') {
            steps {
                sh 'echo === test stage ==='
            }
        }
        */

        stage ('Dependency Check') {
            steps {
                sh 'echo === security stage ==='
                sh 'echo === OWASP dependency check ==='
                sh 'cd old_depproject; mvn clean dependency:copy-dependencies'
                sh 'cd old_depproject; ./aggregatefordepcheck.sh'
                dependencyCheckAnalyzer scanpath: 'old_depproject', \
                  outdir: 'depcheck/report', \
                  datadir: '/var/jenkins_home/depcheck/nvdupdates', \
                  hintsFile: '', \
                  includeVulnReports: true, \
                  includeCsvReports: true, \
                  includeHtmlReports: true, \
                  includeJsonReports: true, \
                  isAutoupdateDisabled: true, \
                  skipOnScmChange: false, \
                  skipOnUpstreamChange: false, \
                  suppressionFile: '', \
                  zipExtensions: ''

               dependencyCheckPublisher pattern: 'depcheck/report/dependency-check-report.xml', \
                  unstableTotalAll: '0', \
                  usePreviousBuildAsReference: false
            }
        }
    }
}

depchecksetwarning1 depchecksetwarning2

Jenkins Standard Build

Neben der Definition über Pipeline-Skripte kann man das OWASP Dependency Check Plugin auch ganz standardmäßig über die GUI klicken.

Hierbei ist die Konfiguration übersichtlicher, aber die Ausgabe nachher nicht so modular und verständlich.

NVD Update GUI Item

Die Konfiguration läuft in diesen Schritten:

  1. ‘New Item’: createnewnvdupdategui1
  2. ‘Build Triggers’ ‘Build periodically’: createnewnvdupdategui2
  3. ‘Build’ ‘Add build step’ ‘Invoke Dependency-Check NVD update only’: createnewnvdupdategui3
  4. Data directory: /var/jenkins_home/depcheck/nvdupdates createnewnvdupdategui4
  5. ‘Save’

Danach über ‘Build Now’ den Build anstoßen.

createnewnvdupdategui5

Die Konsolenausgabe sollte wie folgt aussehen:

createnewnvdupdategui6

OWASP Dependency Check GUI Item

Auf Basis der über das Pipeline-Script oder GUI-Item erzeugten NVD-Datenbank auf dem Jenkins können wir nun die Abhängigkeiten wie folgt testen lassen:

depcheckgui1

Wir klicken auf ‘Ok’.

Unter ‘Source Code Management’ tragen wir den Git Server aus dem Docker Netz ein:

ssh://git@172.17.0.4/git-server/repos/project.git

depcheckgui2

und wählen die Credentials ‘git’ aus:

depcheckgui3 depcheckgui4

Dann geht es unter ‘Build’ zu ‘Invoke top-level Maven targets’:

depcheckgui5a

Nun ist das installierte globale Tool auszuwählen:

depcheckgui5b

Nun entscheiden wir uns für ‘Advanced’. Als Goals clean dependency:copy-dependencies einstellen und Ort der pom.xml wählen

depcheckgui5c

Under ‘Build’ we add another build step using ‘Add build steps’: ‘Invoke Dependency-Check analysis’:

depcheckgui6

Wir wählen ‘Advanced’ und dann:

  • Path to scan: old_depproject
  • Output directory: depcheck/report
  • Data directory: /var/jenkins_home/depcheck/nvdupdates

depcheckgui7

‘Add post-buid action’

depcheckgui8

Als nächstes ist es ‘Advanced’:

  • Dependency Check results: depcheck/report/dependency-check-report.xml
  • Status Thresholds, zB:
  • All priorities Warnung bei: 5, Failure bei: 10
  • Priority high: Warnung bei: 2, Failure bei: 10

depcheckgui9

Danach gehen wir auf ‘Save’.

Über ‘Build now’ den Buildprozess anstoßen.

depcheckgui10

Das Ergebnis ist gelb, wenn wir die pom.xml mit der dependency commons-fileupload bestückt haben - ansonsten blau.

Ebenso zeigt die Konsolenausgabe bei veralteter commons-fileupload Abhängigkeit unstable an:

depcheckgui11

Findings unterdrücken

Manchmal müssen bestimmte Findings ausgeschaltet werden. Das kann unterschiedliche Gründe haben:

  1. Es handelt sich um einen False Positive
  2. Man kann die Library aus bestimmten Gründen nicht ändern

Hierfür dient das sogenannte Suppression File.

Eine Suppression-File ist eine XML-Datei mit folgendem Aufbau:

structuresuppressionfile

In den suppress-Bereichen kann dann aufgelistet werden, was nicht beachtet werden soll.

Die Justierung pro suppress-Bereich erfolgt über 2 Suchkriterien:

Zum einen kann angegeben werden, welche Abhängigkeiten nicht beachtet werden sollen, etwa auf bestimmte Jar-Dateien. Zum anderen definiert man, welche Schwachstelle ausgeschlossen werden soll.

Beispiel:

<suppress>
        <notes><![CDATA[
        This suppresses cpe:/a:csv:csv:1.0 for some.jar in the "c:\path\to" directory.
        ]]></notes>
        <filePath>c:\path\to\some.jar</filePath>
        <cpe>cpe:/a:csv:csv:1.0</cpe>
</suppress>

unterdrückt das Finding cpe:/a:csv:csv:1.0 in der Abhängigkeit c:\path\to\some.jar.

Suppress-Sectionen erzeugen

Nachdem ein HTML-Dependency-Check Report erzeugt wurde kann man sich direkt aus dem Report bei einem Finding die zugehörige Unterdrückung, also den Suppress-Abschnitt ausgeben lassen:

depchecksuppressfromhtmlrep1

Durch das Klicken auf den Suppress-Button bekommt man direkt das Snippet angezeigt:

depchecksuppressfromhtmlrep2

Dieses kann man nun in die XML hineinkopieren.

Suppression-File in Pipeline-Projekten

Wie weiter oben erwähnt dient zur Definition des Suppression-Files ein Parameter namens ‘suppressionFile’.

Ein Auszug aus einem Jenkinsfile könnte wie folgt aussehen:

pipeline {
    agent any
    ...
    stages {
    ...
        stage ('Dependency Check') {
            steps {
                ...
                dependencyCheckAnalyzer scanpath: 'old_depproject', \
                  outdir: 'depcheck/report', \
                  ...
                  suppressionFile: 'suppression.xml', \
                  zipExtensions: ''

               dependencyCheckPublisher pattern: 'depcheck/report/dependency-check-report.xml', \
                  unstableTotalAll: '0', \
                  usePreviousBuildAsReference: false
            }
        }
    }
}

Suppression-File in der GUI

Verwendet man die Standard-GUI, so findet sich das Suppression-File unter ‘Build’ ‘Invoke Dependency-Check analysis’ ‘Suppression File’:

depchecksuppressionfilegui

Sonarqube

Neben dem Einbau von Dependency Check in dem Continuous Integration System Jenkins kann das Analysetool auch noch in anderen nachgelagerten Systemen eingebaut werden.

Ein Beispiel ist das Continuous Inspection Werkzeug Sonarqube mit dem Entwickler den eingecheckten Code auf bestimmte Metriken prüfen können wie etwa der Testabdeckung.

Dieses Kapitel beschreibt wie man Sonarqube mit OWASP Dependency Check ergänzen kann.

Wieder wird anhand eines Beispiel-Dockers gearbeitet, sodass vor einer Einführung in das eigene Projekt die Maßnahmen geprüft werden können.

Um Sonarqube per Docker zu starten geben wir folgenden Befehl im Wurzelverzeichnis ein:

docker run -d --name sonarqube -p 127.0.0.1:9001:9000 -p 127.0.0.1:9092:9092 -v `pwd`/mnt/coninsserver/sonarqube_home/data:/opt/sonarqube/data -v `pwd`/mnt/coninsserver/sonarqube_home/extensions:/opt/sonarqube/extensions sonarqube:7.1

Nach einer gewissen Zeit und wenn der Port nicht belegt sein sollte erscheint im Browser unter http://127.0.0.1:9001/:

sonarqube0

und schließlich:

sonarqube0b

Sonarqube Plugin installieren

Ausgehend von der GitHub Page von dem Dependency Check Sonar Plugin clonen wir uns zunächst das Plugin und bauen es lokal:

git clone https://github.com/stevespringett/dependency-check-sonar-plugin.git
cd dependency-check-sonar-plugin
mvn clean package

Die Ausgabe von Maven zeigt uns an wo es erzeugt wurde:

sonarqubeinstalldepcheckplugin1

Nun kopieren wir die erstellte Jar in den Docker Container in das Unterverzeichnis /extensions/plugins im Sonarqube Homeordner. Der Homeordner findet sich in der Umgebungsvariablen SONARQUBE_HOME:

docker exec <containerid_sonarqube> env
...
LANG=C.UTF-8
JAVA_HOME=/docker-java-home
JAVA_VERSION=8u171
JAVA_DEBIAN_VERSION=8u171-b11-1~deb9u1
CA_CERTIFICATES_JAVA_VERSION=20170531+nmu1
SONAR_VERSION=7.1
SONARQUBE_HOME=/opt/sonarqube
SONARQUBE_JDBC_USERNAME=sonar
SONARQUBE_JDBC_PASSWORD=sonar
SONARQUBE_JDBC_URL=
...
docker cp <Pfad zur erzeugten Jar> <containerid_sonarqube>:/opt/sonarqube/extensions/plugins/

zB bei einer Container-ID xyz:

docker cp sonar-dependency-check-plugin/target/sonar-dependency-check-plugin-1.1.0-SNAPSHOT.jar xyz:/opt/sonarqube/extensions/plugins/

Um Sonarqube mit dem neuen Plugin auszustatten müssen wir das System neustarten.

Hierfür klicken wir rechts oben auf ‘Login’:

installsonarqubedepcheckplugin2

Die Standardcredentials sind:

‘admin’ ‘admin’

installsonarqubedepcheckplugin3

Zunächst werden wir aufgefordert ein Token anzulegen.

Als Name wählen wir hier project für unser Beispielprojekt und klicken auf ‘Generate’:

sonarqube0d

Als nächstes auf ‘Continue’:

sonarqube0e

Als Programmiersprache wählen wir ‘Java’ und als Build-Technologie ‘Maven’. Wir kopieren uns den Kommandozeilenbefehl über den ‘Copy’-Knopf’ und vermerken ihn für später.

sonarqube0f

Dann beenden wir beenden die Anleitung rechts unten mit ‘Finish this tutorial’,

und gehen auf ‘Administration’:

sonarqubeclicktoadministration

Wählen ‘System’

sonarqube0c2

und ‘Restart Server’

sonarqube0c3

‘Restart’

sonarqube0c4 sonarqube0c5

Nach einer kurzen Zeit sollte folgende Seite angezeigt werden:

sonarqube0c6

Wenn wir nun unter ‘Administration’ auf ‘Configuration’ ‘General Settings’ klicken sollte ‘Dependency Check’ mit angezeigt werden:

sonarqube0c7 sonarqube0c8

Wir haben zu diesem Zeitpunkt das Plugin installiert und besitzen ein Token für unser Beispielprojekt.

Code prüfen lassen

Unter ‘Administration’ ‘Configuration’ stellen wir folgende Pfade ein:

  • Dependency-Check HTML report path : reports/dependency-check-report.html
  • Dependency-Check report path: reports/dependency-check-report.xml

sonarqubeclicktoadministration sonarqubecodecheck1

Nun gehen wir in unser lokales Projekt. Das Projekt sollte in der Maven-Konfigurations-Datei pom.xml wie zuvor beschrieben eine angreifbare Abhängigkeit namens commons-fileupload besitzen. Um den Report für Sonarqube zu erzeugen geben wir im Wurzelverzeichnis auf der Kommandozeile folgendes ein:

cd localproject/old_depproject
mvn clean dependency:copy-dependencies
./aggregatefordepcheck.sh
[[ -d reports ]] ||mkdir reports
dependency-check --project "Example Project" -s ./alllibs -l dependency.log -f ALL -o reports
mvn sonar:sonar -Dsonar.host.url=http://127.0.0.1:9001 \
  -Dsonar.login="<token>" \
  -Dsonar.dependencyCheck.reportPath="reports/dependency-check-report.xml" \
  -Dsonar.dependencyCheck.htmlReportPath="reports/dependency-check-report.html"

Wenn wir nun auf ‘Projects’ gehen so sehen wir folgendes:

sonarqubecodecheck2

Und wenn wir auf ‘old_depproject’ gehen bekommen wir eine genaue Aufsschlüsselung:

sonarqubecodecheck2 sonarqubecodecheck3 sonarqubecodecheck4

Zusammenfassung

Wir haben nun eine Infrastruktur in welcher wie Abhängigkeiten in unseren Projekten sowohl über Jenkins als auch Sonarqube prüfen lassen können. Treten neue Sicherheitslücken werden diese von den Systemen erkannt und können gemeldet werden. Im nächsten Blogbeitrag behandeln wir wie wir neben den Abhängigkeitsanalysen in unseren Projekten, aktive Schwachstellenscans auf unser Projekt über Jenkins laufen lassen können, um so weitere Lücken finden zu können.