Propagate source evidence sensitivity through history
This commit is contained in:
@@ -92,6 +92,40 @@ func TestInheritanceSensitivityIncludesSchemaTaggedSource(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceVersionEvidenceSensitivityFollowsInheritanceAndDisplacedHistory(t *testing.T) {
|
||||
const canary = "EVIDENCE-INHERITANCE-CANARY"
|
||||
input := parseCompositionDocument(t, "source-1", "parent: {branch: {token: "+canary+", plain: visible}}\nchild: {extends: parent, branch: public}\n")
|
||||
inspection, _ := inheritanceInspectionForTest(t, input)
|
||||
secretPath := Keys("parent", "branch", "token")
|
||||
evidence := &sourceVersionEvidence{
|
||||
path: secretPath.clone(), resolvedPath: secretPath.clone(),
|
||||
version: Version{Present: true, Text: canary},
|
||||
ancestors: []sourceVersionAncestor{{}, {path: Keys("parent")}, {path: Keys("parent", "branch")}, {path: secretPath.clone()}},
|
||||
}
|
||||
inspection.secrets = classifySecretPathsWithEvidence(nil, func(path Path) bool {
|
||||
return path.String() == secretPath.String()
|
||||
}, inspection.metadata, inspection.final, inspection.applications, []*sourceVersionEvidence{evidence}, inspection.input)
|
||||
|
||||
path := Keys("child", "branch", "token")
|
||||
explanation, found := inspection.Explain(path)
|
||||
if !found || explanation.Effective.Present {
|
||||
t.Fatalf("displaced inherited path = %#v, %v", explanation, found)
|
||||
}
|
||||
for _, change := range explanation.History {
|
||||
if strings.Contains(change.Before.YAML, canary) || strings.Contains(change.After.YAML, canary) ||
|
||||
(change.After.Present && !change.After.Redacted) {
|
||||
t.Fatalf("displaced inherited canary escaped redaction: %#v", explanation.History)
|
||||
}
|
||||
}
|
||||
plain, found := inspection.Explain(Keys("parent", "branch", "plain"))
|
||||
if !found || plain.Effective.Redacted || plain.Effective.YAML != "visible\n" {
|
||||
t.Fatalf("neighboring value was classified with evidence: %#v, %v", plain, found)
|
||||
}
|
||||
if _, found := inspection.Metadata().Lookup(Keys("evidence-only")); found {
|
||||
t.Fatal("evidence classification created a metadata path")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInheritedSecretReferencesResolveOnlyAfterExpansion(t *testing.T) {
|
||||
type entry struct {
|
||||
Credential Secret `yaml:"credential"`
|
||||
|
||||
@@ -75,19 +75,6 @@ func classifySecretPathsWithEvidence(schema *schemaDescriptor, predicate func(Pa
|
||||
classifyProjection(tree)
|
||||
}
|
||||
classifyProjection(final)
|
||||
evidenceSecrets := make(map[string]bool)
|
||||
seenEvidence := make(map[string][]Path)
|
||||
classifyEvidence := func(destination, local Path) {
|
||||
path := appendDestination(destination, local)
|
||||
pointer := path.String()
|
||||
for _, seen := range seenEvidence[pointer] {
|
||||
if sameSecretPath(seen, path) {
|
||||
return
|
||||
}
|
||||
}
|
||||
seenEvidence[pointer] = append(seenEvidence[pointer], path.clone())
|
||||
classifyEvidencePath(evidenceSecrets, schema, predicate, path)
|
||||
}
|
||||
for _, item := range evidence {
|
||||
if item == nil {
|
||||
continue
|
||||
@@ -96,9 +83,9 @@ func classifySecretPathsWithEvidence(schema *schemaDescriptor, predicate func(Pa
|
||||
if !candidate.valid() {
|
||||
candidate = item.path
|
||||
}
|
||||
classifyEvidence(item.destination, candidate)
|
||||
addSecretEvidencePath(paths, item.destination, candidate)
|
||||
for _, ancestor := range item.ancestors {
|
||||
classifyEvidence(item.destination, ancestor.path)
|
||||
addSecretEvidencePath(paths, item.destination, ancestor.path)
|
||||
}
|
||||
}
|
||||
for pointer, path := range paths {
|
||||
@@ -109,36 +96,12 @@ func classifySecretPathsWithEvidence(schema *schemaDescriptor, predicate func(Pa
|
||||
secret[pointer] = true
|
||||
}
|
||||
}
|
||||
classified := closeSecretDependencies(paths, secret, dependencies, inherited)
|
||||
for pointer, sensitive := range evidenceSecrets {
|
||||
if sensitive {
|
||||
classified[pointer] = true
|
||||
}
|
||||
}
|
||||
return classified
|
||||
return closeSecretDependencies(paths, secret, dependencies, inherited)
|
||||
}
|
||||
|
||||
func classifyEvidencePath(secret map[string]bool, schema *schemaDescriptor, predicate func(Path) bool, path Path) {
|
||||
pointer := path.String()
|
||||
if root, ok := schemaSecretRoot(schema, path); ok {
|
||||
secret[root.String()] = true
|
||||
}
|
||||
if predicate != nil && predicate(path.clone()) {
|
||||
secret[pointer] = true
|
||||
}
|
||||
}
|
||||
|
||||
func sameSecretPath(left, right Path) bool {
|
||||
if len(left.segments) != len(right.segments) {
|
||||
return false
|
||||
}
|
||||
for index, segment := range left.segments {
|
||||
other := right.segments[index]
|
||||
if segment.kind != other.kind || segment.key != other.key || segment.index != other.index {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
func addSecretEvidencePath(paths map[string]Path, destination, local Path) {
|
||||
path := appendDestination(destination, local)
|
||||
paths[path.String()] = path.clone()
|
||||
}
|
||||
|
||||
func addSecretPath(paths map[string]Path, pointer string) {
|
||||
|
||||
@@ -974,6 +974,74 @@ func TestSourceVersionUnderlaysProtectRemainingMountedPayload(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceVersionEvidencePropagatesThroughRecordedResolutionDependency(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type credentials struct {
|
||||
Password string `yaml:"password"`
|
||||
Public string `yaml:"public"`
|
||||
}
|
||||
type configuration struct {
|
||||
Credentials credentials `yaml:"credentials"`
|
||||
Derived string `yaml:"derived"`
|
||||
}
|
||||
load := func(header string) *Loaded[configuration] {
|
||||
t.Helper()
|
||||
loaded, err := LoadLayered(Input{
|
||||
Source: Bytes("main", []byte("credentials: {password: main-value}\n")),
|
||||
}, testSecretBinding(), LayeredOptions[configuration]{
|
||||
SourceVersionMode: sourceVersionModeIfPresent,
|
||||
Underlays: []Underlay{{Inputs: []Input{{Source: Bytes("underlay", []byte(
|
||||
"credentials:\n version: "+header+"\n public: visible-neighbor\n",
|
||||
))}}}},
|
||||
Definition: Definition[configuration]{
|
||||
Version: &VersionRule{
|
||||
Path: Keys("credentials", "version"),
|
||||
Check: func(Version, *Validation) error { return nil },
|
||||
},
|
||||
Secret: func(path Path) bool {
|
||||
return path.String() == "/credentials/version"
|
||||
},
|
||||
Resolve: func(value *configuration, resolution *Resolution) error {
|
||||
value.Derived = value.Credentials.Password
|
||||
resolution.Record(Keys("derived"), ChangeNormalization, "derive from credentials", Keys("credentials"))
|
||||
return nil
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil || loaded == nil {
|
||||
t.Fatalf("LoadLayered with header %q = %#v, %v", header, loaded, err)
|
||||
}
|
||||
return loaded
|
||||
}
|
||||
|
||||
left, right := load("202"), load("303")
|
||||
if left.Config.Derived != "main-value" || right.Config.Derived != "main-value" {
|
||||
t.Fatalf("derived values = %q, %q", left.Config.Derived, right.Config.Derived)
|
||||
}
|
||||
if !left.Inspection.secrets["/derived"] || !right.Inspection.secrets["/derived"] {
|
||||
t.Fatalf("evidence did not activate recorded dependency closure: left=%#v right=%#v", left.Inspection.secrets, right.Inspection.secrets)
|
||||
}
|
||||
derived, found := left.Inspection.Explain(Keys("derived"))
|
||||
if !found || !derived.Effective.Redacted {
|
||||
t.Fatalf("derived explanation was not redacted: %#v, found=%v", derived, found)
|
||||
}
|
||||
public, found := left.Inspection.Explain(Keys("credentials", "public"))
|
||||
if !found || public.Effective.Redacted || public.Effective.YAML != "visible-neighbor\n" {
|
||||
t.Fatalf("neighboring payload = %#v, found=%v", public, found)
|
||||
}
|
||||
headerPath := Keys("credentials", "version")
|
||||
if _, found := left.Inspection.Explain(headerPath); found {
|
||||
t.Fatal("consumed declaration created an effective or historical explanation path")
|
||||
}
|
||||
if _, found := left.Inspection.Metadata().Lookup(headerPath); found {
|
||||
t.Fatal("consumed declaration created a metadata path")
|
||||
}
|
||||
if differences, err := Diff(left.Inspection, right.Inspection); err != nil || len(differences) != 0 {
|
||||
t.Fatalf("different consumed headers changed effective diff: %#v, %v", differences, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceVersionSelectedOverlayConsumesHeaderAndRetainsWarning(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user