mirror of
https://github.com/go-gitea/gitea.git
synced 2024-12-26 07:06:13 +01:00
Backport #32946 by wxiaoguang Fix #23703 When Gitea starts, it reads GITEA_RUNNER_REGISTRATION_TOKEN or GITEA_RUNNER_REGISTRATION_TOKEN_FILE to add registration token. Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
parent
0c581106d2
commit
90bd08ceef
@ -10,6 +10,7 @@ import (
|
|||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
@ -51,7 +52,7 @@ func GetRunnerToken(ctx context.Context, token string) (*ActionRunnerToken, erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if !has {
|
} else if !has {
|
||||||
return nil, fmt.Errorf("runner token %q: %w", token, util.ErrNotExist)
|
return nil, fmt.Errorf(`runner token "%s...": %w`, base.TruncateString(token, 3), util.ErrNotExist)
|
||||||
}
|
}
|
||||||
return &runnerToken, nil
|
return &runnerToken, nil
|
||||||
}
|
}
|
||||||
@ -68,19 +69,15 @@ func UpdateRunnerToken(ctx context.Context, r *ActionRunnerToken, cols ...string
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRunnerToken creates a new active runner token and invalidate all old tokens
|
// NewRunnerTokenWithValue creates a new active runner token and invalidate all old tokens
|
||||||
// ownerID will be ignored and treated as 0 if repoID is non-zero.
|
// ownerID will be ignored and treated as 0 if repoID is non-zero.
|
||||||
func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
|
func NewRunnerTokenWithValue(ctx context.Context, ownerID, repoID int64, token string) (*ActionRunnerToken, error) {
|
||||||
if ownerID != 0 && repoID != 0 {
|
if ownerID != 0 && repoID != 0 {
|
||||||
// It's trying to create a runner token that belongs to a repository, but OwnerID has been set accidentally.
|
// It's trying to create a runner token that belongs to a repository, but OwnerID has been set accidentally.
|
||||||
// Remove OwnerID to avoid confusion; it's not worth returning an error here.
|
// Remove OwnerID to avoid confusion; it's not worth returning an error here.
|
||||||
ownerID = 0
|
ownerID = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := util.CryptoRandomString(40)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
runnerToken := &ActionRunnerToken{
|
runnerToken := &ActionRunnerToken{
|
||||||
OwnerID: ownerID,
|
OwnerID: ownerID,
|
||||||
RepoID: repoID,
|
RepoID: repoID,
|
||||||
@ -95,11 +92,19 @@ func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerTo
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = db.GetEngine(ctx).Insert(runnerToken)
|
_, err := db.GetEngine(ctx).Insert(runnerToken)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
|
||||||
|
token, err := util.CryptoRandomString(40)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewRunnerTokenWithValue(ctx, ownerID, repoID, token)
|
||||||
|
}
|
||||||
|
|
||||||
// GetLatestRunnerToken returns the latest runner token
|
// GetLatestRunnerToken returns the latest runner token
|
||||||
func GetLatestRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
|
func GetLatestRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
|
||||||
if ownerID != 0 && repoID != 0 {
|
if ownerID != 0 && repoID != 0 {
|
||||||
|
@ -3722,6 +3722,7 @@ runners.status.active = Active
|
|||||||
runners.status.offline = Offline
|
runners.status.offline = Offline
|
||||||
runners.version = Version
|
runners.version = Version
|
||||||
runners.reset_registration_token = Reset registration token
|
runners.reset_registration_token = Reset registration token
|
||||||
|
runners.reset_registration_token_confirm = Would you like to invalidate the current token and generate a new one?
|
||||||
runners.reset_registration_token_success = Runner registration token reset successfully
|
runners.reset_registration_token_success = Runner registration token reset successfully
|
||||||
|
|
||||||
runs.all_workflows = All Workflows
|
runs.all_workflows = All Workflows
|
||||||
|
@ -171,7 +171,7 @@ func InitWebInstalled(ctx context.Context) {
|
|||||||
auth.Init()
|
auth.Init()
|
||||||
mustInit(svg.Init)
|
mustInit(svg.Init)
|
||||||
|
|
||||||
actions_service.Init()
|
mustInitCtx(ctx, actions_service.Init)
|
||||||
|
|
||||||
mustInit(repo_service.InitLicenseClassifier)
|
mustInit(repo_service.InitLicenseClassifier)
|
||||||
|
|
||||||
|
@ -136,9 +136,8 @@ func RunnerResetRegistrationToken(ctx *context.Context, ownerID, repoID int64, r
|
|||||||
ctx.ServerError("ResetRunnerRegistrationToken", err)
|
ctx.ServerError("ResetRunnerRegistrationToken", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Flash.Success(ctx.Tr("actions.runners.reset_registration_token_success"))
|
ctx.Flash.Success(ctx.Tr("actions.runners.reset_registration_token_success"))
|
||||||
ctx.Redirect(redirectTo)
|
ctx.JSONRedirect(redirectTo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunnerDeletePost response for deleting a runner
|
// RunnerDeletePost response for deleting a runner
|
||||||
|
@ -463,7 +463,7 @@ func registerRoutes(m *web.Router) {
|
|||||||
m.Combo("/{runnerid}").Get(repo_setting.RunnersEdit).
|
m.Combo("/{runnerid}").Get(repo_setting.RunnersEdit).
|
||||||
Post(web.Bind(forms.EditRunnerForm{}), repo_setting.RunnersEditPost)
|
Post(web.Bind(forms.EditRunnerForm{}), repo_setting.RunnersEditPost)
|
||||||
m.Post("/{runnerid}/delete", repo_setting.RunnerDeletePost)
|
m.Post("/{runnerid}/delete", repo_setting.RunnerDeletePost)
|
||||||
m.Get("/reset_registration_token", repo_setting.ResetRunnerRegistrationToken)
|
m.Post("/reset_registration_token", repo_setting.ResetRunnerRegistrationToken)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,23 +4,68 @@
|
|||||||
package actions
|
package actions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"code.gitea.io/gitea/modules/graceful"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/queue"
|
"code.gitea.io/gitea/modules/queue"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
notify_service "code.gitea.io/gitea/services/notify"
|
notify_service "code.gitea.io/gitea/services/notify"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Init() {
|
func initGlobalRunnerToken(ctx context.Context) error {
|
||||||
|
// use the same env name as the runner, for consistency
|
||||||
|
token := os.Getenv("GITEA_RUNNER_REGISTRATION_TOKEN")
|
||||||
|
tokenFile := os.Getenv("GITEA_RUNNER_REGISTRATION_TOKEN_FILE")
|
||||||
|
if token != "" && tokenFile != "" {
|
||||||
|
return errors.New("both GITEA_RUNNER_REGISTRATION_TOKEN and GITEA_RUNNER_REGISTRATION_TOKEN_FILE are set, only one can be used")
|
||||||
|
}
|
||||||
|
if tokenFile != "" {
|
||||||
|
file, err := os.ReadFile(tokenFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to read GITEA_RUNNER_REGISTRATION_TOKEN_FILE: %w", err)
|
||||||
|
}
|
||||||
|
token = strings.TrimSpace(string(file))
|
||||||
|
}
|
||||||
|
if token == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(token) < 32 {
|
||||||
|
return errors.New("GITEA_RUNNER_REGISTRATION_TOKEN must be at least 32 random characters")
|
||||||
|
}
|
||||||
|
|
||||||
|
existing, err := actions_model.GetRunnerToken(ctx, token)
|
||||||
|
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||||
|
return fmt.Errorf("unable to check existing token: %w", err)
|
||||||
|
}
|
||||||
|
if existing != nil {
|
||||||
|
if !existing.IsActive {
|
||||||
|
log.Warn("The token defined by GITEA_RUNNER_REGISTRATION_TOKEN is already invalidated, please use the latest one from web UI")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, err = actions_model.NewRunnerTokenWithValue(ctx, 0, 0, token)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(ctx context.Context) error {
|
||||||
if !setting.Actions.Enabled {
|
if !setting.Actions.Enabled {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
jobEmitterQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "actions_ready_job", jobEmitterQueueHandler)
|
jobEmitterQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "actions_ready_job", jobEmitterQueueHandler)
|
||||||
if jobEmitterQueue == nil {
|
if jobEmitterQueue == nil {
|
||||||
log.Fatal("Unable to create actions_ready_job queue")
|
return errors.New("unable to create actions_ready_job queue")
|
||||||
}
|
}
|
||||||
go graceful.GetManager().RunWithCancel(jobEmitterQueue)
|
go graceful.GetManager().RunWithCancel(jobEmitterQueue)
|
||||||
|
|
||||||
notify_service.RegisterNotifier(NewNotifier())
|
notify_service.RegisterNotifier(NewNotifier())
|
||||||
|
return initGlobalRunnerToken(ctx)
|
||||||
}
|
}
|
||||||
|
80
services/actions/init_test.go
Normal file
80
services/actions/init_test.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package actions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
unittest.MainTest(m, &unittest.TestOptions{
|
||||||
|
FixtureFiles: []string{"action_runner_token.yml"},
|
||||||
|
})
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInitToken(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
t.Run("NoToken", func(t *testing.T) {
|
||||||
|
_, _ = db.Exec(db.DefaultContext, "DELETE FROM action_runner_token")
|
||||||
|
t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN", "")
|
||||||
|
t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN_FILE", "")
|
||||||
|
err := initGlobalRunnerToken(db.DefaultContext)
|
||||||
|
require.NoError(t, err)
|
||||||
|
notEmpty, err := db.IsTableNotEmpty(&actions_model.ActionRunnerToken{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.False(t, notEmpty)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("EnvToken", func(t *testing.T) {
|
||||||
|
tokenValue, _ := util.CryptoRandomString(32)
|
||||||
|
t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN", tokenValue)
|
||||||
|
t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN_FILE", "")
|
||||||
|
err := initGlobalRunnerToken(db.DefaultContext)
|
||||||
|
require.NoError(t, err)
|
||||||
|
token := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunnerToken{Token: tokenValue})
|
||||||
|
assert.True(t, token.IsActive)
|
||||||
|
|
||||||
|
// init with the same token again, should not create a new token
|
||||||
|
err = initGlobalRunnerToken(db.DefaultContext)
|
||||||
|
require.NoError(t, err)
|
||||||
|
token2 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunnerToken{Token: tokenValue})
|
||||||
|
assert.Equal(t, token.ID, token2.ID)
|
||||||
|
assert.True(t, token.IsActive)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("EnvFileToken", func(t *testing.T) {
|
||||||
|
tokenValue, _ := util.CryptoRandomString(32)
|
||||||
|
f := t.TempDir() + "/token"
|
||||||
|
_ = os.WriteFile(f, []byte(tokenValue), 0o644)
|
||||||
|
t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN", "")
|
||||||
|
t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN_FILE", f)
|
||||||
|
err := initGlobalRunnerToken(db.DefaultContext)
|
||||||
|
require.NoError(t, err)
|
||||||
|
token := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunnerToken{Token: tokenValue})
|
||||||
|
assert.True(t, token.IsActive)
|
||||||
|
|
||||||
|
// if the env token is invalidated by another new token, then it shouldn't be active anymore
|
||||||
|
_, err = actions_model.NewRunnerToken(db.DefaultContext, 0, 0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
token = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunnerToken{Token: tokenValue})
|
||||||
|
assert.False(t, token.IsActive)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("InvalidToken", func(t *testing.T) {
|
||||||
|
t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN", "abc")
|
||||||
|
err := initGlobalRunnerToken(db.DefaultContext)
|
||||||
|
assert.ErrorContains(t, err, "must be at least")
|
||||||
|
})
|
||||||
|
}
|
@ -3,7 +3,7 @@
|
|||||||
<h4 class="ui top attached header">
|
<h4 class="ui top attached header">
|
||||||
{{ctx.Locale.Tr "actions.runners.runner_manage_panel"}} ({{ctx.Locale.Tr "admin.total" .Total}})
|
{{ctx.Locale.Tr "actions.runners.runner_manage_panel"}} ({{ctx.Locale.Tr "admin.total" .Total}})
|
||||||
<div class="ui right">
|
<div class="ui right">
|
||||||
<div class="ui top right pointing dropdown">
|
<div class="ui top right pointing dropdown jump">
|
||||||
<button class="ui primary tiny button">
|
<button class="ui primary tiny button">
|
||||||
{{ctx.Locale.Tr "actions.runners.new"}}
|
{{ctx.Locale.Tr "actions.runners.new"}}
|
||||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||||
@ -17,14 +17,18 @@
|
|||||||
Registration Token
|
Registration Token
|
||||||
</div>
|
</div>
|
||||||
<div class="ui input">
|
<div class="ui input">
|
||||||
<input type="text" value="{{.RegistrationToken}}">
|
<input type="text" value="{{.RegistrationToken}}" readonly>
|
||||||
<button class="ui basic label button" aria-label="{{ctx.Locale.Tr "copy"}}" data-clipboard-text="{{.RegistrationToken}}">
|
<button class="ui basic label button" aria-label="{{ctx.Locale.Tr "copy"}}" data-clipboard-text="{{.RegistrationToken}}">
|
||||||
{{svg "octicon-copy" 14}}
|
{{svg "octicon-copy" 14}}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<a href="{{$.Link}}/reset_registration_token">{{ctx.Locale.Tr "actions.runners.reset_registration_token"}}</a>
|
<a class="link-action" data-url="{{$.Link}}/reset_registration_token"
|
||||||
|
data-modal-confirm="{{ctx.Locale.Tr "actions.runners.reset_registration_token_confirm"}}"
|
||||||
|
>
|
||||||
|
{{ctx.Locale.Tr "actions.runners.reset_registration_token"}}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user