Zum Hauptinhalt springen
GitHub DevSecOps Teil 11: Scheduled Pipelines für den Produktionscode
  1. Blogs/

GitHub DevSecOps Teil 11: Scheduled Pipelines für den Produktionscode

Autor
Romano Roth
Ich bin überzeugt: Der nächste Wettbewerbsvorteil ist nicht AI selbst, sondern die Organisation drumherum. Als Chief AI Officer bei Zühlke arbeite ich mit C-Level-Führungskräften daran, Unternehmen zu bauen, die wahrnehmen, entscheiden und sich kontinuierlich anpassen. Seit über 20 Jahren mache ich diese Überzeugung zur Praxis.
Frag die KI über diesen Artikel

Über zehn Sessions haben wir Security-Checks in eine GitHub-Actions-Pipeline verdrahtet, die auf jedem Commit und jedem Pull Request feuert. Das deckt den Code ab, an dem wir aktiv arbeiten. Es deckt nicht den Code ab, der bereits in Produktion läuft, während Researcher laufend neue CVEs in den Libraries finden, die er nutzt. In Teil 11 der GitHub DevSecOps Serie bauen Patrick Steger und ich einen Scheduled Workflow, der den Production-Branch erneut scannt — und stossen dabei auf eine GitHub-Limitierung, die man von Anfang an kennen sollte.

Warum Commit-Trigger nicht reichen
#

Jeder Check, den wir gebaut haben — SAST, SCA, Container Scanning, DAST, alles — läuft, wenn jemand Code pusht. Dieses Modell unterstellt, dass sich das Risiko nur ändert, wenn sich der Code ändert. Tut es aber nicht. Eine Library, von der du vor zwei Monaten abhängig wurdest, kann damals sauber gewesen sein und heute eine kritische CVE haben. Im Repo hat sich nichts bewegt, und trotzdem ist deine Anwendung jetzt verwundbar.

Die Lösung: die Security-Tests regelmässig gegen den Branch laufen lassen, der den Production-Code hält. Neue CVEs in alten Dependencies tauchen so innerhalb eines Tages auf, statt erst beim nächsten Hotfix.

Die richtigen Jobs für den Schedule
#

Ein Scheduled Run ist kein Wiederholungslauf der gesamten Pipeline. Die Jobs, die sich lohnen, sind die, deren Ergebnis sich auch ohne Codeänderung ändern kann — alles, was auf Software Composition Analysis basiert. Konkret in unserer Pipeline:

  • Build bleibt drin. SCA braucht den aufgelösten Dependency-Graph, den der Build produziert.
  • SCA / Dependency Scanning bleibt drin. Genau dafür ist der Schedule da.
  • Container Image Scan bleibt drin. Das Base-Image kann inzwischen neue OS-CVEs haben.
  • License Compliance fällt raus. Lizenzen ändern sich nicht über Nacht.
  • DAST fällt raus. Wir deployen nicht extra in eine Testumgebung, um sie zu scannen.

Drei Jobs, auf einem Cron, gegen Produktion. Das ist die Form.

Die GitHub-Limitierung: nur Default-Branch
#

In GitHub Actions fügst du einem Workflow-File on: schedule: mit einer Cron-Expression hinzu, und der Workflow läuft auf diesem Schedule. Klingt einfach. Dann liest du das Kleingedruckte: der Schedule läuft immer auf dem Default-Branch, auf dem letzten Commit dieses Branches.

Das ist eine echte Einschränkung. Wenn main dein Entwicklungs-Trunk ist und du aus einem release-Branch auslieferst, fasst der Schedule den Release-Branch nicht an. Die Workarounds — über GitHub Hooks, Refs oder externe Trigger — gibt es, aber sie verlangen echten Custom Code. Patrick und ich halten es im Video bewusst einfach und akzeptieren die Konsequenz: wer Scheduled Scans gegen Produktion will, muss seinen Production-Branch zum Default-Branch machen. Das hat Folgewirkungen (Pull Requests landen dort als Default, Merges gehen dort hin) — also bewusst entscheiden.

Den Scheduled Workflow bauen
#

Statt das bestehende Main-Pipeline-File mit Conditionals vollzustopfen, legen wir ein zweites Workflow-File an. Wir kopieren main-pipeline.yml, fügen es als schedule.yml ein und benennen den Workflow in “Schedule Main CI/CD Pipeline” um.

Der Trigger wechselt von on: push (und Konsorten) zu on: schedule mit einer Cron-Expression. Für die Demo setzen wir alle sechs Minuten, damit wir Läufe direkt hintereinander sehen; im Echtbetrieb wählst du täglich oder wöchentlich. Dann werfen wir die Jobs raus, die wir auf dem Schedule nicht brauchen: License Compliance und DAST fliegen. Build, SCA und Container Image Scan bleiben.

Commit, Push, zurück in den Actions-Tab. Der nächste normale Pipeline-Lauf wird abgebrochen. Ein paar Minuten später startet der Scheduled Run, und im Lauf sind nur die Jobs, die wir behalten haben. Sechs Minuten später läuft er wieder. Der Mechanismus funktioniert.

Zwei Workflows, zwei Verantwortungen
#

Der Zwei-File-Ansatz hat einen echten Vorteil gegenüber einem grossen Workflow mit if:-Conditions: jedes File hat eine Aufgabe. main-pipeline.yml ist das, was auf Entwickler-Aktivität läuft. schedule.yml ist das, was auf der Uhr läuft. Neuer Job? Du entscheidest, in welches File er gehört. Diese Klarheit ist die kleine Duplikation wert.

Der Nachteil: die zwei Files können auseinanderlaufen. Wenn du der Hauptpipeline einen neuen SCA-Scanner hinzufügst, denk daran, ihn auch im Scheduled File zu ergänzen. Ein kurzer Kommentar oben in jedem File mit Verweis auf das andere hilft enorm.

Sind wir jetzt sicher?
#

Mit allem, was wir in der Serie gebaut haben — SAST, Secret Detection, SCA, Container Scanning, DAST, Pull-Request-Gating und jetzt ein Scheduled Re-Scan der Produktion — bist du in etwa so weit, wie dich das GitHub-Bordmaterial ohne grösseren Custom-Aufwand bringt. Du wirst weiter Vulnerabilities finden. Du wirst weiter triagieren müssen. Aber du findest sie auf einer Uhr statt durch Zufall.

In der nächsten und letzten Session legen Patrick und ich unsere Empfehlungen für ein Team zusammen, das DevSecOps auf GitHub fährt.

Key Takeaways
#

  1. Commit-Trigger fangen keine CVEs in unverändertem Code. Neue Vulnerabilities in deinen Dependencies tauchen täglich auf. Ein Schedule ist der einzige Weg, sie ohne Redeploy zu sehen.

  2. Schedule nur die SCA-artigen Jobs. Build, SCA und Container Scanning lohnen sich auf einem Cron. License Compliance und DAST nicht.

  3. GitHub-Schedules laufen nur auf dem Default-Branch. Plane das ein. Ist dein Production-Branch nicht der Default-Branch, sehen Schedules ihn ohne ernsthafte Workarounds nicht.

  4. Zweites Workflow-File statt Conditionals. main-pipeline.yml für Commits, schedule.yml für den Cron. Jedes File hat einen Zweck; neue Jobs landen am richtigen Ort.

  5. Das Cron-Intervall ist eine echte Entscheidung. Alle sechs Minuten ist eine Demo-Kadenz. Wähle täglich oder wöchentlich, je nachdem, wie schnell dein Team auf ein neues Finding reagieren kann.

  6. GitHubs Defaults schieben dich Richtung main als Production. Das hat Folgen für Pull Requests, Merges und menschliche Fehler. Triff die Wahl bewusst, nicht aus Versehen.