mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-06-22 01:34:25 +02:00
Compare commits
2 Commits
abec931d98
...
f17b6b9fc3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f17b6b9fc3 | ||
|
|
c7c4bd600a |
@@ -26,6 +26,7 @@ import (
|
|||||||
|
|
||||||
"dario.cat/mergo"
|
"dario.cat/mergo"
|
||||||
"github.com/Masterminds/semver"
|
"github.com/Masterminds/semver"
|
||||||
|
cerrdefs "github.com/containerd/errdefs"
|
||||||
"github.com/docker/cli/cli/compose/loader"
|
"github.com/docker/cli/cli/compose/loader"
|
||||||
"github.com/docker/cli/cli/connhelper"
|
"github.com/docker/cli/cli/connhelper"
|
||||||
"github.com/go-git/go-billy/v5/helper/polyfill"
|
"github.com/go-git/go-billy/v5/helper/polyfill"
|
||||||
@@ -152,6 +153,8 @@ func (cr *containerReference) Copy(destPath string, files ...*FileEntry) common.
|
|||||||
func (cr *containerReference) CopyDir(destPath, srcPath string, useGitIgnore bool) common.Executor {
|
func (cr *containerReference) CopyDir(destPath, srcPath string, useGitIgnore bool) common.Executor {
|
||||||
return common.NewPipelineExecutor(
|
return common.NewPipelineExecutor(
|
||||||
common.NewInfoExecutor("docker cp src=%s dst=%s", srcPath, destPath),
|
common.NewInfoExecutor("docker cp src=%s dst=%s", srcPath, destPath),
|
||||||
|
cr.connect(),
|
||||||
|
cr.find(),
|
||||||
cr.copyDir(destPath, srcPath, useGitIgnore),
|
cr.copyDir(destPath, srcPath, useGitIgnore),
|
||||||
func(ctx context.Context) error {
|
func(ctx context.Context) error {
|
||||||
// If this fails, then folders have wrong permissions on non root container
|
// If this fails, then folders have wrong permissions on non root container
|
||||||
@@ -167,6 +170,16 @@ func (cr *containerReference) GetContainerArchive(ctx context.Context, srcPath s
|
|||||||
if common.Dryrun(ctx) {
|
if common.Dryrun(ctx) {
|
||||||
return nil, errors.New("DRYRUN is not supported in GetContainerArchive")
|
return nil, errors.New("DRYRUN is not supported in GetContainerArchive")
|
||||||
}
|
}
|
||||||
|
// Direct entry point (no pipeline) — revalidate cr.id ourselves.
|
||||||
|
if err := cr.connect()(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := cr.find()(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if cr.id == "" {
|
||||||
|
return nil, cr.missingContainerError("get archive %s", srcPath)
|
||||||
|
}
|
||||||
result, err := cr.cli.CopyFromContainer(ctx, cr.id, client.CopyFromContainerOptions{SourcePath: srcPath})
|
result, err := cr.cli.CopyFromContainer(ctx, cr.id, client.CopyFromContainerOptions{SourcePath: srcPath})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -314,10 +327,22 @@ func (cr *containerReference) Close() common.Executor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// missingContainerError is the shared "container X does not exist" error
|
||||||
|
// used by ops that need a live cr.id.
|
||||||
|
func (cr *containerReference) missingContainerError(format string, args ...any) error {
|
||||||
|
return fmt.Errorf("container %q does not exist; cannot "+format, append([]any{cr.input.Name}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
func (cr *containerReference) find() common.Executor {
|
func (cr *containerReference) find() common.Executor {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
if cr.id != "" {
|
if cr.id != "" {
|
||||||
return nil
|
// Validate cached id; clear only on definitive NotFound so a
|
||||||
|
// transient daemon error doesn't abort cleanup pipelines.
|
||||||
|
_, err := cr.cli.ContainerInspect(ctx, cr.id, client.ContainerInspectOptions{})
|
||||||
|
if !cerrdefs.IsNotFound(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cr.id = ""
|
||||||
}
|
}
|
||||||
containers, err := cr.cli.ContainerList(ctx, client.ContainerListOptions{
|
containers, err := cr.cli.ContainerList(ctx, client.ContainerListOptions{
|
||||||
All: true,
|
All: true,
|
||||||
@@ -335,7 +360,6 @@ func (cr *containerReference) find() common.Executor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cr.id = ""
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -592,6 +616,9 @@ func (cr *containerReference) extractFromImageEnv(env *map[string]string) common
|
|||||||
|
|
||||||
func (cr *containerReference) exec(cmd []string, env map[string]string, user, workdir string) common.Executor {
|
func (cr *containerReference) exec(cmd []string, env map[string]string, user, workdir string) common.Executor {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
|
if cr.id == "" {
|
||||||
|
return cr.missingContainerError("exec %v", cmd)
|
||||||
|
}
|
||||||
logger := common.Logger(ctx)
|
logger := common.Logger(ctx)
|
||||||
// Fix slashes when running on Windows
|
// Fix slashes when running on Windows
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
@@ -746,6 +773,9 @@ func (cr *containerReference) waitForCommand(ctx context.Context, isTerminal boo
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cr *containerReference) CopyTarStream(ctx context.Context, destPath string, tarStream io.Reader) error {
|
func (cr *containerReference) CopyTarStream(ctx context.Context, destPath string, tarStream io.Reader) error {
|
||||||
|
if cr.id == "" {
|
||||||
|
return cr.missingContainerError("copy to %s", destPath)
|
||||||
|
}
|
||||||
// Mkdir
|
// Mkdir
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
tw := tar.NewWriter(buf)
|
tw := tar.NewWriter(buf)
|
||||||
@@ -779,6 +809,9 @@ func (cr *containerReference) CopyTarStream(ctx context.Context, destPath string
|
|||||||
|
|
||||||
func (cr *containerReference) copyDir(dstPath, srcPath string, useGitIgnore bool) common.Executor {
|
func (cr *containerReference) copyDir(dstPath, srcPath string, useGitIgnore bool) common.Executor {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
|
if cr.id == "" {
|
||||||
|
return cr.missingContainerError("copy directory to %s", dstPath)
|
||||||
|
}
|
||||||
logger := common.Logger(ctx)
|
logger := common.Logger(ctx)
|
||||||
tarFile, err := os.CreateTemp("", "act")
|
tarFile, err := os.CreateTemp("", "act")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -853,6 +886,9 @@ func (cr *containerReference) copyDir(dstPath, srcPath string, useGitIgnore bool
|
|||||||
|
|
||||||
func (cr *containerReference) copyContent(dstPath string, files ...*FileEntry) common.Executor {
|
func (cr *containerReference) copyContent(dstPath string, files ...*FileEntry) common.Executor {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
|
if cr.id == "" {
|
||||||
|
return cr.missingContainerError("copy to %s", dstPath)
|
||||||
|
}
|
||||||
logger := common.Logger(ctx)
|
logger := common.Logger(ctx)
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
tw := tar.NewWriter(&buf)
|
tw := tar.NewWriter(&buf)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
|
|
||||||
"gitea.com/gitea/runner/act/common"
|
"gitea.com/gitea/runner/act/common"
|
||||||
|
|
||||||
|
cerrdefs "github.com/containerd/errdefs"
|
||||||
"github.com/moby/moby/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
mobyclient "github.com/moby/moby/client"
|
mobyclient "github.com/moby/moby/client"
|
||||||
"github.com/sirupsen/logrus/hooks/test"
|
"github.com/sirupsen/logrus/hooks/test"
|
||||||
@@ -98,6 +99,16 @@ func (m *mockDockerClient) CopyToContainer(ctx context.Context, id string, optio
|
|||||||
return args.Get(0).(mobyclient.CopyToContainerResult), args.Error(1)
|
return args.Get(0).(mobyclient.CopyToContainerResult), args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockDockerClient) ContainerInspect(ctx context.Context, id string, opts mobyclient.ContainerInspectOptions) (mobyclient.ContainerInspectResult, error) {
|
||||||
|
args := m.Called(ctx, id, opts)
|
||||||
|
return args.Get(0).(mobyclient.ContainerInspectResult), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockDockerClient) ContainerList(ctx context.Context, opts mobyclient.ContainerListOptions) (mobyclient.ContainerListResult, error) {
|
||||||
|
args := m.Called(ctx, opts)
|
||||||
|
return args.Get(0).(mobyclient.ContainerListResult), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
type endlessReader struct {
|
type endlessReader struct {
|
||||||
io.Reader
|
io.Reader
|
||||||
}
|
}
|
||||||
@@ -298,6 +309,105 @@ func TestDockerCopyTarStreamErrorInMkdir(t *testing.T) {
|
|||||||
client.AssertExpectations(t)
|
client.AssertExpectations(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// find() must drop a stale cached id so later Copy/Exec don't hit the
|
||||||
|
// daemon with a torn-down container.
|
||||||
|
func TestFindRevalidatesStaleID(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
notFound := cerrdefs.ErrNotFound.WithMessage("No such container")
|
||||||
|
boom := errors.New("daemon unreachable")
|
||||||
|
newCR := func(id string) (*containerReference, *mockDockerClient) {
|
||||||
|
client := &mockDockerClient{}
|
||||||
|
return &containerReference{id: id, cli: client, input: &NewContainerInput{Name: "job-1"}}, client
|
||||||
|
}
|
||||||
|
listOpts := mobyclient.ContainerListOptions{All: true}
|
||||||
|
inspectOpts := mobyclient.ContainerInspectOptions{}
|
||||||
|
|
||||||
|
t.Run("stale id cleared, name lookup empty", func(t *testing.T) {
|
||||||
|
cr, client := newCR("stale")
|
||||||
|
client.On("ContainerInspect", ctx, "stale", inspectOpts).Return(mobyclient.ContainerInspectResult{}, notFound)
|
||||||
|
client.On("ContainerList", ctx, listOpts).Return(mobyclient.ContainerListResult{}, nil)
|
||||||
|
require.NoError(t, cr.find()(ctx))
|
||||||
|
assert.Empty(t, cr.id)
|
||||||
|
client.AssertExpectations(t)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("stale id cleared, name lookup repopulates", func(t *testing.T) {
|
||||||
|
cr, client := newCR("stale")
|
||||||
|
client.On("ContainerInspect", ctx, "stale", inspectOpts).Return(mobyclient.ContainerInspectResult{}, notFound)
|
||||||
|
client.On("ContainerList", ctx, listOpts).Return(mobyclient.ContainerListResult{Items: []container.Summary{
|
||||||
|
{ID: "other", Names: []string{"/somebody-else"}},
|
||||||
|
{ID: "fresh", Names: []string{"/job-1"}},
|
||||||
|
}}, nil)
|
||||||
|
require.NoError(t, cr.find()(ctx))
|
||||||
|
assert.Equal(t, "fresh", cr.id)
|
||||||
|
client.AssertExpectations(t)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("live id kept", func(t *testing.T) {
|
||||||
|
cr, client := newCR("live")
|
||||||
|
client.On("ContainerInspect", ctx, "live", inspectOpts).Return(mobyclient.ContainerInspectResult{}, nil)
|
||||||
|
require.NoError(t, cr.find()(ctx))
|
||||||
|
assert.Equal(t, "live", cr.id)
|
||||||
|
client.AssertExpectations(t)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("transient inspect error trusts cache", func(t *testing.T) {
|
||||||
|
cr, client := newCR("live")
|
||||||
|
client.On("ContainerInspect", ctx, "live", inspectOpts).Return(mobyclient.ContainerInspectResult{}, boom)
|
||||||
|
require.NoError(t, cr.find()(ctx))
|
||||||
|
assert.Equal(t, "live", cr.id)
|
||||||
|
client.AssertExpectations(t)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("list error propagates", func(t *testing.T) {
|
||||||
|
cr, client := newCR("")
|
||||||
|
client.On("ContainerList", ctx, listOpts).Return(mobyclient.ContainerListResult{}, boom)
|
||||||
|
require.ErrorIs(t, cr.find()(ctx), boom)
|
||||||
|
client.AssertExpectations(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every daemon entry point fails fast with a clear, container-named
|
||||||
|
// error when no live cr.id is known.
|
||||||
|
func TestRejectsMissingContainer(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
client := &mockDockerClient{}
|
||||||
|
client.On("ContainerList", ctx, mobyclient.ContainerListOptions{All: true}).Return(mobyclient.ContainerListResult{}, nil)
|
||||||
|
cr := &containerReference{cli: client, input: &NewContainerInput{Name: "job-1"}}
|
||||||
|
check := func(op string, err error) {
|
||||||
|
t.Helper()
|
||||||
|
require.Error(t, err, op)
|
||||||
|
assert.Contains(t, err.Error(), `container "job-1" does not exist`, op)
|
||||||
|
}
|
||||||
|
check("copyContent", cr.copyContent("/var/run/act", &FileEntry{Name: "x", Mode: 0o644})(ctx))
|
||||||
|
check("copyDir", cr.copyDir("/var/run/act", "/src", false)(ctx))
|
||||||
|
check("CopyTarStream", cr.CopyTarStream(ctx, "/var/run/act", &bytes.Buffer{}))
|
||||||
|
check("exec", cr.exec([]string{"echo"}, nil, "", "")(ctx))
|
||||||
|
_, err := cr.GetContainerArchive(ctx, "/var/run/act/x")
|
||||||
|
check("GetContainerArchive", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// End-to-end: a stale cr.id is cleared, repopulated from name lookup,
|
||||||
|
// and the Copy completes against the fresh id.
|
||||||
|
func TestPublicCopyPipelineHandlesStaleID(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
client := &mockDockerClient{}
|
||||||
|
client.On("ContainerInspect", ctx, "stale", mobyclient.ContainerInspectOptions{}).
|
||||||
|
Return(mobyclient.ContainerInspectResult{}, cerrdefs.ErrNotFound.WithMessage("gone"))
|
||||||
|
client.On("ContainerList", ctx, mobyclient.ContainerListOptions{All: true}).
|
||||||
|
Return(mobyclient.ContainerListResult{Items: []container.Summary{
|
||||||
|
{ID: "fresh", Names: []string{"/job-1"}},
|
||||||
|
}}, nil)
|
||||||
|
client.On("CopyToContainer", ctx, "fresh", mock.MatchedBy(func(opts mobyclient.CopyToContainerOptions) bool {
|
||||||
|
return opts.DestinationPath == "/var/run/act"
|
||||||
|
})).Return(mobyclient.CopyToContainerResult{}, nil)
|
||||||
|
|
||||||
|
cr := &containerReference{id: "stale", cli: client, input: &NewContainerInput{Name: "job-1"}}
|
||||||
|
require.NoError(t, cr.Copy("/var/run/act", &FileEntry{Name: "x", Mode: 0o644})(ctx))
|
||||||
|
assert.Equal(t, "fresh", cr.id)
|
||||||
|
client.AssertExpectations(t)
|
||||||
|
}
|
||||||
|
|
||||||
// TestDockerCopyToSymlinkPath is a regression test for gitea/runner#981. Most base images
|
// TestDockerCopyToSymlinkPath is a regression test for gitea/runner#981. Most base images
|
||||||
// symlink /var/run to /run, so copying into /var/run/act traverses that symlink. The broken
|
// symlink /var/run to /run, so copying into /var/run/act traverses that symlink. The broken
|
||||||
// docker 29.5.1 daemon fails the extraction with "mkdirat var/run: file exists" (fixed in
|
// docker 29.5.1 daemon fails the extraction with "mkdirat var/run: file exists" (fixed in
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ func (rc *RunContext) commandHandler(ctx context.Context) common.LineHandler {
|
|||||||
logger.Infof("%s", line)
|
logger.Infof("%s", line)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
arg = unescapeCommandData(arg)
|
arg = UnescapeCommandData(arg)
|
||||||
kvPairs = unescapeKvPairs(kvPairs)
|
kvPairs = unescapeKvPairs(kvPairs)
|
||||||
switch command {
|
switch command {
|
||||||
case "set-env":
|
case "set-env":
|
||||||
@@ -151,7 +151,7 @@ func parseKeyValuePairs(kvPairs, separator string) map[string]string {
|
|||||||
return rtn
|
return rtn
|
||||||
}
|
}
|
||||||
|
|
||||||
func unescapeCommandData(arg string) string {
|
func UnescapeCommandData(arg string) string {
|
||||||
escapeMap := map[string]string{
|
escapeMap := map[string]string{
|
||||||
"%25": "%",
|
"%25": "%",
|
||||||
"%0D": "\r",
|
"%0D": "\r",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -166,9 +167,29 @@ func withStepLogger(ctx context.Context, stepNumber int, stepID, stepName, stage
|
|||||||
|
|
||||||
type entryProcessor func(entry *logrus.Entry) *logrus.Entry
|
type entryProcessor func(entry *logrus.Entry) *logrus.Entry
|
||||||
|
|
||||||
|
func AppendSecretMasker(oldnew []string, v string) []string {
|
||||||
|
ret := oldnew
|
||||||
|
|
||||||
|
for l := range strings.SplitSeq(v, "\n") {
|
||||||
|
tm := strings.TrimSpace(l)
|
||||||
|
// formatted JSON secrets could otherwise mask {,[,],} everywhere
|
||||||
|
if len(tm) > 1 {
|
||||||
|
ret = append(ret, tm, "***")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
// valueMasker applies secrets and ::add-mask:: patterns to every log entry, including
|
// valueMasker applies secrets and ::add-mask:: patterns to every log entry, including
|
||||||
// raw_output (command/stream) lines; there is no bypass by field.
|
// raw_output (command/stream) lines; there is no bypass by field.
|
||||||
func valueMasker(insecureSecrets bool, secrets map[string]string) entryProcessor {
|
func valueMasker(insecureSecrets bool, secrets map[string]string) entryProcessor {
|
||||||
|
var oldnew []string
|
||||||
|
for _, v := range secrets {
|
||||||
|
oldnew = AppendSecretMasker(oldnew, v)
|
||||||
|
}
|
||||||
|
oldnew = slices.Clip(oldnew)
|
||||||
|
defReplacer := strings.NewReplacer(oldnew...)
|
||||||
return func(entry *logrus.Entry) *logrus.Entry {
|
return func(entry *logrus.Entry) *logrus.Entry {
|
||||||
if insecureSecrets {
|
if insecureSecrets {
|
||||||
return entry
|
return entry
|
||||||
@@ -176,16 +197,16 @@ func valueMasker(insecureSecrets bool, secrets map[string]string) entryProcessor
|
|||||||
|
|
||||||
masks := Masks(entry.Context)
|
masks := Masks(entry.Context)
|
||||||
|
|
||||||
for _, v := range secrets {
|
if len(*masks) == 0 {
|
||||||
if v != "" {
|
entry.Message = defReplacer.Replace(entry.Message)
|
||||||
entry.Message = strings.ReplaceAll(entry.Message, v, "***")
|
} else {
|
||||||
}
|
cmasker := oldnew
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range *masks {
|
for _, v := range *masks {
|
||||||
if v != "" {
|
cmasker = AppendSecretMasker(cmasker, v)
|
||||||
entry.Message = strings.ReplaceAll(entry.Message, v, "***")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entry.Message = strings.NewReplacer(cmasker...).Replace(entry.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
return entry
|
return entry
|
||||||
|
|||||||
52
act/runner/logger_test.go
Normal file
52
act/runner/logger_test.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package runner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValueMasker(t *testing.T) {
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
lines string
|
||||||
|
secrets map[string]string
|
||||||
|
masks []string
|
||||||
|
disallowed []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Multiline Private Key",
|
||||||
|
lines: "cat << EOF > private.key\nPRIVATE_KEY_BEGIN\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\nPRIVATE_KEY_END\nEOF",
|
||||||
|
secrets: map[string]string{
|
||||||
|
"PRIVATE_KEY": "PRIVATE_KEY_BEGIN\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\nPRIVATE_KEY_END",
|
||||||
|
},
|
||||||
|
disallowed: []string{"KEY", "dsdfseffefsefes", "PRIVATE_KEY_END"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Multiline Private Key in masks",
|
||||||
|
lines: "cat << EOF > private.key\nPRIVATE_KEY_BEGIN\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\nPRIVATE_KEY_END\nEOF",
|
||||||
|
masks: []string{"PRIVATE_KEY_BEGIN\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\ndsdfseffefsefes\nPRIVATE_KEY_END"},
|
||||||
|
disallowed: []string{"KEY", "dsdfseffefsefes", "PRIVATE_KEY_END"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, entry := range table {
|
||||||
|
t.Run(entry.name, func(t *testing.T) {
|
||||||
|
ctx := WithMasks(t.Context(), &entry.masks)
|
||||||
|
masker := valueMasker(false, entry.secrets)
|
||||||
|
for line := range strings.SplitSeq(entry.lines, "\n") {
|
||||||
|
lentry := masker(&logrus.Entry{
|
||||||
|
Context: ctx,
|
||||||
|
Message: line,
|
||||||
|
})
|
||||||
|
for _, line := range entry.disallowed {
|
||||||
|
assert.NotContains(t, lentry.Message, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gitea.com/gitea/runner/act/runner"
|
||||||
"gitea.com/gitea/runner/internal/pkg/client"
|
"gitea.com/gitea/runner/internal/pkg/client"
|
||||||
"gitea.com/gitea/runner/internal/pkg/config"
|
"gitea.com/gitea/runner/internal/pkg/config"
|
||||||
"gitea.com/gitea/runner/internal/pkg/metrics"
|
"gitea.com/gitea/runner/internal/pkg/metrics"
|
||||||
@@ -73,13 +74,13 @@ type Reporter struct {
|
|||||||
func NewReporter(ctx context.Context, cancel context.CancelFunc, client client.Client, task *runnerv1.Task, cfg *config.Config) *Reporter {
|
func NewReporter(ctx context.Context, cancel context.CancelFunc, client client.Client, task *runnerv1.Task, cfg *config.Config) *Reporter {
|
||||||
var oldnew []string
|
var oldnew []string
|
||||||
if v := task.Context.Fields["token"].GetStringValue(); v != "" {
|
if v := task.Context.Fields["token"].GetStringValue(); v != "" {
|
||||||
oldnew = append(oldnew, v, "***")
|
oldnew = runner.AppendSecretMasker(oldnew, v)
|
||||||
}
|
}
|
||||||
if v := task.Context.Fields["gitea_runtime_token"].GetStringValue(); v != "" {
|
if v := task.Context.Fields["gitea_runtime_token"].GetStringValue(); v != "" {
|
||||||
oldnew = append(oldnew, v, "***")
|
oldnew = runner.AppendSecretMasker(oldnew, v)
|
||||||
}
|
}
|
||||||
for _, v := range task.Secrets {
|
for _, v := range task.Secrets {
|
||||||
oldnew = append(oldnew, v, "***")
|
oldnew = runner.AppendSecretMasker(oldnew, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
rv := &Reporter{
|
rv := &Reporter{
|
||||||
@@ -689,7 +690,7 @@ func (r *Reporter) parseLogRow(entry *log.Entry) *runnerv1.LogRow {
|
|||||||
|
|
||||||
matches := cmdRegex.FindStringSubmatch(content)
|
matches := cmdRegex.FindStringSubmatch(content)
|
||||||
if matches != nil {
|
if matches != nil {
|
||||||
if output := r.handleCommand(content, matches[1], matches[3]); output != nil {
|
if output := r.handleCommand(content, matches[1], runner.UnescapeCommandData(matches[3])); output != nil {
|
||||||
content = *output
|
content = *output
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
@@ -705,6 +706,6 @@ func (r *Reporter) parseLogRow(entry *log.Entry) *runnerv1.LogRow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reporter) addMask(msg string) {
|
func (r *Reporter) addMask(msg string) {
|
||||||
r.oldnew = append(r.oldnew, msg, "***")
|
r.oldnew = runner.AppendSecretMasker(r.oldnew, msg)
|
||||||
r.logReplacer = strings.NewReplacer(r.oldnew...)
|
r.logReplacer = strings.NewReplacer(r.oldnew...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,19 @@ func TestReporter_parseLogRow(t *testing.T) {
|
|||||||
"foo *** bar",
|
"foo *** bar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Add-mask-multiline", false,
|
||||||
|
[]string{
|
||||||
|
"foo mysecret bar",
|
||||||
|
"::add-mask::LINE1%0ALINE2",
|
||||||
|
"foo LINE1 bar",
|
||||||
|
},
|
||||||
|
[]string{
|
||||||
|
"foo mysecret bar",
|
||||||
|
"<nil>",
|
||||||
|
"foo *** bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Debug enabled", true,
|
"Debug enabled", true,
|
||||||
[]string{
|
[]string{
|
||||||
|
|||||||
Reference in New Issue
Block a user