Preserve all unknown PolicyRequirement fields on (podman image trust set)

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č <mitr@redhat.com>
This commit is contained in:
Miloslav Trmač
2022-08-25 01:25:08 +02:00
parent ad0c785f8e
commit 61fe95bb4f
2 changed files with 87 additions and 7 deletions

View File

@ -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)
}
}

View File

@ -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.