From 61fe95bb4fa7b6f83cd2a013877664db90cd773b Mon Sep 17 00:00:00 2001 From: Miloslav Trmač Date: Thu, 25 Aug 2022 01:25:08 +0200 Subject: Preserve all unknown PolicyRequirement fields on (podman image trust set) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We are unmarshaling and re-marshaling JSON, which can _silently_ drop data with the Go design decision.data. Try harder, by using json.RawMessage at least for the data we care about. Alternatively, this could use json.Decoder.DisallowUnknownFields. Signed-off-by: Miloslav Trmač --- pkg/trust/policy.go | 30 +++++++++++++++++------ pkg/trust/policy_test.go | 64 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 7 deletions(-) (limited to 'pkg/trust') diff --git a/pkg/trust/policy.go b/pkg/trust/policy.go index daa7698b2..326fe17af 100644 --- a/pkg/trust/policy.go +++ b/pkg/trust/policy.go @@ -40,6 +40,18 @@ type repoContent struct { SignedIdentity json.RawMessage `json:"signedIdentity,omitempty"` } +// genericPolicyContent is the overall structure of a policy.json file (= c/image/v5/signature.Policy), using generic data for individual requirements. +type genericPolicyContent struct { + Default json.RawMessage `json:"default"` + Transports genericTransportsContent `json:"transports,omitempty"` +} + +// genericTransportsContent contains policies for individual transports (= c/image/v5/signature.Policy.Transports), using generic data for individual requirements. +type genericTransportsContent map[string]genericRepoMap + +// genericRepoMap maps a scope name to requirements that apply to that scope (= c/image/v5/signature.PolicyTransportScopes) +type genericRepoMap map[string]json.RawMessage + // DefaultPolicyPath returns a path to the default policy of the system. func DefaultPolicyPath(sys *types.SystemContext) string { systemDefaultPolicyPath := "/etc/containers/policy.json" @@ -152,7 +164,7 @@ type AddPolicyEntriesInput struct { // AddPolicyEntries adds one or more policy entries necessary to implement AddPolicyEntriesInput. func AddPolicyEntries(policyPath string, input AddPolicyEntriesInput) error { var ( - policyContentStruct policyContent + policyContentStruct genericPolicyContent newReposContent []repoContent ) trustType := input.Type @@ -188,8 +200,12 @@ func AddPolicyEntries(policyPath string, input AddPolicyEntriesInput) error { default: return fmt.Errorf("unknown trust type %q", input.Type) } + newReposJSON, err := json.Marshal(newReposContent) + if err != nil { + return err + } - _, err := os.Stat(policyPath) + _, err = os.Stat(policyPath) if !os.IsNotExist(err) { policyContent, err := ioutil.ReadFile(policyPath) if err != nil { @@ -200,7 +216,7 @@ func AddPolicyEntries(policyPath string, input AddPolicyEntriesInput) error { } } if input.Scope == "default" { - policyContentStruct.Default = newReposContent + policyContentStruct.Default = json.RawMessage(newReposJSON) } else { if len(policyContentStruct.Default) == 0 { return errors.New("default trust policy must be set") @@ -209,18 +225,18 @@ func AddPolicyEntries(policyPath string, input AddPolicyEntriesInput) error { for transport, transportval := range policyContentStruct.Transports { _, registryExists = transportval[input.Scope] if registryExists { - policyContentStruct.Transports[transport][input.Scope] = newReposContent + policyContentStruct.Transports[transport][input.Scope] = json.RawMessage(newReposJSON) break } } if !registryExists { if policyContentStruct.Transports == nil { - policyContentStruct.Transports = make(map[string]repoMap) + policyContentStruct.Transports = make(map[string]genericRepoMap) } if policyContentStruct.Transports["docker"] == nil { - policyContentStruct.Transports["docker"] = make(map[string][]repoContent) + policyContentStruct.Transports["docker"] = make(map[string]json.RawMessage) } - policyContentStruct.Transports["docker"][input.Scope] = append(policyContentStruct.Transports["docker"][input.Scope], newReposContent...) + policyContentStruct.Transports["docker"][input.Scope] = json.RawMessage(newReposJSON) } } diff --git a/pkg/trust/policy_test.go b/pkg/trust/policy_test.go index 0f9721722..3952b72c3 100644 --- a/pkg/trust/policy_test.go +++ b/pkg/trust/policy_test.go @@ -108,6 +108,70 @@ func TestAddPolicyEntries(t *testing.T) { }, }, }, parsedPolicy) + + // Test that completely unknown JSON is preserved + jsonWithUnknownData := `{ + "default": [ + { + "type": "this is unknown", + "unknown field": "should be preserved" + } + ], + "transports": + { + "docker-daemon": + { + "": [{ + "type":"this is unknown 2", + "unknown field 2": "should be preserved 2" + }] + } + } +}` + err = os.WriteFile(policyPath, []byte(jsonWithUnknownData), 0600) + require.NoError(t, err) + err = AddPolicyEntries(policyPath, AddPolicyEntriesInput{ + Scope: "quay.io/innocuous", + Type: "signedBy", + PubKeyFiles: []string{"/1.pub"}, + }) + require.NoError(t, err) + updatedJSONWithUnknownData, err := os.ReadFile(policyPath) + require.NoError(t, err) + // Decode updatedJSONWithUnknownData so that this test does not depend on details of the encoding. + // To reduce noise in the constants below: + type a = []interface{} + type m = map[string]interface{} + var parsedUpdatedJSON m + err = json.Unmarshal(updatedJSONWithUnknownData, &parsedUpdatedJSON) + require.NoError(t, err) + assert.Equal(t, m{ + "default": a{ + m{ + "type": "this is unknown", + "unknown field": "should be preserved", + }, + }, + "transports": m{ + "docker-daemon": m{ + "": a{ + m{ + "type": "this is unknown 2", + "unknown field 2": "should be preserved 2", + }, + }, + }, + "docker": m{ + "quay.io/innocuous": a{ + m{ + "type": "signedBy", + "keyType": "GPGKeys", + "keyPath": "/1.pub", + }, + }, + }, + }, + }, parsedUpdatedJSON) } // xNewPRSignedByKeyPath is a wrapper for NewPRSignedByKeyPath which must not fail. -- cgit v1.2.3-54-g00ecf