166 lines
5.3 KiB
Go
166 lines
5.3 KiB
Go
package config
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
|
|
configkit "gitea.maximumdirect.net/eric/ConfigKit"
|
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
|
)
|
|
|
|
func (e EffectiveConfig) RedactedResolvedPipelinePayload() pipeline.ResolvedPipeline {
|
|
return cloneResolvedPipeline(e.ResolvedPipeline)
|
|
}
|
|
|
|
func cloneResolvedPipeline(in pipeline.ResolvedPipeline) pipeline.ResolvedPipeline {
|
|
out := in
|
|
out.ConfiguredValidationPolicy = cloneValidationPolicyOverride(in.ConfiguredValidationPolicy)
|
|
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.Steps) > 0 {
|
|
out.Steps = make([]pipeline.ResolvedPipelineStep, len(in.Steps))
|
|
for i, step := range in.Steps {
|
|
out.Steps[i] = pipeline.ResolvedPipelineStep{ID: step.ID}
|
|
if len(step.ArtifactLanes) > 0 {
|
|
out.Steps[i].ArtifactLanes = make([]pipeline.ResolvedArtifactLane, len(step.ArtifactLanes))
|
|
for j, lane := range step.ArtifactLanes {
|
|
out.Steps[i].ArtifactLanes[j] = 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 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")
|
|
}
|
|
|
|
// sensitiveDocumentPath applies the existing option-key classification to the
|
|
// fully typed ConfigKit document. Dynamic option maps can be nested below any
|
|
// producer or validator, so every key after an options container participates.
|
|
func sensitiveDocumentPath(path configkit.Path) bool {
|
|
segments := strings.Split(strings.TrimPrefix(path.String(), "/"), "/")
|
|
withinOptions := false
|
|
for _, segment := range segments {
|
|
segment = strings.ReplaceAll(strings.ReplaceAll(segment, "~1", "/"), "~0", "~")
|
|
if withinOptions && sensitiveConfigKey(segment) {
|
|
return true
|
|
}
|
|
if segment == "options" {
|
|
withinOptions = true
|
|
}
|
|
}
|
|
return false
|
|
}
|