mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-20 10:45:00 +02:00
Adds OpenID Connect workload identity to Gitea Actions: jobs declaring `id-token: write` can request short-lived tokens to authenticate to cloud providers and other services without stored secrets. Tokens follow GitHub's format, so clients like `@actions/core` and existing trust policies carry over. - Issuer at `/api/actions/oidc` with discovery and JWKS, signed with a dedicated RS256 key - GitHub's claims, including `workflow_ref`, `job_workflow_ref` and the immutable `repo:owner@id/repo@id:ref:...` subject - `id-token: write` or `write-all` grant it, default permissions and fork pull requests never get it, owner and repository maximum permissions can disable it - Reusable workflows record the exact ref they were loaded from, and dispatch rejects refs that only resolve through a same-named tag - Generated signing keys are written atomically, and generated ES384 and ES512 OAuth2 keys use their own curves instead of P-256 - `github.ref_type` is `branch` for pull request runs, like on GitHub Fixes https://github.com/go-gitea/gitea/issues/26383 Fixes https://github.com/go-gitea/gitea/issues/33681 Docs: https://gitea.com/gitea/docs/pulls/526 Assisted-by: ChatGPT:gpt-5.6-sol, Claude Code:claude-opus-5-5 Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: silverwind <me@silverwind.io>
67 lines
2.2 KiB
Go
67 lines
2.2 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package actions
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
|
|
"gitea.dev/modules/auth/httpauth"
|
|
"gitea.dev/modules/log"
|
|
"gitea.dev/modules/util"
|
|
actions_service "gitea.dev/services/actions"
|
|
"gitea.dev/services/context"
|
|
)
|
|
|
|
func oidcConfiguration(resp http.ResponseWriter, req *http.Request) {
|
|
issuer := actions_service.OIDCIssuer()
|
|
context.NewBaseContext(resp, req).JSON(http.StatusOK, map[string]any{
|
|
"issuer": issuer,
|
|
"jwks_uri": issuer + "/jwks",
|
|
"response_types_supported": []string{"id_token"},
|
|
"subject_types_supported": []string{"public"},
|
|
"id_token_signing_alg_values_supported": []string{actions_service.OIDCSigningAlgorithm},
|
|
"claims_supported": actions_service.OIDCClaimsSupported(),
|
|
"scopes_supported": []string{"openid"},
|
|
})
|
|
}
|
|
|
|
func oidcKeys(resp http.ResponseWriter, req *http.Request) {
|
|
ctx := context.NewBaseContext(resp, req)
|
|
key, err := actions_service.OIDCSigningKey()
|
|
if err != nil {
|
|
log.Error("Actions OIDC signing key: %v", err)
|
|
ctx.HTTPError(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
jwk, err := key.ToJWK()
|
|
if err != nil {
|
|
log.Error("Actions OIDC JWK: %v", err)
|
|
ctx.HTTPError(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
jwk["use"] = "sig"
|
|
ctx.JSON(http.StatusOK, map[string]any{"keys": []map[string]string{jwk}})
|
|
}
|
|
|
|
func oidcToken(resp http.ResponseWriter, req *http.Request) {
|
|
ctx := context.NewBaseContext(resp, req)
|
|
token := ""
|
|
if auth, ok := httpauth.ParseAuthorizationHeader(req.Header.Get("Authorization")); ok && auth.BearerToken != nil {
|
|
token = auth.BearerToken.Token
|
|
}
|
|
idToken, err := actions_service.CreateOIDCToken(ctx, token, ctx.FormString("audience"))
|
|
if errors.Is(err, util.ErrPermissionDenied) {
|
|
ctx.Resp.Header().Set("WWW-Authenticate", "Bearer")
|
|
ctx.HTTPError(http.StatusUnauthorized)
|
|
return
|
|
} else if err != nil {
|
|
log.Error("Actions OIDC token: %v", err)
|
|
ctx.HTTPError(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
ctx.Resp.Header().Set("Cache-Control", "no-store")
|
|
ctx.JSON(http.StatusOK, map[string]string{"value": idToken})
|
|
}
|