From df8c04d996a72df11f7119a950b454546789ecd1 Mon Sep 17 00:00:00 2001 From: zc Date: Wed, 20 Mar 2024 21:21:01 +0800 Subject: [PATCH] log writer support mask replace to hidden secrets --- .golangci.yaml | 2 +- core/worker/runtime/replacer.go | 68 +++++++++++++++++++++++++++++++++ core/worker/worker.go | 23 +++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 core/worker/runtime/replacer.go diff --git a/.golangci.yaml b/.golangci.yaml index ffd1d66..8875e2d 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -3,7 +3,7 @@ linters-settings: threshold: 200 funlen: lines: -1 - statements: 100 + statements: 150 gci: skip-generated: true custom-order: true diff --git a/core/worker/runtime/replacer.go b/core/worker/runtime/replacer.go new file mode 100644 index 0000000..66eb78c --- /dev/null +++ b/core/worker/runtime/replacer.go @@ -0,0 +1,68 @@ +// Copyright © 2024 zc2638 . +// +// 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 runtime + +import ( + "io" + "strings" +) + +// maskReplacer is an io.Writer that finds and masks sensitive data. +type maskReplacer struct { + w io.WriteCloser + r *strings.Replacer +} + +// NewMaskReplacer returns a replacer that wraps io.Writer w. +func NewMaskReplacer(w io.WriteCloser, values []string) io.WriteCloser { + var oldnew []string + for _, v := range values { + if len(v) == 0 { + continue + } + + for _, part := range strings.Split(v, "\n") { + part = strings.TrimSpace(part) + + // avoid masking empty or single character strings. + if len(part) < 2 { + continue + } + + masked := "******" + oldnew = append(oldnew, part) + oldnew = append(oldnew, masked) + } + } + if len(oldnew) == 0 { + return w + } + return &maskReplacer{ + w: w, + r: strings.NewReplacer(oldnew...), + } +} + +// Write writes p to the base writer. The method scans for any +// sensitive data in p and masks before writing. +func (r *maskReplacer) Write(p []byte) (n int, err error) { + _, err = r.w.Write([]byte(r.r.Replace(string(p)))) + return len(p), err +} + +// Close closes the base writer. +func (r *maskReplacer) Close() error { + return r.w.Close() +} diff --git a/core/worker/worker.go b/core/worker/worker.go index ee7587a..e249764 100644 --- a/core/worker/worker.go +++ b/core/worker/worker.go @@ -22,11 +22,13 @@ import ( "math" "time" + "github.com/99nil/gopkg/sets" "github.com/zc2638/wslog" "golang.org/x/sync/errgroup" "github.com/zc2638/ink/core/clients" "github.com/zc2638/ink/core/constant" + "github.com/zc2638/ink/core/worker/runtime" v1 "github.com/zc2638/ink/pkg/api/core/v1" "github.com/zc2638/ink/pkg/livelog" ) @@ -210,6 +212,26 @@ func execute( return nil } + secretValueSet := sets.New[string]() + for _, secret := range secrets { + if err := secret.Decrypt(); err == nil { + status.Phase = v1.PhaseFailed + status.Error = err.Error() + for _, step := range status.Steps { + step.Phase = v1.PhaseSkipped + step.Started = status.Started + } + if err := client.StageEnd(ctx, status); err != nil { + return fmt.Errorf("stage end failed: %v", err) + } + return nil + } + for _, v := range secret.Data { + secretValueSet.Add(v) + } + } + secretValueList := secretValueSet.List() + var ( failed bool canceled bool @@ -278,6 +300,7 @@ func execute( } } wc := livelog.NewWriter(logHandle) + wc = runtime.NewMaskReplacer(wc, secretValueList) stepLog.Debug("Execute step hook") state, err := hook.Step(ctx, spec, stepSpec, wc)