Skip to content

Commit

Permalink
Add artifacts v4 jwt to job message and accept it
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristopherHX committed Jan 21, 2024
1 parent 1df06e3 commit 33eaca4
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 6 deletions.
34 changes: 28 additions & 6 deletions routers/api/actions/artifacts.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import (
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
web_types "code.gitea.io/gitea/modules/web/types"
actions_service "code.gitea.io/gitea/services/actions"
)

const artifactRouteBase = "/_apis/pipelines/workflows/{run_id}/artifacts"
Expand Down Expand Up @@ -137,12 +138,33 @@ func ArtifactContexter() func(next http.Handler) http.Handler {
return
}

authToken := strings.TrimPrefix(authHeader, "Bearer ")
task, err := actions.GetRunningTaskByToken(req.Context(), authToken)
if err != nil {
log.Error("Error runner api getting task: %v", err)
ctx.Error(http.StatusInternalServerError, "Error runner api getting task")
return
// New act_runner uses jwt to authenticate
tID, err := actions_service.ParseAuthorizationToken(req)

var task *actions.ActionTask
if err == nil {

task, err = actions.GetTaskByID(req.Context(), tID)
if err != nil {
log.Error("Error runner api getting task by ID: %v", err)
ctx.Error(http.StatusInternalServerError, "Error runner api getting task by ID")
return
}
if task.Status != actions.StatusRunning {
log.Error("Error runner api getting task: task is not running")
ctx.Error(http.StatusInternalServerError, "Error runner api getting task: task is not running")
return
}
} else {
// Old act_runner uses GITEA_TOKEN to authenticate
authToken := strings.TrimPrefix(authHeader, "Bearer ")

task, err = actions.GetRunningTaskByToken(req.Context(), authToken)
if err != nil {
log.Error("Error runner api getting task: %v", err)
ctx.Error(http.StatusInternalServerError, "Error runner api getting task")
return
}
}

if err := task.LoadJob(req.Context()); err != nil {
Expand Down
6 changes: 6 additions & 0 deletions routers/api/actions/runner/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ func generateTaskContext(t *actions_model.ActionTask) *structpb.Struct {

refName := git.RefName(ref)

giteaRuntimeToken, err := actions.CreateAuthorizationToken(t.ID, t.Job.RunID, t.JobID)
if err != nil {
log.Error("actions.CreateAuthorizationToken failed: %v", err)
}

taskContext, err := structpb.NewStruct(map[string]any{
// standard contexts, see https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
"action": "", // string, The name of the action currently running, or the id of a step. GitHub removes special characters, and uses the name __run when the current step runs a script without an id. If you use the same action more than once in the same job, the name will include a suffix with the sequence number with underscore before it. For example, the first script you run will have the name __run, and the second script will be named __run_2. Similarly, the second invocation of actions/checkout will be actionscheckout2.
Expand Down Expand Up @@ -190,6 +195,7 @@ func generateTaskContext(t *actions_model.ActionTask) *structpb.Struct {

// additional contexts
"gitea_default_actions_url": setting.Actions.DefaultActionsURL.URL(),
"gitea_runtime_token": giteaRuntimeToken,
})
if err != nil {
log.Error("structpb.NewStruct failed: %v", err)
Expand Down
77 changes: 77 additions & 0 deletions services/actions/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package actions

import (
"fmt"
"net/http"
"strings"
"time"

"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"

"github.com/golang-jwt/jwt/v5"
)

type actionsClaims struct {
jwt.RegisteredClaims
Scp string `json:"scp"`
TaskID int64
RunID int64
JobID int64
}

func CreateAuthorizationToken(taskID, runID, jobID int64) (string, error) {
now := time.Now()

claims := actionsClaims{
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(now.Add(24 * time.Hour)),
NotBefore: jwt.NewNumericDate(now),
},
Scp: fmt.Sprintf("Actions.Results:%d:%d", runID, jobID),
TaskID: taskID,
RunID: runID,
JobID: jobID,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

tokenString, err := token.SignedString([]byte(setting.SecretKey))
if err != nil {
return "", err
}

return tokenString, nil
}

func ParseAuthorizationToken(req *http.Request) (int64, error) {
h := req.Header.Get("Authorization")
if h == "" {
return 0, nil
}

parts := strings.SplitN(h, " ", 2)
if len(parts) != 2 {
log.Error("split token failed: %s", h)
return 0, fmt.Errorf("split token failed")
}

token, err := jwt.ParseWithClaims(parts[1], &actionsClaims{}, func(t *jwt.Token) (any, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
}
return []byte(setting.SecretKey), nil
})
if err != nil {
return 0, err
}

c, ok := token.Claims.(*actionsClaims)
if !token.Valid || !ok {
return 0, fmt.Errorf("invalid token claim")
}

return c.TaskID, nil
}

0 comments on commit 33eaca4

Please sign in to comment.