Daniel Bross | 10 min
BlogSoftwareCicdDevsecops

Von Push zu Produktion: unsere CI/CD-Pipelines in GitLab

Mit GitLab CI prüfen wir Codeänderungen, testen neue Versionen und bereiten ihre Auslieferung vor. Wie wir dabei Sicherheitsprüfungen, Code-Reviews und fachliche Abnahme verbinden und nach welchen Kriterien wir eine Version für den produktiven Einsatz freigeben.

Ein Sicherheitsupdate soll schnell bei den Anwendern ankommen. Gleichzeitig muss das Entwicklungsteam prüfen, ob die neue Version funktioniert und bestehende Abläufe weiterhin nutzbar sind. Dafür hilft ein Verfahren, das bereits im Alltag erprobt ist und bei jeder Änderung dieselben vereinbarten Prüfungen ausführt.

Wir nutzen dafür GitLab CI/CD. Eine Pipeline führt die einzelnen Arbeitsschritte automatisch aus, vom Prüfen des Quellcodes bis zur Bereitstellung einer testbaren Anwendung. Ergebnisse und Fehlermeldungen bleiben mit der jeweiligen Änderung verknüpft. Das Team sieht, welche Prüfung fehlgeschlagen ist und wo es nacharbeiten muss.

Continuous Integration (CI) bezeichnet das regelmäßige Zusammenführen und Prüfen von Codeänderungen. Continuous Delivery (CD) hält geprüfte Softwarestände für die Auslieferung bereit. Der produktive Einsatz ist bei uns eine eigene Freigabeentscheidung.

Frühes Feedback für die Entwicklung

Die ersten Prüfungen sollen Entwickler erreichen, solange sie noch an einer Änderung arbeiten. Ein falsch verwendeter Datentyp oder ein fehlgeschlagener Test lässt sich dann meist leichter zuordnen. Deshalb beginnen unsere Pipelines mit schnellen Codeprüfungen. Aufwendigere Tests und das Erstellen der auslieferbaren Anwendung folgen im weiteren Ablauf.

Auch Sicherheitsprüfungen gehören dazu. Sie untersuchen den eigenen Code und die eingebundenen Bibliotheken. Vor einer Freigabe kann das Team so beurteilen, ob eine Änderung neue Risiken mitbringt und welche Befunde noch bearbeitet werden müssen.

Dieses Zusammenspiel von Entwicklung, Sicherheit und Betrieb wird als DevSecOps bezeichnet. Bei uns gehört dazu auch die organisatorische Seite: Unser Informationssicherheits-Managementsystem ist nach ISO/IEC 27001 zertifiziert. Reviews und dokumentierte Freigaben ergänzen die automatisierten Prüfungen.

Gemeinsame Bausteine für unsere GitLab-Pipelines

Wir pflegen unsere Pipeline-Vorlagen in einem zentralen GitLab-Repository. Die Projekte stellen daraus die für ihre Anwendung passenden Abläufe zusammen. Das erleichtert die Wartung: Eine Verbesserung an einer gemeinsam genutzten Prüfung kann anschließend in mehreren Projekten übernommen werden.

Die Vorlagen sind nach Aufgaben aufgeteilt:

  • Eine gemeinsame Basis legt den Ablauf und grundlegende Prüfungen fest, etwa zur Projektkonfiguration und zur vorhandenen Lizenzangabe.
  • Sprach- und projektspezifische Bausteine übernehmen das Bauen und Testen der Anwendung. Eine Weboberfläche braucht hier andere Werkzeuge als ein Dienst in Go oder Python.
  • Sicherheitsbausteine prüfen Quellcode und Abhängigkeiten. Sie liefern auch die Grundlage für ein Verzeichnis der verwendeten Softwarebestandteile.
  • Bausteine für Veröffentlichung und Betrieb legen fertige Pakete und Container-Images in der vorgesehenen Ablage ab, ordnen sie einer Version zu und übernehmen die Bereitstellung in der Zielumgebung.

Die Projekte binden eine festgelegte Version der Vorlagen ein. Ein Wechsel auf eine neuere Fassung lässt sich damit pro Projekt prüfen und abstimmen. Einzelne Schritte können je nach Anwendung unterschiedlich konfiguriert werden. Ausnahmen von verpflichtenden Prüfungen müssen begründet und dokumentiert sein.

Welche KI-Werkzeuge im Projekt eingesetzt werden dürfen

Unsere Projekte unterscheiden sich auch darin, welche Daten und welcher Code an externe KI-Dienste übermittelt werden dürfen. Die Einstufung richtet sich nach den Sicherheitsanforderungen und dem Schutzbedarf des Projekts. GitLab zeigt diese Einstufung direkt am Projekt an und verlinkt die zugehörige Richtlinie.

Unser Drei-Stufen-Modell für KI in der Softwareentwicklung beschreibt die Regeln ausführlich: von der Nutzung freigegebener KI-Werkzeuge in Standardprojekten über die Beschränkung auf einzelne Codeausschnitte bis zum ausschließlichen Einsatz lokal betriebener Modelle bei besonders schutzbedürftiger Software. Die Einstufung gibt dem Team vor, welche Unterstützung es im jeweiligen Projekt nutzen darf.

Wie eine Änderung zur freigegebenen Version wird

In GitLab reichen Entwickler ihre Änderungen als Merge Request zur Prüfung ein. Dort sind der geänderte Code, die Begründung und die Ergebnisse der Pipeline gemeinsam einsehbar. Welche Prüfungen erforderlich sind, legen wir für das Projekt fest. Bei Container-Anwendungen reicht der Ablauf von der Codeprüfung bis zum kontrollierten Ausrollen einer neuen Version.

1. Den Code auf typische Fehler prüfen

Sogenannte Linter untersuchen den Quellcode auf auffällige Konstruktionen und Verstöße gegen die vereinbarten Programmierregeln. Für JavaScript und TypeScript kommt dafür beispielsweise ESLint zum Einsatz. Typprüfungen erkennen zusätzlich, wenn Funktionen mit unpassenden Daten arbeiten, etwa mit einem Text an einer Stelle, die eine Zahl erwartet.

Die Prüfregeln werden zusammen mit dem Code versioniert. Nach unseren Qualitätsvorgaben müssen auch Warnungen bearbeitet werden; notwendige Ausnahmen sind im Code zu begründen. So kann die prüfende Person nachvollziehen, warum eine Regel an einer bestimmten Stelle abweicht.

2. Nach Sicherheitsproblemen suchen

Die Sicherheitsprüfungen betrachten verschiedene mögliche Fehlerquellen:

  • Statische Codeanalyse untersucht den Quellcode auf bekannte Schwachstellenmuster, ohne die Anwendung auszuführen. Dafür nutzen wir je nach Sprache Werkzeuge wie Semgrep oder Gosec.
  • Abhängigkeitsprüfungen gleichen verwendete Bibliotheken mit bekannten Sicherheitslücken ab, beispielsweise mit Trivy.
  • Werkzeuge wie Gitleaks suchen nach versehentlich im Code abgelegten Passwörtern und Zugangsschlüsseln.
  • Prüfungen der Betriebskonfiguration können riskante Einstellungen erkennen, etwa in den Konfigurationsdateien für Kubernetes.

Die Berichte stehen dem Team in GitLab zur Verfügung. Unser zentraler Trivy-Scan unterscheidet dabei nach Schweregrad: Befunde mittlerer Schwere erscheinen im Bericht, hohe und kritische Befunde lassen den Prüfschritt fehlschlagen. Damit ist für die Beteiligten sichtbar, was eine Freigabe verhindert.

3. Verhalten und Zusammenspiel testen

Unit-Tests prüfen einzelne Funktionen, etwa eine Berechnung oder die Behandlung ungültiger Eingaben. Integrationstests untersuchen das Zusammenspiel mehrerer Komponenten, beispielsweise einer Anwendung mit ihrer Datenbank. Welche Tests sinnvoll sind, ergibt sich aus den Funktionen und Risiken des Projekts.

Mit zunehmendem Projektumfang vereinbaren wir auch eine Mindest-Testabdeckung. Sie beschreibt, wie viel des Codes bei den automatisierten Tests ausgeführt wird. Bei der Bewertung schauen wir zusätzlich darauf, ob wichtige Fehlerfälle und fachliche Regeln tatsächlich geprüft werden. Unterschreitungen des vereinbarten Mindestwerts brauchen eine dokumentierte Begründung.

4. Die Anwendung als Container bauen

Beim Build entsteht aus dem Quellcode die ausführbare Anwendung. Wir verpacken sie zusammen mit ihrer Laufzeitumgebung in ein Container-Image. Dieses Paket lässt sich anschließend in den vorgesehenen Test- und Betriebsumgebungen einsetzen.

Die benötigten Basis-Images beziehen wir über interne Registries, also Ablagen für Container-Images, die auch externe Quellen anbinden. Festgelegte Versionen helfen dabei, Abhängigkeiten nachzuvollziehen und Updates gezielt zu prüfen.

Damit wiederholte Builds weniger Arbeit verursachen, unterstützen unsere Vorlagen einen optionalen BuildKit-Cache. Er hält bereits erzeugte Bestandteile vor und verwendet sie erneut, solange sich ihre Grundlagen nicht geändert haben. Das spart Rechenzeit insbesondere bei umfangreichen Anwendungen mit vielen Abhängigkeiten.

5. Die Container-Konfiguration absichern

Eine Anwendung kann beim Betrieb mehr Rechte besitzen, als sie benötigt. Wir prüfen Container deshalb auch auf ihre Konfiguration. Dockle hilft beispielsweise dabei zu erkennen, ob eine Anwendung mit Administratorrechten startet oder unsichere Voreinstellungen mitbringt. Diese Absicherung wird auch als Container-Hardening bezeichnet.

Harbor übernimmt als Registry die Ablage der Images und unterstützt die laufende Schwachstellenprüfung mit Trivy. Erneute Scans können auch Sicherheitslücken melden, die erst nach dem ursprünglichen Build bekannt geworden sind.

6. Softwarebestandteile erfassen und Schwachstellen bewerten

Für Builds und insbesondere für veröffentlichte Versionen erstellen wir eine Softwarestückliste, die Software Bill of Materials (SBOM). Sie verzeichnet die enthaltenen Komponenten samt Versionen. Das verwendete CycloneDX-Format erlaubt anderen Werkzeugen, diese Angaben automatisch auszuwerten.

Zur Auswertung und Bearbeitung der Sicherheitsbefunde setzen wir DevGuard ein. Meldet ein Scanner eine Schwachstelle in einer Bibliothek, prüfen wir, ob die betroffene Funktion in unserer Anwendung erreichbar ist und unter welchen Bedingungen sich die Lücke ausnutzen ließe. Daraus ergibt sich, ob ein Update, eine andere Schutzmaßnahme oder eine begründete Einstufung als nicht betroffen erforderlich ist.

Solche Bewertungen halten wir als VEX-Informationen fest. VEX ist ein standardisiertes Format, das beschreibt, ob ein Produkt von einer bekannten Schwachstelle betroffen ist und worauf diese Einschätzung beruht. Die Bewertung gehört zum jeweiligen Softwarestand und wird bei relevanten Änderungen überprüft.

Wo Softwarestückliste, Lizenzprüfung und Schwachstellenbewertung als verpflichtende Freigabekriterien vereinbart sind, müssen sie vollständig vorliegen. Fehlende Nachweise oder Verstöße gegen die Lizenzvorgaben blockieren die Freigabe, bis sie bearbeitet sind.

7. Die Herkunft der ausgelieferten Software prüfen

Mit Cosign lassen sich Container-Images und zugehörige Nachweise digital signieren. Harbor speichert diese gemeinsam. Eine gültige Signatur aus einer vertrauenswürdigen Quelle erlaubt später zu prüfen, wer ein Image signiert hat und ob es seitdem verändert wurde.

Wenn das Projekt es erfordert, kann die Betriebsumgebung diese Prüfung vor jedem Start erzwingen. In Kubernetes lässt sich das etwa mit Kyverno umsetzen. Dort werden dann nur Images zugelassen, die den festgelegten Signaturregeln entsprechen.

8. Eine Version auf dem Testsystem bereitstellen

Für die Bereitstellung in Kubernetes nutzen wir unser Open-Source-Werkzeug adeploy. Es erzeugt die benötigte Betriebskonfiguration aus versionierten Vorlagen und rollt die Anwendung in der vorgesehenen Umgebung aus.

Auf dem Testsystem können Ende-zu-Ende-Tests mit Playwright oder Puppeteer zusammenhängende Nutzerabläufe prüfen, vom Browser bis zu den beteiligten Diensten. Der Product Owner oder ein benannter Ansprechpartner auf Kundenseite kann dort außerdem selbst testen, ob die neue Funktion die vereinbarten Anforderungen erfüllt. Der abgenommene Softwarestand muss der später ausgelieferten Version zugeordnet werden können.

9. Die Ergebnisse an den Freigabekriterien messen

Vor einem Release, also der Veröffentlichung einer neuen Version, werden die Ergebnisse gegen die vereinbarten Qualitäts- und Sicherheitskriterien geprüft. Diese Freigabeprüfung wird auch Policy Gate genannt. Sie legt fest, welche Befunde eine Veröffentlichung stoppen und welche Voraussetzungen erfüllt sein müssen.

Das betrifft etwa fehlgeschlagene Tests, offene Sicherheitsrisiken oder unzulässige Lizenzen. Falls eine Ausnahme vertretbar ist, müssen ihre Begründung und die verantwortliche Person dokumentiert sein. Die Entscheidung bleibt dadurch auch später nachvollziehbar.

10. Die Version veröffentlichen und produktiv einsetzen

Ein Git-Tag markiert einen bestimmten Codezustand als Version und stößt die zugehörige Release-Pipeline an. Die erzeugten Pakete erhalten damit eine Versionszuordnung, über die sie später ausgewählt werden können.

Für den produktiven Einsatz wählen wir die freigegebene Version aus und stoßen die Bereitstellung gesondert an. Dieser Schritt heißt Deployment. Wer ihn ausführen darf, lässt sich über Berechtigungen beschränken. Je nach Vorgaben des Auftraggebers kann der produktive Betrieb auch einen eigenen, getrennten Auslieferungsprozess nutzen.

Definition of Done: Wann ist eine Aufgabe erledigt?

Vor der Abnahme prüfen wir für jede Entwicklungsaufgabe die relevanten Qualitäts- und Sicherheitskriterien. Diese gemeinsame Vereinbarung heißt Definition of Done und umfasst auch Aufgaben, die das Team selbst bewerten muss:

Prüfungen bestanden

Die vereinbarten Code-, Sicherheits- und Lizenzprüfungen sind erfolgreich durchlaufen, alle erforderlichen Tests bestanden. Warnungen sind bearbeitet oder durch begründete Ausnahmen abgedeckt. Die Ergebnisse gehören zum geprüften Softwarestand.

Vier-Augen-Prinzip eingehalten

Mindestens ein Kollege prüft die Änderung vor der Übernahme. Die Beschreibung erläutert Ziel, Risiken und Testhinweise. Bei wesentlichen oder sicherheitsrelevanten Änderungen führt die prüfende Person zusätzlich stichprobenartige Tests in einer lokalen Entwicklungsumgebung durch.

Bedrohungsmodell aktuell

Neue Datenflüsse oder Schnittstellen können zusätzliche Angriffsmöglichkeiten schaffen. Das Team aktualisiert deshalb bei solchen Änderungen das Bedrohungsmodell. Es hält Risiken, mögliche Auswirkungen und Schutzmaßnahmen fest und benennt, wer sich darum kümmert.

Schwachstellen bewertet

Gefundene Schwachstellen sind auf ihre Ausnutzbarkeit geprüft. Die Bewertung begründet, ob die Anwendung betroffen ist und welche Maßnahmen erforderlich sind. Sie wird zusammen mit dem Code versioniert und lässt sich der jeweiligen Veröffentlichung zuordnen.

Architektur dokumentiert

Wichtige technische Entscheidungen sind mit ihrer Begründung festgehalten, etwa zur Aufteilung der Anwendung oder zum Schutz ihrer Schnittstellen. Die Dokumentation liegt beim Code. Kollegen prüfen Änderungen daran gemeinsam mit der zugehörigen Implementierung.

Barrierefreiheit geprüft

Bei Änderungen an der Oberfläche prüfen wir die vereinbarten Anforderungen an Barrierefreiheit. Dazu prüfen wir Farbkontraste und testen die Bedienung mit der Tastatur und mit einem Screenreader, der Bildschirminhalte vorliest. Automatisierte Prüfungen ergänzen diese Tests.

Was Auftraggeber daran nachvollziehen können

Für die Planung eines Releases lässt sich in GitLab sehen, welche Prüfungen bereits bestanden sind und woran das Team noch arbeitet. Auf dem Testsystem können die fachlich Verantwortlichen die neue Funktion ausprobieren und Rückmeldung geben, bevor sie im Alltag eingesetzt wird.

Auch die Nachweise für eine spätere Prüfung entstehen während der Entwicklung. Wer eine Freigabe nachvollziehen möchte, kann vom Versionsstand zu den zugehörigen Codeänderungen, Testergebnissen und Sicherheitsbewertungen gelangen. Das erleichtert die Vorbereitung von Audits und die Übergabe an Betriebsteams.

Wird später eine Sicherheitslücke in einer Bibliothek bekannt, hilft die Softwarestückliste dabei, betroffene Versionen der eigenen Anwendung zu ermitteln. Die dokumentierte Bewertung zeigt, welche Risiken das Team bereits untersucht hat. Daraus lässt sich ableiten, wo erneut geprüft werden muss und welche Updates Vorrang haben.

Prüftiefe und Aufwand auf das Projekt abstimmen

Den Umfang der Pipeline legen wir nach Schutzbedarf und Projektstand fest. Bei einem frühen Prototyp beginnen wir beispielsweise mit Codeprüfungen, Tests der zentralen Funktionen und einem automatisierten Build. Sobald weitere Bibliotheken hinzukommen oder die Anwendung von außen erreichbar wird, überprüfen wir, welche zusätzlichen Sicherheitsprüfungen erforderlich sind.

Für den produktiven Betrieb gewinnen ein vollständiges Komponentenverzeichnis, die Bewertung gemeldeter Schwachstellen und Tests ganzer Nutzerabläufe an Gewicht. Bei Anwendungen mit sensiblen Daten können solche Anforderungen bereits ab der ersten Entwicklungsphase verpflichtend sein.

Dabei berücksichtigen wir auch die Laufzeit der Pipeline und den Aufwand, ihre Ergebnisse zu bearbeiten. Jede verpflichtende Prüfung braucht jemanden, der auf Fehler reagiert. Welche Befunde eine Freigabe verhindern und wie Ausnahmen behandelt werden, vereinbaren wir mit dem Projektteam und dem Auftraggeber.