Veröffentlicht am: 9. Dezember 2025

10 Minuten Lesezeit

Mit CI-Integration Python-Pakete sichern & publizieren

Implementierung einer sicheren CI/CD-Pipeline über sechs Stufen mit der GitLab-DevSecOps-Plattform.

Die Sicherheit der Software-Lieferkette ist ein zentrales Anliegen in der Softwareentwicklung. Unternehmen müssen die Authentizität und Integrität ihrer Software-Pakete nachweisen können. Diese Anleitung zeigt die Implementierung einer sicheren CI/CD-Pipeline für Python-Pakete mittels GitLab CI, einschließlich Paket-Signierung und Attestierung mit Sigstores Cosign. Die NIS2-Richtlinie (EU) 2022/2555 verpflichtet Unternehmen der kritischen Infrastruktur und wichtige Einrichtungen zur Absicherung ihrer Software-Lieferkette. Artikel 21 fordert explizit "Sicherheit bei der Beschaffung, Entwicklung und Wartung von Netz- und Informationssystemen" sowie "Sicherheit der Lieferkette". Kryptographische Signierung von Software-Paketen erfüllt diese Anforderungen durch nachweisbare Integrität und lückenlose Rückverfolgbarkeit der Herkunft. In dieser Anleitung:

Warum Python-Pakete signieren und attestieren?

Nach den hochkarätigen Angriffen auf SolarWinds (2020) und der Log4Shell-Schwachstelle (2021) hat das BSI verschärfte Empfehlungen zur Absicherung der Software-Lieferkette herausgegeben. Die NIS2-Umsetzung in Deutschland verstärkt diese Anforderungen: Betreiber kritischer Infrastrukturen und wichtige Einrichtungen müssen die Integrität ihrer Software-Komponenten nachweisen können. Kryptographische Signierung bietet diesen Nachweis durch fälschungssichere, öffentlich verifizierbare Herkunftsbestätigung. Vier Gründe für Signierung und Attestierung der Python-Pakete:

  • Sicherheit der Lieferkette: Paket-Signierung stellt sicher, dass der Code zwischen Build und Deployment nicht manipuliert wurde und schützt vor Angriffen auf die Lieferkette. - Compliance-Anforderungen: Viele Unternehmen, insbesondere in regulierten Branchen, verlangen kryptographische Signaturen und Herkunftsnachweise für alle bereitgestellte Software. - Rückverfolgbarkeit: Attestierungen liefern einen verifizierbaren Nachweis der Build-Bedingungen, einschließlich wer das Paket unter welchen Umständen erstellt hat. - Vertrauens-Verifizierung: Nutzer des Pakets können dessen Authentizität vor der Installation kryptographisch überprüfen.

Pipeline-Übersicht

Die Gewährleistung der Code-Integrität und -Authentizität ist notwendig. Diese Pipeline erstellt nicht nur den Code, sondern erzeugt eine kryptographisch verifizierbare Dokumentation darüber, wie, wann und von wem das Paket erstellt wurde. Jede Stufe fungiert als Kontrollinstanz, die die Herkunft des Pakets prüft und dokumentiert. Sechs Stufen einer GitLab-Pipeline, die die Sicherheit und Vertrauenswürdigkeit des Pakets gewährleisten:

  • Build: Erstellt ein sauberes, standardisiertes Paket, das einfach geteilt und installiert werden kann. - Signierung: Fügt eine digitale Signatur hinzu, die beweist, dass das Paket seit der Erstellung nicht manipuliert wurde. - Verifizierung: Überprüft, dass die Signatur gültig ist und das Paket alle Sicherheitsanforderungen erfüllt. - Veröffentlichung: Lädt das verifizierte Paket in die GitLab-Paket-Registry hoch und stellt es zur Nutzung bereit. - Signaturen veröffentlichen: Macht Signaturen zur Verifizierung verfügbar. - Nutzer-Verifizierung: Simuliert, wie Endnutzer die Paket-Authentizität verifizieren können.

Vollständige Pipeline-Implementierung: Umgebung einrichten

Vor dem Paket-Build muss eine konsistente und sichere Build-Umgebung eingerichtet werden. Diese Konfiguration stellt sicher, dass jedes Paket mit denselben Werkzeugen, Einstellungen und Sicherheitskontrollen erstellt wird.

Umgebungskonfiguration

Die Pipeline benötigt spezifische Werkzeuge und Einstellungen. Primäre Konfigurationen:

  • Python 3.10 für konsistente Builds - Cosign 2.2.3 für Paket-Signierung - GitLab-Paket-Registry-Integration - Fest codierte Paket-Version für Reproduzierbarkeit Hinweis zur Versionierung: Dieses Beispiel nutzt eine fest codierte Version ("1.0.0") statt einer Ableitung aus Git-Tags oder Commits. Dieser Ansatz gewährleistet vollständige Reproduzierbarkeit und macht das Pipeline-Verhalten vorhersagbarer. In Produktivumgebungen lässt sich semantische Versionierung basierend auf Git-Tags oder eine andere Versionierungsstrategie einsetzen, die zum Release-Prozess passt. Werkzeug-Anforderungen:
  • Basis-Utilities: curl, wget - Cosign für kryptographische Signierung - Python-Packaging-Tools: build, twine, setuptools, wheel

Konfigurations-Aufschlüsselung

  PYTHON_VERSION: '3.10'
  PACKAGE_NAME: ${CI_PROJECT_NAME}
  PACKAGE_VERSION: "1.0.0"
  FULCIO_URL: 'https://fulcio.sigstore.dev'
  REKOR_URL: 'https://rekor.sigstore.dev'
  CERTIFICATE_IDENTITY: 'https://gitlab.com/${CI_PROJECT_PATH}//.gitlab-ci.yml@refs/heads/${CI_DEFAULT_BRANCH}'
  CERTIFICATE_OIDC_ISSUER: 'https://gitlab.com'
  PIP_CACHE_DIR: "$CI_PROJECT_DIR/.pip-cache"
  COSIGN_YES: "true"
  GENERIC_PACKAGE_BASE_URL: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${PACKAGE_NAME}/${PACKAGE_VERSION}"

Caching beschleunigt nachfolgende Builds:

  paths:
    - ${PIP_CACHE_DIR}

Build: Das Paket erstellen

Jede Software-Entwicklung beginnt mit der Erstellung. In dieser Pipeline transformiert die Build-Stage Quellcode in ein verteilbares Paket, bereit für verschiedene Python-Umgebungen. Der Build-Prozess erstellt zwei standardisierte Formate:

  • Ein Wheel-Paket (.whl) für schnelle, effiziente Installation - Eine Quell-Distribution (.tar.gz) mit dem vollständigen Code Reproduzierbare Builds erfüllen beispielsweise NIS2-Anforderungen zur Nachvollziehbarkeit der Software-Herstellung. Implementierung der Build-Stage:
  extends: .python-job
  stage: build
  script:
    - git init
    - git config --global init.defaultBranch main
    - git config --global user.email "[email protected]"
    - git config --global user.name "CI"
    - git add .
    - git commit -m "Initial commit"
    - export NORMALIZED_NAME=$(echo "${CI_PROJECT_NAME}" | tr '-' '_')
    - sed -i "s/name = \".*\"/name = \"${NORMALIZED_NAME}\"/" pyproject.toml
    - sed -i "s|\"Homepage\" = \".*\"|\"Homepage\" = \"https://gitlab.com/${CI_PROJECT_PATH}\"|" pyproject.toml
    - python -m build
  artifacts:
    paths:
      - dist/
      - pyproject.toml

Die Build-Stage führt folgende Schritte aus:

  1. Initialisiert ein Git-Repository (git init) und konfiguriert es mit Basis-Einstellungen 2. Normalisiert den Paketnamen durch Umwandlung von Bindestrichen in Unterstriche, was für Python-Packaging erforderlich ist 3. Aktualisiert die Paket-Metadaten in pyproject.toml entsprechend den Projekt-Einstellungen 4. Erstellt sowohl Wheel- als auch Quell-Distributions-Pakete mittels python -m build 5. Bewahrt die erstellten Pakete und Konfiguration als Artefakte für nachfolgende Stufen

Signierung: Die digitale Beglaubigung

Ist die Attestierung die Biografie des Pakets, so ist die Signierung dessen kryptographisches Siegel der Authentizität. Hier wird das Paket von einer bloßen Datei-Sammlung in ein verifiziertes, manipulationssicheres Artefakt transformiert. Die Signierung-Stage nutzt Cosign zur Anwendung einer digitalen Signatur als unzerbrechliches Siegel. Dies ist kein einfacher Stempel, sondern ein komplexer kryptographischer Handshake, der die Integrität und Herkunft des Pakets beweist. Die schlüssellose Signierung mittels OIDC eliminiert das Risiko von Credential-Leaks und erfüllt beispielsweise ISO 27001 A.12.1.2 (Verwaltung privilegierter Zugriffsrechte).

  extends: .python+cosign-job
  stage: sign
  id_tokens:
    SIGSTORE_ID_TOKEN:
      aud: sigstore
  script:
    - |
      for file in dist/*.whl dist/*.tar.gz; do
        if [ -f "$file" ]; then
          filename=$(basename "$file")
          cosign sign-blob --yes \
            --fulcio-url=${FULCIO_URL} \
            --rekor-url=${REKOR_URL} \
            --oidc-issuer $CI_SERVER_URL \
            --identity-token $SIGSTORE_ID_TOKEN \
            --output-signature "dist/${filename}.sig" \
            --output-certificate "dist/${filename}.crt" \
            "$file"
        fi
      done
  artifacts:
    paths:
      - dist/

Diese Signierung-Stage führt mehrere zentrale Operationen aus:

  1. Erhält ein OIDC-Token von GitLab zur Authentifizierung bei Sigstore-Diensten 2. Verarbeitet jedes erstellte Paket (sowohl Wheel als auch Quell-Distribution) 3. Nutzt Cosign zur Erstellung einer kryptographischen Signatur (.sig) für jedes Paket 4. Generiert ein Zertifikat (.crt), das die Authentizität der Signatur beweist 5. Speichert sowohl Signaturen als auch Zertifikate zusammen mit den Paketen als Artefakte

Verifizierung: Die Sicherheitskontrolle

Die Verifizierung ist das finale Qualitätskontroll-Gate. Dies ist nicht nur eine Prüfung, sondern eine Sicherheitsuntersuchung, bei der jeder Aspekt des Pakets überprüft wird. ISO 27001 Anhang A.14.2.8 fordert Sicherheitstests vor der Bereitstellung von Systemen. Die Verification-Stage der Pipeline erfüllt diese Anforderung durch kryptographische Überprüfung: Jedes Paket wird vor der Veröffentlichung gegen seine Signatur validiert. Scheitert die Prüfung, stoppt die Pipeline automatisch – eine erzwungene Qualitätssicherung, die manuelle Umgehung ausschließt. Die erzwungene kryptographische Prüfung vor der Veröffentlichung entspricht beispielsweise BSI-Empfehlungen zur systematischen Qualitätssicherung.

  extends: .python+cosign-job
  stage: verify
  script:
    - |
      failed=0
      for file in dist/*.whl dist/*.tar.gz; do
        if [ -f "$file" ]; then
          filename=$(basename "$file")
          if ! cosign verify-blob \
            --signature "dist/${filename}.sig" \
            --certificate "dist/${filename}.crt" \
            --certificate-identity "${CERTIFICATE_IDENTITY}" \
            --certificate-oidc-issuer "${CERTIFICATE_OIDC_ISSUER}" \
            "$file"; then
            failed=1
          fi
        fi
      done
      if [ $failed -eq 1 ]; then
        exit 1
      fi

Die Verifizierung-Stage implementiert mehrere Sicherheitsprüfungen:

  1. Untersucht jede Paketdatei im dist-Verzeichnis 2. Nutzt Cosign zur Überprüfung, dass die Signatur zum Paketinhalt passt 3. Bestätigt, dass die Zertifikats-Identität der erwarteten GitLab-Pipeline-Identität entspricht 4. Validiert, dass das Zertifikat vom vertrauenswürdigen OIDC-Provider ausgestellt wurde 5. Lässt die gesamte Pipeline fehlschlagen, wenn eine Prüfung fehlschlägt, sodass nur verifizierte Pakete fortfahren

Veröffentlichung: Die kontrollierte Freigabe

Bei der Veröffentlichung werden die verifizierten Pakete über die GitLab-Paket-Registry verfügbar gemacht. Dies ist eine sorgfältig orchestrierte Freigabe, die sicherstellt, dass nur verifizierte, authentifizierte Pakete ihr Ziel erreichen.

  extends: .python-job
  stage: publish
  script:
    - |
      cat << EOF > ~/.pypirc
      [distutils]
      index-servers = gitlab
      [gitlab]
      repository = ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi
      username = gitlab-ci-token
      password = ${CI_JOB_TOKEN}
      EOF
      TWINE_PASSWORD=${CI_JOB_TOKEN} TWINE_USERNAME=gitlab-ci-token \
        twine upload --repository-url ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi \
        dist/*.whl dist/*.tar.gz

Die Veröffentlichung-Stage übernimmt mehrere wichtige Aufgaben:

  1. Erstellt eine .pypirc-Konfigurationsdatei mit GitLab-Paket-Registry-Zugangsdaten 2. Nutzt das GitLab-CI-Job-Token für sichere Authentifizierung 3. Lädt sowohl Wheel- als auch Quell-Distributions-Pakete in die GitLab-PyPI-Registry hoch 4. Stellt die Pakete zur Installation via pip bereit

Signaturen veröffentlichen: Verifizierung ermöglichen

Nach Veröffentlichung der Pakete müssen deren Signaturen und Zertifikate zur Verifizierung verfügbar gemacht werden. Diese werden in der generischen Paket-Registry von GitLab gespeichert und sind damit einfach zugänglich für Nutzer, die die Paket-Authentizität verifizieren möchten.

  extends: .python+cosign-job
  stage: publish_signatures
  script:
    - |
      for file in dist/*.whl dist/*.tar.gz; do
        if [ -f "$file" ]; then
          filename=$(basename "$file")
          curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" \
               --fail \
               --upload-file "dist/${filename}.sig" \
               "${GENERIC_PACKAGE_BASE_URL}/${filename}.sig"

          curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" \
               --fail \
               --upload-file "dist/${filename}.crt" \
               "${GENERIC_PACKAGE_BASE_URL}/${filename}.crt"
        fi
      done

Die Signaturen-Veröffentlichung-Stage führt folgende Schlüsseloperationen aus:

  1. Verarbeitet jedes erstellte Paket zur Identifikation der zugehörigen Signaturdateien 2. Nutzt die GitLab-API zum Upload der Signatur-Datei (.sig) in die generische Paket-Registry 3. Lädt die zugehörige Zertifikat-Datei (.crt) hoch 4. Stellt diese Verifizierungs-Artefakte für nachgelagerte Paket-Nutzer bereit 5. Verwendet dieselbe Version und denselben Paketnamen zur Aufrechterhaltung der Verbindung zwischen Paketen und Signaturen

Nutzer-Verifizierung: Die Anwendererfahrung testen

Die finale Stufe simuliert, wie Endnutzer die Authentizität des Pakets verifizieren werden. Diese Stufe dient sowohl als finale Prüfung als auch als praktisches Beispiel des Verifizierungsprozesses.

  extends: .python+cosign-job
  stage: consumer_verification
  script:
    - |
      git init
      git config --global init.defaultBranch main
      mkdir -p pkg signatures

      pip download --index-url "https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/pypi/simple" \
          "${NORMALIZED_NAME}==${PACKAGE_VERSION}" --no-deps -d ./pkg

      pip download --no-binary :all: \
          --index-url "https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/pypi/simple" \
          "${NORMALIZED_NAME}==${PACKAGE_VERSION}" --no-deps -d ./pkg

      failed=0
      for file in pkg/*.whl pkg/*.tar.gz; do
        if [ -f "$file" ]; then
          filename=$(basename "$file")
          sig_url="${GENERIC_PACKAGE_BASE_URL}/${filename}.sig"
          cert_url="${GENERIC_PACKAGE_BASE_URL}/${filename}.crt"

          curl --fail --silent --show-error \
               --header "JOB-TOKEN: ${CI_JOB_TOKEN}" \
               --output "signatures/${filename}.sig" \
               "$sig_url"

          curl --fail --silent --show-error \
               --header "JOB-TOKEN: ${CI_JOB_TOKEN}" \
               --output "signatures/${filename}.crt" \
               "$cert_url"

          if ! cosign verify-blob \
            --signature "signatures/${filename}.sig" \
            --certificate "signatures/${filename}.crt" \
            --certificate-identity "${CERTIFICATE_IDENTITY}" \
            --certificate-oidc-issuer "${CERTIFICATE_OIDC_ISSUER}" \
            "$file"; then
            failed=1
          fi
        fi
      done

      if [ $failed -eq 1 ]; then
        exit 1
      fi

Diese Nutzer-Verifizierung-Stage simuliert die Endnutzer-Erfahrung durch:

  1. Erstellung einer sauberen Umgebung zum Test der Paket-Installation 2. Download der veröffentlichten Pakete aus der GitLab-PyPI-Registry 3. Abruf der zugehörigen Signaturen und Zertifikate aus der generischen Paket-Registry 4. Durchführung derselben Verifizierungsschritte, die Endnutzer ausführen würden 5. Sicherstellung, dass der gesamte Prozess aus Nutzer-Perspektive funktioniert 6. Fehlschlag der Pipeline bei Fehlern in einem Verifizierungsschritt für frühzeitige Problemerkennung

Zusammenfassung

Das BSI IT-Grundschutz-Kompendium empfiehlt in Baustein CON.8 die Sicherstellung der Software-Integrität durch geeignete Maßnahmen. Kryptographische Signierung mit öffentlich nachprüfbaren Transparenzprotokollen (Sigstore Rekor) entspricht dieser Empfehlung: Jedes Paket erhält eine unveränderbare digitale Signatur, die auch Endnutzer vor der Installation verifizieren können. Dies schafft Vertrauen in die Software-Lieferkette vom Build bis zur Bereitstellung. Diese umfassende Pipeline bietet eine sichere und zuverlässige Methode zum Erstellen, Signieren und Veröffentlichen von Python-Paketen in die GitLab-Paket-Registry. Durch Befolgung dieser Praktiken und Implementierung der empfohlenen Sicherheitsmaßnahmen lässt sich sicherstellen, dass Pakete angemessen verifiziert und sicher an Nutzer verteilt werden. Die Pipeline kombiniert moderne Sicherheitspraktiken mit effizienter Automatisierung zur Schaffung einer robusten Software-Lieferkette. Mittels Sigstores Cosign für Signierung und Attestierung sowie GitLabs integrierten Sicherheitsfunktionen lassen sich Nutzern vertrauenswürdige, kryptographisch verifizierte Pakete bereitstellen.

Starten Sie noch heute Ihre Sicherheitsreise mit einer kostenlosen Testversion von GitLab Ultimate.

Weitere Informationen

Feedback erwünscht

Dieser Blogbeitrag hat gefallen oder es gibt Fragen oder Feedback? Ein neues Diskussionsthema im GitLab-Community-Forum erstellen und Eindrücke austauschen.

Feedback teilen

Mehr als 50 % der Fortune-100-Unternehmen vertrauen GitLab

Stelle jetzt bessere Software schneller bereit

Erlebe, was dein Team mit der intelligenten

DevSecOps-Plattform erreichen kann.