Zum Hauptinhalt springen

Secrets und Umgebungsvariablen

Secrets sind verschlüsselte Werte, die als Umgebungsvariablen in Workflow-Jobs eingespeist werden. Nach dem Anlegen kannst du sie nur noch schreiben: Der Wert lässt sich weder über die Oberfläche noch über die API zurücklesen. Diese Seite behandelt Geltungsbereiche, das Anlegen, die Verwendung in Workflows, Rotation und Limits.

Secrets werden im Ruhezustand mit T Cloud Public DEW KMS verschlüsselt und erst zur Laufzeit des Jobs auf dem Runner entschlüsselt.

Secrets auf Organisationsebene verwaltest du im Org-Admin unter frem.sh/<org>/_admin/secrets. Secrets auf Repository-Ebene liegen in Forgejo unter Einstellungen → Actions → Geheimnisse des jeweiligen Repositorys. Umgebungen mit ihren Secrets, Variablen, Branch-Regeln und Prüfern verwaltest du im Org-Admin unter frem.sh/<org>/_admin/environments. fremforge übergibt einem Job die Secrets seiner Umgebung, wenn der Job einem Runner zugewiesen wird, und wendet dort auch die Rangfolge an (Umgebung, dann Repository, dann Organisation).

Geltungsbereiche

GeltungsbereichWo festgelegtVerfügbar für
Organisations-SecretOrg-Admin → SecretsAlle Repositorys der Organisation
Repository-SecretRepository → Einstellungen → GeheimnisseNur dieses Repository
Eingegrenztes CI-SecretAPI /orgs/<org>/ci-secrets (Seite im Org-Admin folgt)Jobs, deren Workflow, Job, Branch oder Tag, Ereignis und Umgebung der Geltungsbereich des Secrets zulässt, und nur wenn der Job darauf verweist (siehe unten)
Umgebungs-SecretOrg-Admin → Umgebungen → <env>Jobs, die environment: <env> deklarieren, auf einem Branch oder Tag, den die Umgebung zulässt, und nur die Secrets, auf die der Job verweist (siehe unten)

Rangfolge: Ein Umgebungs-Secret hat Vorrang vor einem an ein Repository gebundenen eingegrenzten Secret, dieses vor einem organisationsweiten eingegrenzten Secret, dieses vor einem Repository-Secret und dieses vor einem Organisations-Secret. Gibt es denselben Namen in mehreren Geltungsbereichen, gewinnt der spezifischste.

Secrets anlegen

Über die Oberfläche: Wechsle zum passenden Geltungsbereich, scrolle zu Add or update a secret, gib Namen (Konvention: UPPER_SNAKE_CASE) und Wert ein und klicke auf Save secret.

Über die API:

# Org secret
curl -X PUT \
  -H "Authorization: Bearer <PAT>" \
  -H "Content-Type: application/json" \
  -d '{"name":"MY_SECRET","data":"the-value"}' \
  https://frem.sh/_app/api/v1/orgs/<org>/actions/secrets/MY_SECRET

# Repo secret
curl -X PUT \
  -H "Authorization: Bearer <PAT>" \
  -H "Content-Type: application/json" \
  -d '{"name":"MY_SECRET","data":"the-value"}' \
  https://frem.sh/api/v1/repos/<org>/<repo>/actions/secrets/MY_SECRET

Nach dem Anlegen sind Secrets nur noch schreibbar. Den Wert eines Secrets kannst du weder über die API noch über die Oberfläche zurücklesen, sondern ihn nur überschreiben oder löschen.

Secrets in Workflows verwenden

Du verweist mit ${{ secrets.NAME }} auf Secrets. Sie stehen als Umgebungsvariablen zur Verfügung.

env:
  DATABASE_URL: ${{ secrets.DATABASE_URL }}

steps:
  - name: Deploy
    env:
      API_KEY: ${{ secrets.DEPLOY_API_KEY }}
    run: ./deploy.sh

Secrets werden in Logs maskiert: Jede Log-Ausgabe, die exakt einem Secret-Wert entspricht, wird durch *** ersetzt. Die Maskierung greift nur bei exakten Treffern; base64- oder URL-kodierte oder anderweitig umgewandelte Varianten des Werts werden nicht automatisch maskiert.

Umgebungs-Secrets und Deployment-Gates

Jobs, die environment: deklarieren, erhalten nur die Secrets dieser Umgebung (dazu Repository- und Organisations-Secrets). Das ist so gewollt: Umgebungs-Secrets stellen Produktionszugangsdaten hinter den Freigabeablauf der Umgebung.

jobs:
  deploy-prod:
    runs-on: fremforge
    environment: production   # triggers approval gate if reviewers are configured
    steps:
      - name: Deploy
        env:
          DB_URL: ${{ secrets.PROD_DB_URL }}   # environment secret on 'production'
        run: ./deploy.sh

Erforderliche Prüfer legst du unter Org-Admin → Umgebungen → <env> fest. Der Job pausiert vor dem Schritt und wartet auf die Freigabe; das Secret wird erst eingespeist, wenn der Job freigegeben ist und läuft.

Umgebungsnamen werden wie in Forgejo und GitHub ohne Beachtung der Groß- und Kleinschreibung abgeglichen: environment: Production ist die Umgebung production, mit ihrer Branch-Regel, ihren Prüfern und ihren Secrets. Unterscheiden sich zwei Umgebungen nur in der Schreibweise, wird diejenige verwendet, die exakt so geschrieben ist, wie der Job sie deklariert; passt keine exakt, erhält der Job keine Umgebung und sein Deployment-Gate weist ihn ab (environment_case_ambiguous). Benenne eine der beiden um.

Ein pull_request_target-Lauf aus einem Fork erhält keine Umgebung, keine ihrer Secrets und nicht ihren OIDC-Claim, egal was der Job deklariert. Das ist strenger als bei GitHub. Siehe Reichweite von Secrets bei Fork-PRs.

Umgebungs-Secrets erreichen nur die Jobs, die auf sie verweisen

Seit dem 26.10.2026 erhält ein Job ein Umgebungs-Secret nur, wenn er darauf verweist: in seinen eigenen Schritten, in env:, with: oder if: oder im env: auf Workflow-Ebene, als ${{ secrets.NAME }} oder ${{ secrets['NAME'] }}. Ein Secret, auf das der Job nicht verweist, wird gar nicht erst an seinen Runner geschickt und liegt damit nicht auf dem Rechner, auf dem der Job läuft.

${{ toJSON(secrets) }}, ein berechneter Name wie ${{ secrets[format('{0}_KEY', 'DEPLOY')] }} und secrets: inherit beim Aufruf eines wiederverwendbaren Workflows verweisen wie bei GitHub auf alle Secrets der Umgebung.

Ein Schritt kann ein Secret nur über einen solchen Ausdruck lesen. Ein Workflow, der heute funktioniert, funktioniert also weiter: Die Änderung entfernt nur Secrets, die kein Schritt lesen konnte.

Kann fremforge die Workflow-Datei des Jobs beim Commit des Jobs nicht lesen oder den Job darin nicht finden, erhält der Job keines der Secrets der Umgebung. Der Job läuft trotzdem, mit seinem OIDC-Token und seinen Variablen.

Wer welches Secret erhalten hat: das Lese-Audit

Jeder CI-Job, der ein Umgebungs- oder eingegrenztes Secret erhält oder auf eines verweist, das ihm verweigert wird, schreibt einen Eintrag in das Audit-Log der Organisation, Aktion ci.secret.read:

FeldWert
actordie Person, die den Lauf gestartet hat (bei einem geplanten Lauf die Organisation)
repository, repository_id, workflow, jobwo er lief
run_id, run_number, run_attempt, ref, eventwelcher Lauf
environmentdie Umgebung, die der Job deklariert hat
secretsdie NAMEN, die der Job erhalten hat, jeweils mit ihrem Geltungsbereich (environment, repo oder org)
withheldNamen, auf die der Job verwiesen hat, die ihm aber nicht übergeben werden konnten, jeweils mit Grund: workflow-unreadable oder job-not-found, und bei einem eingegrenzten Secret das Feld des Geltungsbereichs, das es verweigert hat (scope-workflow, scope-job, scope-ref, scope-event, scope-environment), oder undecryptable

Der Wert eines Secrets wird nie aufgezeichnet, auch kein Hash davon. Ein Job, der auf keines der Secrets der Umgebung verweist, erhält keines und schreibt keinen Eintrag.

Der Eintrag wird geschrieben, bevor die Secrets an den Runner übergeben werden. Lässt er sich nicht speichern, erhält der Job keines davon: Kein Secret verlässt fremforge, ohne aufgezeichnet zu sein. In diesem Fall wird die Rufbereitschaft von fremforge alarmiert.

Die Einträge fragst du über die API ab:

curl -H "Authorization: Bearer $FREMFORGE_TOKEN" \
  "https://frem.sh/_app/api/v1/orgs/<org>/audit?action=ci.secret.read&since=2026-10-26"

Die Einträge sind wie jeder andere Audit-Eintrag Teil der Hash-Kette des Audit-Logs, seiner Aufbewahrung und seines SIEM-Streams.

Repository- und Organisations-Secrets, die in Forgejo gesetzt sind, sind nicht abgedeckt: Forgejo übergibt sie weiterhin an jeden Job des Repositorys, und ihr Auslesen wird nicht aufgezeichnet. Verwende stattdessen ein eingegrenztes CI-Secret und lösche die gleichnamige Kopie in Forgejo: Solange es sie gibt, erreicht sie auch die Jobs, die der Geltungsbereich abweist.

Eingegrenzte CI-Secrets

Ein eingegrenztes CI-Secret wird von fremforge verwahrt, für die ganze Organisation oder für ein einzelnes Repository, und einem Job nur übergeben, wenn jedes Feld seines Geltungsbereichs, das du gesetzt hast, passt (innerhalb eines Felds genügt einer der aufgeführten Werte):

FeldPasst aufBeispiel
workflowsden Dateinamen des Workflowsdeploy.yaml, release-*.yml
jobsdie Job-ID (den Schlüssel unter jobs:)deploy, publish-*
refsden Branch oder Tagrefs/heads/main, refs/tags/v*
eventsden Auslöser des Workflowspush, workflow_dispatch, schedule, pull_request, pull_request_target, release
environmentder Job deklariert diese Umgebung, auf einem Branch oder Tag, den die Umgebung zulässtproduction

* ist der einzige Platzhalter. Ein Feld, das du weglässt, lässt alles zu, mit einer Ausnahme: Ohne aufgeführte events erhält ein Lauf auf dem Ref eines Pull-Requests (refs/pull/...) das Secret nie. Soll Code aus einem Pull-Request ein eingegrenztes Secret erhalten, führe pull_request (oder pull_request_target) ausdrücklich auf.

Der Job muss außerdem auf das Secret verweisen (wie oben). Jede Übergabe und jede Verweigerung eines Secrets, auf das der Job verwiesen hat, wird im ci.secret.read-Eintrag des Jobs aufgezeichnet. Ein Pull-Request aus einem Fork erhält nie eines.

Welche Felder eine Sicherheitsgrenze sind. Wer in das Repository pushen darf, kann einen Branch anlegen, dessen Workflow in einer Datei namens deploy.yaml einen Job namens deploy enthält, und ihn starten. workflows und jobs begrenzen also, welche Codepfade das Secret normalerweise erhalten, schützen es aber nicht vor Personen mit Schreibzugriff. Das leisten refs (zeige damit auf geschützte Branches oder Tags) und environment (mit einer Branch-Regel und erforderlichen Prüfern). Das ist genauso wie bei GitHub.

Ein Lauf aus der Merge-Warteschlange (refs/heads/fremforge-queue/...) ist ein Build im selben Repository, den eine Maintainerin oder ein Maintainer eingereiht hat, und ein leerer refs-Geltungsbereich lässt ihn wie jeden Branch zu. Schränke refs ein, wenn ein Secret Läufe aus der Merge-Warteschlange nicht erreichen darf.

Jobs in einem wiederverwendbaren Workflow und, auf einer Forgejo-Version, die die Job-ID noch nicht mitschickt, Jobs mit eigenem name: oder einer Matrix erhalten noch keine eingegrenzten Secrets.

Ein Secret anlegen oder ersetzen (der Wert ist nur schreibbar; jede Änderung wird mitsamt dem ersetzten Geltungsbereich auditiert):

curl -X PUT -H "Authorization: Bearer $FREMFORGE_TOKEN" -H 'Content-Type: application/json' \
  -d '{"value":"...","workflows":["deploy.yaml"],"jobs":["deploy"],"refs":["refs/heads/main"],"events":["push","workflow_dispatch"]}' \
  "https://frem.sh/_app/api/v1/orgs/<org>/ci-secrets/DEPLOY_KEY?repository_id=<forgejo repository id>"

Lass repository_id für ein organisationsweites Secret weg. Ein unbekanntes Feld, ein environment, das kein Name ist, und ein leerer Listeneintrag werden abgewiesen (400) statt ignoriert, sodass ein Tippfehler ein Secret nie auf alle Jobs ausweiten kann. GET /orgs/<org>/ci-secrets listet Namen, Geltungsbereiche und den Zeitpunkt des letzten Auslesens; DELETE .../ci-secrets/<NAME>[?repository_id=] entfernt eines. Scopes: ci-secrets:read, ci-secrets:write. Limits: 500 pro Organisation, 100 pro Repository, 20 Muster pro Feld, 64 KB pro Wert.

Um scoped CI-Secrets als Code zu verwalten, nutze fremforge_ci_secret im fremforge-Provider (ab 0.9.0). Sein Wert ist ein Write-only-Argument und erreicht nie den Terraform-State.

Rotation von Secrets

  1. Lege zuerst die neuen Zugangsdaten im externen System an.
  2. Überschreibe das Secret in fremforge mit dem neuen Wert. Lösche es nicht und lege es nicht neu an.
  3. Starte einen neuen Workflow-Lauf, um zu prüfen, dass die neuen Zugangsdaten durchgängig funktionieren.
  4. Widerrufe die alten Zugangsdaten im externen System.

Das Überschreiben erhält den Audit-Verlauf und vermeidet ein Zeitfenster, in dem das Secret für Jobs fehlt, die zwischen Löschen und Neuanlegen laufen.

Limits

ElementLimit
Maximale Größe eines Secret-Werts64 KB
Organisations-Secrets1.000
Repository-Secrets100 pro Repository
Umgebungs-Secrets100 pro Umgebung
Eingegrenzte CI-Secrets500 pro Organisation, 100 pro Repository
Muster für Secret-NamenBuchstaben, Ziffern und Unterstrich; muss mit einem Buchstaben beginnen; darf nicht mit FORGEJO_ oder GITHUB_ beginnen

OIDC als Alternative zu Secrets

Für Deployments zu Cloud-Anbietern (T Cloud Public, AWS, Azure, GCP) solltest du statt langlebiger, als Secrets gespeicherter Zugangsdaten die OIDC-Token-Föderation in Betracht ziehen. OIDC stellt pro Job kurzlebige Tokens aus, die auf den jeweiligen Workflow-Lauf beschränkt sind. Nichts zu rotieren, nichts, was durchsickern kann.

OIDC ist das empfohlene Muster für jeden Job, der in eine Cloud-Infrastruktur deployt.

Querverweise