固定 workflow job key 身份契约

This commit is contained in:
2026-09-20 17:59:59 +00:00
parent 3c1fca1832
commit 3f336310af
6 changed files with 82 additions and 25 deletions
+29 -7
View File
@@ -2,16 +2,19 @@
package taskidentity
import (
"bytes"
"crypto/sha256"
"errors"
"fmt"
"regexp"
"strings"
"gitea.dev/actionslib/pkg/model"
runnerv1 "gitea.dev/actionslib/runner/v1"
)
var safeSegment = regexp.MustCompile(`^[A-Za-z0-9._-]+$`)
var safeTaskKey = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_-]*$`)
// Identity is the trusted identity context extracted from a fetched task.
type Identity struct {
@@ -28,14 +31,14 @@ func FromTask(task *runnerv1.Task, trustDomain string) (Identity, error) {
}
repository := strings.TrimSpace(task.Context.GetFields()["repository"].GetStringValue())
taskName := strings.TrimSpace(task.Context.GetFields()["job"].GetStringValue())
taskName, err := workflowTaskKey(task.WorkflowPayload)
if err != nil {
return Identity{}, err
}
parts := strings.Split(repository, "/")
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return Identity{}, fmt.Errorf("invalid task repository %q", repository)
}
if taskName == "" {
return Identity{}, errors.New("task job is required")
}
trustDomain = strings.TrimSpace(trustDomain)
if trustDomain == "" || strings.ContainsAny(trustDomain, "/ ") {
return Identity{}, fmt.Errorf("invalid trust domain %q", trustDomain)
@@ -44,7 +47,7 @@ func FromTask(task *runnerv1.Task, trustDomain string) (Identity, error) {
path := strings.Join([]string{
sanitize(parts[0]),
sanitize(parts[1]),
sanitize(taskName),
taskName,
}, "/")
return Identity{
Repository: repository,
@@ -53,8 +56,27 @@ func FromTask(task *runnerv1.Task, trustDomain string) (Identity, error) {
}, nil
}
// sanitize intentionally matches the bootstrap Python implementation so
// existing Zot and OpenBao policies keep their current identity names.
func workflowTaskKey(payload []byte) (string, error) {
workflow, err := model.ReadWorkflow(bytes.NewReader(payload))
if err != nil {
return "", fmt.Errorf("parse task workflow: %w", err)
}
jobIDs := workflow.GetJobIDs()
if len(jobIDs) != 1 {
return "", fmt.Errorf("task workflow must contain exactly one job, got %d", len(jobIDs))
}
if !safeTaskKey.MatchString(jobIDs[0]) {
return "", fmt.Errorf("task job key %q must match %s", jobIDs[0], safeTaskKey)
}
return jobIDs[0], nil
}
// BackoffTaskSegment deterministically converts a legacy display name into a
// collision-resistant path segment. Canonical task identities do not use it.
func BackoffTaskSegment(value string) string {
return sanitize(value)
}
func sanitize(value string) string {
if safeSegment.MatchString(value) {
return value