mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-11 07:56:20 +00:00
enhance(actions): set ref_protected in context (#38852)
## Problem The github.ref_protected Actions context value was hard-coded to false, even when Gitea has a matching protected-branch or protected-tag rule. That prevents policy-driven deployment workflows from relying on Gitea as the source of truth. A deployment runner or external identity provider may require a protected ref before releasing credentials. The workaround is an exact-ref allowlist outside Gitea, which duplicates repository protection policy and can drift when rules change. ## Solution Resolve configured protection rules for branch and tag refs. Non-branch/tag refs remain false; lookup failures are logged and conservatively return false. This changes the Actions context only; it does not add Actions OIDC issuance. --------- Co-authored-by: Giteabot <teabot@gitea.io>
This commit is contained in:
committed by
GitHub
parent
a8fe401613
commit
52d0e18dac
@@ -0,0 +1,36 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
module_git "gitea.dev/modules/git"
|
||||
)
|
||||
|
||||
// IsRefProtected checks whether a branch or tag ref is protected.
|
||||
func IsRefProtected(ctx context.Context, repoID int64, ref module_git.RefName) (bool, error) {
|
||||
if ref.IsBranch() {
|
||||
return IsBranchProtected(ctx, repoID, ref.ShortName())
|
||||
}
|
||||
if !ref.IsTag() {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
protectedTags, err := GetProtectedTags(ctx, repoID)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("get protected tags: %w", err)
|
||||
}
|
||||
for _, protectedTag := range protectedTags {
|
||||
if err := protectedTag.EnsureCompiledPattern(); err != nil {
|
||||
return false, fmt.Errorf("compile protected tag pattern %q: %w", protectedTag.NamePattern, err)
|
||||
}
|
||||
if protectedTag.matchString(ref.ShortName()) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"gitea.dev/actionslib/pkg/model"
|
||||
actions_model "gitea.dev/models/actions"
|
||||
"gitea.dev/models/db"
|
||||
git_model "gitea.dev/models/git"
|
||||
actions_module "gitea.dev/modules/actions"
|
||||
"gitea.dev/modules/actions/jobparser"
|
||||
"gitea.dev/modules/container"
|
||||
@@ -53,6 +54,11 @@ func GenerateGiteaContext(ctx context.Context, run *actions_model.ActionRun, att
|
||||
}
|
||||
|
||||
refName := git.RefName(ref)
|
||||
refProtected, err := git_model.IsRefProtected(ctx, run.RepoID, refName)
|
||||
if err != nil {
|
||||
log.Error("GenerateGiteaContext: check protection for ref %q: %v", refName, err)
|
||||
refProtected = false
|
||||
}
|
||||
|
||||
gitContext := GiteaContext{
|
||||
// standard contexts, see https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
|
||||
@@ -73,7 +79,7 @@ func GenerateGiteaContext(ctx context.Context, run *actions_model.ActionRun, att
|
||||
"job": "", // string, The job_id of the current job.
|
||||
"ref": ref, // string, The fully-formed ref of the branch or tag that triggered the workflow run. For workflows triggered by push, this is the branch or tag ref that was pushed. For workflows triggered by pull_request, this is the pull request merge branch. For workflows triggered by release, this is the release tag created. For other triggers, this is the branch or tag ref that triggered the workflow run. This is only set if a branch or tag is available for the event type. The ref given is fully-formed, meaning that for branches the format is refs/heads/<branch_name>, for pull requests it is refs/pull/<pr_number>/merge, and for tags it is refs/tags/<tag_name>. For example, refs/heads/feature-branch-1.
|
||||
"ref_name": refName.ShortName(), // string, The short ref name of the branch or tag that triggered the workflow run. This value matches the branch or tag name shown on GitHub. For example, feature-branch-1.
|
||||
"ref_protected": false, // boolean, true if branch protections are configured for the ref that triggered the workflow run.
|
||||
"ref_protected": refProtected, // boolean, true if protection rules are configured for the ref that triggered the workflow run.
|
||||
"ref_type": string(refName.RefType()), // string, The type of ref that triggered the workflow run. Valid values are branch or tag.
|
||||
"path": "", // string, Path on the runner to the file that sets system PATH variables from workflow commands. This file is unique to the current step and is a different file for each step in a job. For more information, see "Workflow commands for GitHub Actions."
|
||||
"repository": run.Repo.OwnerName + "/" + run.Repo.Name, // string, The owner and repository name. For example, Codertocat/Hello-World.
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
act_model "gitea.dev/actionslib/pkg/model"
|
||||
actions_model "gitea.dev/models/actions"
|
||||
"gitea.dev/models/db"
|
||||
git_model "gitea.dev/models/git"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unittest"
|
||||
user_model "gitea.dev/models/user"
|
||||
@@ -355,6 +356,47 @@ func TestGenerateGiteaContextPullRequestTarget(t *testing.T) {
|
||||
assert.Equal(t, "main", giteaCtx["ref_name"])
|
||||
}
|
||||
|
||||
func TestGenerateGiteaContextRefProtected(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
||||
require.NoError(t, git_model.UpdateProtectBranch(t.Context(), repo, &git_model.ProtectedBranch{
|
||||
RepoID: repo.ID,
|
||||
RuleName: "master",
|
||||
}, git_model.WhitelistOptions{}))
|
||||
require.NoError(t, git_model.InsertProtectedTag(t.Context(), &git_model.ProtectedTag{
|
||||
RepoID: repo.ID,
|
||||
NamePattern: "v*",
|
||||
}))
|
||||
|
||||
gitCtx := GenerateGiteaContext(t.Context(), &actions_model.ActionRun{
|
||||
RepoID: repo.ID,
|
||||
Repo: repo,
|
||||
TriggerUser: &user_model.User{Name: "test-user"},
|
||||
Ref: "refs/heads/master",
|
||||
}, nil, nil)
|
||||
|
||||
assert.Equal(t, true, gitCtx["ref_protected"])
|
||||
|
||||
tagCtx := GenerateGiteaContext(t.Context(), &actions_model.ActionRun{
|
||||
RepoID: repo.ID,
|
||||
Repo: repo,
|
||||
TriggerUser: &user_model.User{Name: "test-user"},
|
||||
Ref: "refs/tags/v1.0.0",
|
||||
}, nil, nil)
|
||||
|
||||
assert.Equal(t, true, tagCtx["ref_protected"])
|
||||
|
||||
unprotectedTagCtx := GenerateGiteaContext(t.Context(), &actions_model.ActionRun{
|
||||
RepoID: repo.ID,
|
||||
Repo: repo,
|
||||
TriggerUser: &user_model.User{Name: "test-user"},
|
||||
Ref: "refs/tags/other",
|
||||
}, nil, nil)
|
||||
|
||||
assert.Equal(t, false, unprotectedTagCtx["ref_protected"])
|
||||
}
|
||||
|
||||
// TestGenerateGiteaContext_NilAttempt verifies that, with no explicit attempt,
|
||||
// use GetLatestAttempt to load the latest attempt and resolve attempt-related context variables.
|
||||
func TestGenerateGiteaContext_NilAttempt(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user