Files
notarius/internal/core/config/redaction.go

178 lines
5.4 KiB
Go

package config
import (
"reflect"
"strings"
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
)
func (c Config) Redacted() Config {
return redactConfig(cloneConfig(c))
}
func (c Config) RedactedSummaryPayload() any {
return c.Redacted()
}
func (e EffectiveConfig) RedactedSummaryPayload() any {
return EffectiveConfig{
Config: e.Config.Redacted(),
PipelineID: e.PipelineID,
Only: append([]string(nil), e.Only...),
ReferenceOverrides: append([]pipeline.ReferenceBinding(nil), e.ReferenceOverrides...),
ReferenceUnbinds: append([]pipeline.ReferenceUnbind(nil), e.ReferenceUnbinds...),
ResolvedPipeline: cloneResolvedPipeline(e.ResolvedPipeline),
}
}
func (e EffectiveConfig) RedactedResolvedPipelinePayload() pipeline.ResolvedPipeline {
return cloneResolvedPipeline(e.ResolvedPipeline)
}
func cloneResolvedPipeline(in pipeline.ResolvedPipeline) pipeline.ResolvedPipeline {
out := in
out.Input = redactBinding(cloneModuleBinding(in.Input))
out.Chunk = redactBinding(cloneModuleBinding(in.Chunk))
out.ChunkReferences = pipeline.CloneReferenceTarget(in.ChunkReferences)
out.Output = redactBinding(cloneModuleBinding(in.Output))
if len(in.ValidatorChains) > 0 {
out.ValidatorChains = make([]pipeline.ResolvedValidatorChain, len(in.ValidatorChains))
for i, chain := range in.ValidatorChains {
out.ValidatorChains[i] = cloneResolvedValidatorChain(chain)
}
}
if len(in.ArtifactLanes) > 0 {
out.ArtifactLanes = make([]pipeline.ResolvedArtifactLane, len(in.ArtifactLanes))
for i, lane := range in.ArtifactLanes {
out.ArtifactLanes[i] = cloneResolvedArtifactLane(lane)
}
}
return out
}
func cloneResolvedValidatorChain(in pipeline.ResolvedValidatorChain) pipeline.ResolvedValidatorChain {
out := in
if len(in.Validators) > 0 {
out.Validators = make([]pipeline.ResolvedValidator, len(in.Validators))
for i, validator := range in.Validators {
out.Validators[i] = pipeline.ResolvedValidator{
Binding: redactBinding(cloneModuleBinding(validator.Binding)),
ExecutionClass: validator.ExecutionClass,
Target: validator.Target,
ArtifactKind: validator.ArtifactKind,
}
}
}
return out
}
func cloneResolvedArtifactLane(in pipeline.ResolvedArtifactLane) pipeline.ResolvedArtifactLane {
out := in
out.Extract = redactBinding(cloneModuleBinding(in.Extract))
out.Merge = redactBinding(cloneModuleBinding(in.Merge))
out.Normalize = redactBinding(cloneModuleBinding(in.Normalize))
out.ExtractReferences = pipeline.CloneReferenceTarget(in.ExtractReferences)
out.MergeReferences = pipeline.CloneReferenceTarget(in.MergeReferences)
out.NormalizeReferences = pipeline.CloneReferenceTarget(in.NormalizeReferences)
if len(in.Validators) > 0 {
out.Validators = make([]pipeline.ModuleBinding, len(in.Validators))
for i, binding := range in.Validators {
out.Validators[i] = redactBinding(cloneModuleBinding(binding))
}
}
return out
}
func redactConfig(cfg Config) Config {
for id, profile := range cfg.Pipelines {
profile.Input = redactBinding(profile.Input)
profile.Chunk = redactBinding(profile.Chunk)
profile.Output = redactBinding(profile.Output)
for laneID, lane := range profile.Artifacts {
lane.Extract = redactBinding(lane.Extract)
lane.Merge = redactBinding(lane.Merge)
lane.Normalize = redactBinding(lane.Normalize)
for i := range lane.Validators {
lane.Validators[i] = redactBinding(lane.Validators[i])
}
profile.Artifacts[laneID] = lane
}
cfg.Pipelines[id] = profile
}
return cfg
}
func redactBinding(binding pipeline.ModuleBinding) pipeline.ModuleBinding {
binding.Options = redactOptions(binding.Options)
for i := range binding.Validators.Validators {
binding.Validators.Validators[i] = redactBinding(binding.Validators.Validators[i])
}
return binding
}
func redactOptions(values map[string]any) map[string]any {
if len(values) == 0 {
return nil
}
out := make(map[string]any, len(values))
for key, value := range values {
if sensitiveConfigKey(key) {
out[key] = "[REDACTED]"
continue
}
out[key] = redactOptionValue(value)
}
return out
}
func redactOptionValue(value any) any {
if value == nil {
return nil
}
reflected := reflect.ValueOf(value)
switch reflected.Kind() {
case reflect.Map:
if reflected.Type().Key().Kind() != reflect.String {
return value
}
if reflected.IsNil() {
return nil
}
out := make(map[string]any, reflected.Len())
iterator := reflected.MapRange()
for iterator.Next() {
key := iterator.Key().String()
if sensitiveConfigKey(key) {
out[key] = "[REDACTED]"
continue
}
out[key] = redactOptionValue(iterator.Value().Interface())
}
return out
case reflect.Slice:
if reflected.IsNil() {
return nil
}
if reflected.Type().Elem().Kind() == reflect.Uint8 {
out := reflect.MakeSlice(reflected.Type(), reflected.Len(), reflected.Len())
reflect.Copy(out, reflected)
return out.Interface()
}
fallthrough
case reflect.Array:
items := make([]any, reflected.Len())
for i := 0; i < reflected.Len(); i++ {
items[i] = redactOptionValue(reflected.Index(i).Interface())
}
return items
default:
return value
}
}
func sensitiveConfigKey(key string) bool {
key = strings.ToLower(key)
return strings.Contains(key, "api_key") || strings.Contains(key, "apikey") || strings.Contains(key, "authorization") || strings.Contains(key, "bearer") || strings.Contains(key, "password") || strings.Contains(key, "secret") || strings.Contains(key, "token")
}