feat: add OIDC workload identity federation support #1232

Merged
mfenniak merged 1 commit from mpminardi/runner:mpminardi/workload-identity into main 2026-01-07 01:39:34 +00:00
Contributor

Add support for OIDC workload identity federation in a shape similar to
what GitHub supports.

Add a new workflow and job-level setting named "allow-generating-id-tokens"
that enables ID token generation at the workflow or specific job level.

Export ACTIONS_ID_TOKEN_REQUEST_TOKEN and ACTIONS_ID_TOKEN_REQUEST_URL in
the runner environment. These are populated from the task context keys
forgejo_actions_id_token_request_token and
forgejo_actions_id_token_request_url respectively which are only set when
the aforementioned "allow-generating-id-tokens" setting is enabled.

Required by https://codeberg.org/forgejo/forgejo/pulls/10481

Signed-off-by: Mario Minardi [email protected]

  • features
    • PR: feat: add OIDC workload identity federation support
Add support for OIDC workload identity federation in a shape similar to what GitHub supports. Add a new workflow and job-level setting named "allow-generating-id-tokens" that enables ID token generation at the workflow or specific job level. Export ACTIONS_ID_TOKEN_REQUEST_TOKEN and ACTIONS_ID_TOKEN_REQUEST_URL in the runner environment. These are populated from the task context keys forgejo_actions_id_token_request_token and forgejo_actions_id_token_request_url respectively which are only set when the aforementioned "allow-generating-id-tokens" setting is enabled. Required by https://codeberg.org/forgejo/forgejo/pulls/10481 Signed-off-by: Mario Minardi <[email protected]> <!--start release-notes-assistant--> <!--URL:https://code.forgejo.org/forgejo/runner--> - features - [PR](https://code.forgejo.org/forgejo/runner/pulls/1232): <!--number 1232 --><!--line 0 --><!--description ZmVhdDogYWRkIE9JREMgd29ya2xvYWQgaWRlbnRpdHkgZmVkZXJhdGlvbiBzdXBwb3J0-->feat: add OIDC workload identity federation support<!--description--> <!--end release-notes-assistant-->
mpminardi force-pushed mpminardi/workload-identity from 2f6e9dd7e2
Some checks failed
issue-labels / release-notes (pull_request_target) Successful in 5s
checks / build and test (pull_request) Has been cancelled
checks / runner exec tests (pull_request) Has been cancelled
checks / integration tests (docker-latest) (pull_request) Has been cancelled
checks / integration tests (docker-stable) (pull_request) Has been cancelled
checks / runner integration tests (pull_request) Has been cancelled
checks / validate mocks (pull_request) Has been cancelled
checks / validate pre-commit-hooks file (pull_request) Has been cancelled
to 7cf87484a6
Some checks failed
cascade / forgejo (pull_request_target) Has been skipped
cascade / debug (pull_request_target) Has been skipped
cascade / end-to-end (pull_request_target) Has been skipped
issue-labels / release-notes (pull_request_target) Successful in 6s
checks / build and test (pull_request) Has been cancelled
checks / runner exec tests (pull_request) Has been cancelled
checks / integration tests (docker-latest) (pull_request) Has been cancelled
checks / integration tests (docker-stable) (pull_request) Has been cancelled
checks / runner integration tests (pull_request) Has been cancelled
checks / validate mocks (pull_request) Has been cancelled
checks / validate pre-commit-hooks file (pull_request) Has been cancelled
2025-12-20 21:47:03 +00:00
Compare
mpminardi force-pushed mpminardi/workload-identity from 7cf87484a6
Some checks failed
cascade / forgejo (pull_request_target) Has been skipped
cascade / debug (pull_request_target) Has been skipped
cascade / end-to-end (pull_request_target) Has been skipped
issue-labels / release-notes (pull_request_target) Successful in 6s
checks / build and test (pull_request) Has been cancelled
checks / runner exec tests (pull_request) Has been cancelled
checks / integration tests (docker-latest) (pull_request) Has been cancelled
checks / integration tests (docker-stable) (pull_request) Has been cancelled
checks / runner integration tests (pull_request) Has been cancelled
checks / validate mocks (pull_request) Has been cancelled
checks / validate pre-commit-hooks file (pull_request) Has been cancelled
to 8c434e940b
Some checks are pending
checks / build and test (pull_request) Blocked by required conditions
checks / runner exec tests (pull_request) Blocked by required conditions
checks / integration tests (docker-latest) (pull_request) Blocked by required conditions
checks / integration tests (docker-stable) (pull_request) Blocked by required conditions
checks / runner integration tests (pull_request) Blocked by required conditions
checks / validate mocks (pull_request) Blocked by required conditions
checks / validate pre-commit-hooks file (pull_request) Blocked by required conditions
cascade / forgejo (pull_request_target) Has been skipped
cascade / debug (pull_request_target) Has been skipped
cascade / end-to-end (pull_request_target) Has been skipped
issue-labels / release-notes (pull_request_target) Successful in 4s
2025-12-26 17:57:32 +00:00
Compare
Owner

@mpminardi I would happily permit this PR to run CI so that you can see test results, but I'm unable to due to the conflicting files. If you'd like the CI run, please rebase/merge main and let me know, and I will permit it.

@mpminardi I would happily permit this PR to run CI so that you can see test results, but I'm unable to due to the conflicting files. If you'd like the CI run, please rebase/merge main and let me know, and I will permit it.
mpminardi force-pushed mpminardi/workload-identity from 8c434e940b
Some checks are pending
checks / build and test (pull_request) Blocked by required conditions
checks / runner exec tests (pull_request) Blocked by required conditions
checks / integration tests (docker-latest) (pull_request) Blocked by required conditions
checks / integration tests (docker-stable) (pull_request) Blocked by required conditions
checks / runner integration tests (pull_request) Blocked by required conditions
checks / validate mocks (pull_request) Blocked by required conditions
checks / validate pre-commit-hooks file (pull_request) Blocked by required conditions
cascade / forgejo (pull_request_target) Has been skipped
cascade / debug (pull_request_target) Has been skipped
cascade / end-to-end (pull_request_target) Has been skipped
issue-labels / release-notes (pull_request_target) Successful in 4s
to a535809b35
All checks were successful
cascade / forgejo (pull_request_target) Has been skipped
cascade / debug (pull_request_target) Has been skipped
cascade / end-to-end (pull_request_target) Has been skipped
issue-labels / release-notes (pull_request_target) Successful in 8s
checks / build and test (pull_request) Successful in 1m18s
checks / validate mocks (pull_request) Successful in 48s
checks / validate pre-commit-hooks file (pull_request) Successful in 55s
checks / runner exec tests (pull_request) Successful in 44s
checks / runner integration tests (pull_request) Successful in 8m25s
checks / integration tests (docker-latest) (pull_request) Successful in 13m41s
checks / integration tests (docker-stable) (pull_request) Successful in 16m43s
2025-12-26 20:39:33 +00:00
Compare
Author
Contributor

@mfenniak thank you! Have rebased the PR on top of main

@mfenniak thank you! Have rebased the PR on top of main
@ -53,2 +54,4 @@
"boolean": {}
},
"workflow-allow-generating-id-tokens": {
"description": "Allow all jobs in the workflow to generate an OIDC compliant ID token.\n\n[Documentation](https://forgejo.org/docs/TODO)",
Author
Contributor

TODO: need to actually have this and the below point to docs before merging (or omit the docs link / add later).

TODO: need to actually have this and the below point to docs before merging (or omit the docs link / add later).
mpminardi marked this conversation as resolved
mpminardi force-pushed mpminardi/workload-identity from a535809b35
All checks were successful
cascade / forgejo (pull_request_target) Has been skipped
cascade / debug (pull_request_target) Has been skipped
cascade / end-to-end (pull_request_target) Has been skipped
issue-labels / release-notes (pull_request_target) Successful in 8s
checks / build and test (pull_request) Successful in 1m18s
checks / validate mocks (pull_request) Successful in 48s
checks / validate pre-commit-hooks file (pull_request) Successful in 55s
checks / runner exec tests (pull_request) Successful in 44s
checks / runner integration tests (pull_request) Successful in 8m25s
checks / integration tests (docker-latest) (pull_request) Successful in 13m41s
checks / integration tests (docker-stable) (pull_request) Successful in 16m43s
to 1fc525ff60
All checks were successful
cascade / forgejo (pull_request_target) Has been skipped
cascade / debug (pull_request_target) Has been skipped
cascade / end-to-end (pull_request_target) Has been skipped
issue-labels / release-notes (pull_request_target) Successful in 4s
checks / validate mocks (pull_request) Successful in 34s
checks / validate pre-commit-hooks file (pull_request) Successful in 36s
checks / build and test (pull_request) Successful in 45s
checks / runner exec tests (pull_request) Successful in 28s
/ example-docker-compose (pull_request) Successful in 2m4s
Integration tests for the release process / release-simulation (pull_request) Successful in 4m43s
checks / runner integration tests (pull_request) Successful in 5m46s
/ example-lxc-systemd (pull_request) Successful in 6m54s
checks / integration tests (docker-latest) (pull_request) Successful in 9m48s
checks / integration tests (docker-stable) (pull_request) Successful in 11m14s
2025-12-31 20:50:47 +00:00
Compare
mpminardi force-pushed mpminardi/workload-identity from 1fc525ff60
All checks were successful
cascade / forgejo (pull_request_target) Has been skipped
cascade / debug (pull_request_target) Has been skipped
cascade / end-to-end (pull_request_target) Has been skipped
issue-labels / release-notes (pull_request_target) Successful in 4s
checks / validate mocks (pull_request) Successful in 34s
checks / validate pre-commit-hooks file (pull_request) Successful in 36s
checks / build and test (pull_request) Successful in 45s
checks / runner exec tests (pull_request) Successful in 28s
/ example-docker-compose (pull_request) Successful in 2m4s
Integration tests for the release process / release-simulation (pull_request) Successful in 4m43s
checks / runner integration tests (pull_request) Successful in 5m46s
/ example-lxc-systemd (pull_request) Successful in 6m54s
checks / integration tests (docker-latest) (pull_request) Successful in 9m48s
checks / integration tests (docker-stable) (pull_request) Successful in 11m14s
to d35bdda98d
All checks were successful
cascade / forgejo (pull_request_target) Has been skipped
cascade / debug (pull_request_target) Has been skipped
cascade / end-to-end (pull_request_target) Has been skipped
checks / validate mocks (pull_request) Successful in 27s
checks / build and test (pull_request) Successful in 41s
checks / validate pre-commit-hooks file (pull_request) Successful in 33s
checks / runner exec tests (pull_request) Successful in 28s
checks / runner integration tests (pull_request) Successful in 5m11s
checks / integration tests (docker-latest) (pull_request) Successful in 9m18s
issue-labels / release-notes (pull_request_target) Successful in 4s
checks / integration tests (docker-stable) (pull_request) Successful in 11m7s
2026-01-03 23:19:53 +00:00
Compare
mpminardi changed title from WIP: feat: add OIDC workload identity federation support to feat: add OIDC workload identity federation support 2026-01-03 23:20:11 +00:00
mfenniak left a comment
Owner

This looks really good, with some minor comments for us to resolve.

I think the workflow entry name can be better. I'd propose: enable-openid-connect: true.

  • enable-* is consistent with enable-email-notifications
  • "allow" is quite passive, it doesn't sound like it's turning something on; enable says "I'm changing a setting"
  • openid-connect (or maybe enable-oidc 🤷) is a little more specific about what is being turned on, rather than "tokens" which in this context is a term pretty disconnected from what you're doing

I'm not firm on enable-openid-connect and I'm happy to discuss... although we shouldn't burn too many cycles on it either... but I quite dislike allow-generating-id-tokens (said with respect).

This looks really good, with some minor comments for us to resolve. I think the workflow entry name can be better. I'd propose: `enable-openid-connect: true`. - `enable-*` is consistent with `enable-email-notifications` - "allow" is quite passive, it doesn't sound like it's turning something on; enable says "I'm changing a setting" - `openid-connect` (or maybe `enable-oidc` 🤷) is a little more specific about what is being turned on, rather than "tokens" which in this context is a term pretty disconnected from what you're doing I'm not firm on `enable-openid-connect` and I'm happy to discuss... although we shouldn't burn too many cycles on it either... but I quite dislike `allow-generating-id-tokens` (said with respect).
@ -31,1 +30,3 @@
RawConcurrency *RawConcurrency `yaml:"concurrency"` // For Gitea
RawNotifications yaml.Node `yaml:"enable-email-notifications"`
RawConcurrency *RawConcurrency `yaml:"concurrency"` // For Gitea
RawAllowGeneratingIDTokens yaml.Node `yaml:"allow-generating-id-tokens"`
Owner

From the usage, it seems like these RawAllowGeneratingIDTokens struct members could be defined as *bool rather than yaml.Node, eliminating the need for the yaml type check functions. yaml.Node should only be needed when the input could be multiple different types of yaml node and the runner needs to standardize it -- eg. runs-on can be a scalar, an array, or a mapping, and then is standardized for code to understand.

From the usage, it seems like these `RawAllowGeneratingIDTokens` struct members could be defined as `*bool` rather than `yaml.Node`, eliminating the need for the yaml type check functions. `yaml.Node` should only be needed when the input could be multiple different types of yaml node and the runner needs to standardize it -- eg. `runs-on` can be a scalar, an array, or a mapping, and then is standardized for code to understand.
mfenniak marked this conversation as resolved
@ -313,2 +313,4 @@
runEnvs["ACTIONS_RUNTIME_TOKEN"] = runtimeToken
runEnvs["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = taskContext["forgejo_actions_id_token_request_token"].GetStringValue()
runEnvs["ACTIONS_ID_TOKEN_REQUEST_URL"] = taskContext["forgejo_actions_id_token_request_url"].GetStringValue()
Owner

This should also be covered with an automated test, but I am assuming that you're expecting to cover it in the end-to-end test. Please confirm.

This should also be covered with an automated test, but I am assuming that you're expecting to cover it in the end-to-end test. Please confirm.
Author
Contributor

Yup this should be covered by the end-to-end test! Can look at adding some simple regression tests here as well though.

Yup this should be covered by the end-to-end test! Can look at adding some simple regression tests here as well though.
mfenniak marked this conversation as resolved
@ -62,2 +62,4 @@
masker.add(v)
}
if v := task.Context.Fields["forgejo_actions_id_token_request_token"].GetStringValue(); v != "" {
masker.add(v)
Owner

Please cover with an automated test -- it would be security sensitive for this to regress.

It raises the question of whether this belongs in ${{ secrets... }} and not in the environment. What do you think?

Please cover with an automated test -- it would be security sensitive for this to regress. It raises the question of whether this belongs in `${{ secrets... }}` and not in the environment. What do you think?
Author
Contributor

Whoops good catch! Will add some testing around this.

It raises the question of whether this belongs in ${{ secrets... }} and not in the environment. What do you think?

That is also a fair point! The thinking here was to have maximum compatibility with the patterns GitHub uses for this functionality which exports these into the runner environment and documents doing:

curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=api://AzureADTokenExchange"

We could instead put these values into secrets (and vars potentially for the URL) and drop the extra bits added to the ACTIONS_ID_TOKEN_REQUEST_URL under the hood and have something like:

curl -H "Authorization: bearer ${{secrets.ACTIONS_ID_TOKEN_REQUEST_TOKEN}}" "${{ vars.ACTIONS_ID_TOKEN_REQUEST_URL}}?audience=api://AzureADTokenExchange"

Downside here being that it will force more of a change for users migrating from GitHub / require changes in reusable third-party actions that end up being ported over to Forgejo vs having things "just work".

I don't have super strong preferences around this though! If we do want to diverge from the GitHub patterns for this I don't think it will be a huge amount of friction / it could be explained in docs pretty easily, but it will add more friction than keeping the patterns here as-is.

Whoops good catch! Will add some testing around this. > It raises the question of whether this belongs in ${{ secrets... }} and not in the environment. What do you think? That is also a fair point! The thinking here was to have maximum compatibility with the patterns GitHub uses for this functionality which exports these into the runner environment and documents doing: ```shell curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=api://AzureADTokenExchange" ``` We could instead put these values into secrets (and vars potentially for the URL) and drop the extra bits added to the `ACTIONS_ID_TOKEN_REQUEST_URL` under the hood and have something like: ```shell curl -H "Authorization: bearer ${{secrets.ACTIONS_ID_TOKEN_REQUEST_TOKEN}}" "${{ vars.ACTIONS_ID_TOKEN_REQUEST_URL}}?audience=api://AzureADTokenExchange" ``` Downside here being that it will force more of a change for users migrating from GitHub / require changes in reusable third-party actions that end up being ported over to Forgejo vs having things "just work". I don't have super strong preferences around this though! If we do want to diverge from the GitHub patterns for this I don't think it will be a _huge_ amount of friction / it could be explained in docs pretty easily, but it will add more friction than keeping the patterns here as-is.
Owner

OK, let's leave it as-is (w/ test automation around the masking). I think compatibility is a good reason, and other than the enforced masking of secrets, there's no significant value. 👍

OK, let's leave it as-is (w/ test automation around the masking). I think compatibility is a good reason, and other than the enforced masking of secrets, there's no significant value. 👍
mfenniak marked this conversation as resolved
Author
Contributor

@mfenniak wrote in #1232 (comment):

This looks really good, with some minor comments for us to resolve.

I think the workflow entry name can be better. I'd propose: enable-openid-connect: true.

* `enable-*` is consistent with `enable-email-notifications`

* "allow" is quite passive, it doesn't sound like it's turning something on; enable says "I'm changing a setting"

* `openid-connect` (or maybe `enable-oidc` :shrug:) is a little more specific about what is being turned on, rather than "tokens" which in this context is a term pretty disconnected from what you're doing

I'm not firm on enable-openid-connect and I'm happy to discuss... although we shouldn't burn too many cycles on it either... but I quite dislike allow-generating-id-tokens (said with respect).

enable-openid-connect sounds good to me!

@mfenniak wrote in https://code.forgejo.org/forgejo/runner/pulls/1232#issuecomment-71543: > This looks really good, with some minor comments for us to resolve. > > I think the workflow entry name can be better. I'd propose: `enable-openid-connect: true`. > > * `enable-*` is consistent with `enable-email-notifications` > > * "allow" is quite passive, it doesn't sound like it's turning something on; enable says "I'm changing a setting" > > * `openid-connect` (or maybe `enable-oidc` :shrug:) is a little more specific about what is being turned on, rather than "tokens" which in this context is a term pretty disconnected from what you're doing > > > I'm not firm on `enable-openid-connect` and I'm happy to discuss... although we shouldn't burn too many cycles on it either... but I quite dislike `allow-generating-id-tokens` (said with respect). `enable-openid-connect` sounds good to me!
mpminardi force-pushed mpminardi/workload-identity from d35bdda98d
All checks were successful
cascade / forgejo (pull_request_target) Has been skipped
cascade / debug (pull_request_target) Has been skipped
cascade / end-to-end (pull_request_target) Has been skipped
checks / validate mocks (pull_request) Successful in 27s
checks / build and test (pull_request) Successful in 41s
checks / validate pre-commit-hooks file (pull_request) Successful in 33s
checks / runner exec tests (pull_request) Successful in 28s
checks / runner integration tests (pull_request) Successful in 5m11s
checks / integration tests (docker-latest) (pull_request) Successful in 9m18s
issue-labels / release-notes (pull_request_target) Successful in 4s
checks / integration tests (docker-stable) (pull_request) Successful in 11m7s
to bf63f59d4e
Some checks failed
cascade / forgejo (pull_request_target) Has been skipped
cascade / debug (pull_request_target) Has been skipped
cascade / end-to-end (pull_request_target) Has been skipped
issue-labels / release-notes (pull_request_target) Successful in 4s
checks / validate pre-commit-hooks file (pull_request) Successful in 49s
checks / validate mocks (pull_request) Successful in 52s
checks / integration tests (docker-latest) (pull_request) Has been cancelled
checks / integration tests (docker-stable) (pull_request) Has been cancelled
checks / build and test (pull_request) Has been cancelled
checks / runner exec tests (pull_request) Has been cancelled
checks / runner integration tests (pull_request) Has been cancelled
2026-01-04 22:32:02 +00:00
Compare
mpminardi force-pushed mpminardi/workload-identity from bf63f59d4e
Some checks failed
cascade / forgejo (pull_request_target) Has been skipped
cascade / debug (pull_request_target) Has been skipped
cascade / end-to-end (pull_request_target) Has been skipped
issue-labels / release-notes (pull_request_target) Successful in 4s
checks / validate pre-commit-hooks file (pull_request) Successful in 49s
checks / validate mocks (pull_request) Successful in 52s
checks / integration tests (docker-latest) (pull_request) Has been cancelled
checks / integration tests (docker-stable) (pull_request) Has been cancelled
checks / build and test (pull_request) Has been cancelled
checks / runner exec tests (pull_request) Has been cancelled
checks / runner integration tests (pull_request) Has been cancelled
to 351e4eef4a
All checks were successful
checks / validate mocks (pull_request) Successful in 32s
checks / validate pre-commit-hooks file (pull_request) Successful in 47s
checks / build and test (pull_request) Successful in 3m4s
checks / runner exec tests (pull_request) Successful in 30s
checks / runner integration tests (pull_request) Successful in 6m13s
checks / integration tests (docker-latest) (pull_request) Successful in 10m22s
checks / integration tests (docker-stable) (pull_request) Successful in 12m17s
cascade / forgejo (pull_request_target) Has been skipped
cascade / debug (pull_request_target) Has been skipped
cascade / end-to-end (pull_request_target) Successful in 6s
issue-labels / release-notes (pull_request_target) Successful in 7s
2026-01-04 22:34:27 +00:00
Compare
mfenniak approved these changes 2026-01-07 01:39:30 +00:00
Owner

Great! I'll tag a release of the runner in a day or two so that the forgejo side's jobparser upgrade can be done, unblocking the forgejo PR. Thank you for the contribution.

Great! I'll tag a release of the runner in a day or two so that the forgejo side's `jobparser` upgrade can be done, unblocking the forgejo PR. Thank you for the contribution.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
forgejo/runner!1232
No description provided.