From 7986eba00202be0137c74ab14e0a45b7be255d61 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 8 Jan 2022 20:39:52 +0100 Subject: [PATCH] Hide multi line secrets from log (#671) close #388 --- pipeline/rpc/line.go | 25 ++++++++---------- pipeline/shared/replace_secrets.go | 34 +++++++++++++++++++++++++ pipeline/shared/replace_secrets_test.go | 33 ++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 15 deletions(-) create mode 100644 pipeline/shared/replace_secrets.go create mode 100644 pipeline/shared/replace_secrets_test.go diff --git a/pipeline/rpc/line.go b/pipeline/rpc/line.go index e6ef57e93..a7cd0f342 100644 --- a/pipeline/rpc/line.go +++ b/pipeline/rpc/line.go @@ -7,6 +7,8 @@ import ( "time" "github.com/rs/zerolog/log" + + "github.com/woodpecker-ci/woodpecker/pipeline/shared" ) // Identifies the type of line in the logs. @@ -49,22 +51,14 @@ type LineWriter struct { // NewLineWriter returns a new line reader. func NewLineWriter(peer Peer, id, name string, secret ...string) *LineWriter { - w := new(LineWriter) - w.peer = peer - w.id = id - w.name = name - w.num = 0 - w.now = time.Now().UTC() - - var oldnew []string - for _, old := range secret { - oldnew = append(oldnew, old) - oldnew = append(oldnew, "********") + return &LineWriter{ + peer: peer, + id: id, + name: name, + now: time.Now().UTC(), + rep: shared.NewSecretsReplacer(secret), + lines: nil, } - if len(oldnew) != 0 { - w.rep = strings.NewReplacer(oldnew...) - } - return w } func (w *LineWriter) Write(p []byte) (n int, err error) { @@ -72,6 +66,7 @@ func (w *LineWriter) Write(p []byte) (n int, err error) { if w.rep != nil { out = w.rep.Replace(out) } + log.Trace().Str("name", w.name).Str("ID", w.id).Msgf("grpc write line: %s", out) line := &Line{ Out: out, diff --git a/pipeline/shared/replace_secrets.go b/pipeline/shared/replace_secrets.go new file mode 100644 index 000000000..ad0899103 --- /dev/null +++ b/pipeline/shared/replace_secrets.go @@ -0,0 +1,34 @@ +// Copyright 2022 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shared + +import "strings" + +func NewSecretsReplacer(secrets []string) *strings.Replacer { + var oldnew []string + for _, old := range secrets { + old = strings.TrimSpace(old) + if len(old) == 0 { + continue + } + // since replacer is executed on each line we have to split multi-line-secrets + for _, part := range strings.Split(old, "\n") { + oldnew = append(oldnew, part) + oldnew = append(oldnew, "********") + } + } + + return strings.NewReplacer(oldnew...) +} diff --git a/pipeline/shared/replace_secrets_test.go b/pipeline/shared/replace_secrets_test.go new file mode 100644 index 000000000..f5a26a140 --- /dev/null +++ b/pipeline/shared/replace_secrets_test.go @@ -0,0 +1,33 @@ +package shared + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewSecretsReplacer(t *testing.T) { + tc := []struct { + log string + secrets []string + expect string + }{{ + log: "start log\ndone", + secrets: []string{""}, + expect: "start log\ndone", + }, { + log: `this IS secret: password`, + secrets: []string{"password", " IS "}, + expect: `this ******** secret: ********`, + }, { + log: "start log\ndone\nnow\nan\nmulti line secret!! ;)", + secrets: []string{"an\nmulti line secret!!"}, + expect: "start log\ndone\nnow\n********\n******** ;)", + }} + + for _, c := range tc { + rep := NewSecretsReplacer(c.secrets) + result := rep.Replace(c.log) + assert.EqualValues(t, c.expect, result) + } +}