From dc977e1eff5178650f71e6906141dda9112cfacf Mon Sep 17 00:00:00 2001 From: jakobmoellerdev Date: Fri, 3 Jan 2025 17:19:31 +0100 Subject: [PATCH] feat: jsonNormalisation/v3 and old fixes avoiding broken sigs Package jsonv3 provides a normalization which is completely based on the abstract (internal) version of the component descriptor and is therefore agnostic of the final serialization format. Signatures using this algorithm can be transferred among different schema versions, as long as is able to handle the complete information using for the normalization. jsonv2 is the predecessor of this version but had internal defaulting logic that is no longer included as part of this normalization. Thus v3 should be preferred over v2. Note that between v2 and v3 differences can occur mainly if the "extra identity" field is not unique, in which case the v2 normalization opinionated on how to differentiate these items. This no longer happens in v3, meaning the component descriptor is normalized as is. v2 and v1 were adjusted to accomodate the old(but new because forgotten) legacy behavior in legacy.go. Without this, old signatures would not work --- .github/config/golangci.yaml | 22 +++ .github/config/wordlist.txt | 3 +- api/ocm/compdesc/norm_test.go | 70 ++++++++- api/ocm/compdesc/normalization.go | 3 + api/ocm/compdesc/normalizations/init.go | 1 + .../compdesc/normalizations/jsonv1/norm.go | 7 +- .../compdesc/normalizations/jsonv2/norm.go | 3 + .../compdesc/normalizations/jsonv3/norm.go | 31 ++++ .../compdesc/normalizations/legacy/legacy.go | 57 +++++++ api/ocm/selectors/accessors/accessors.go | 1 + .../common/options/hashoption/option.go | 19 ++- .../common/options/signoption/option.go | 4 +- .../ocmcmds/components/hash/cmd_test.go | 146 ++++++++---------- .../ocmcmds/components/sign/cmd_test.go | 85 +++++++--- docs/reference/ocm_hash_componentversions.md | 14 +- docs/reference/ocm_logging.md | 1 + docs/reference/ocm_sign_componentversions.md | 5 +- 17 files changed, 345 insertions(+), 127 deletions(-) create mode 100644 api/ocm/compdesc/normalizations/jsonv3/norm.go create mode 100644 api/ocm/compdesc/normalizations/legacy/legacy.go diff --git a/.github/config/golangci.yaml b/.github/config/golangci.yaml index 8352fbc2a3..01770b48d1 100644 --- a/.github/config/golangci.yaml +++ b/.github/config/golangci.yaml @@ -173,4 +173,26 @@ issues: - path: ignore/.*\.go linters: - dupword + # Deprecated algorithms and fields for extra identity field defaulting + # TODO: To be removed once v1 + v2 are removed. + - path: "cmds/.*|api/.*" + linters: + - staticcheck + text: "SA1019: jsonv1.Algorithm is deprecated" + - path: "cmds/.*|api/.*" + linters: + - staticcheck + text: "SA1019: compdesc.JsonNormalisationV1 is deprecated" + - path: "cmds/.*|api/.*" + linters: + - staticcheck + text: "SA1019: jsonv2.Algorithm is deprecated" + - path: "cmds/.*|api/.*" + linters: + - staticcheck + text: "SA1019: compdesc.JsonNormalisationV2 is deprecated" + - path: "cmds/.*|api/.*" + linters: + - staticcheck + text: "SA1019: legacy.DefaultingOfVersionIntoExtraIdentity is deprecated" diff --git a/.github/config/wordlist.txt b/.github/config/wordlist.txt index d38b0d4469..55c8a10bd4 100644 --- a/.github/config/wordlist.txt +++ b/.github/config/wordlist.txt @@ -308,4 +308,5 @@ xml yaml yitsushi yml -yyyy \ No newline at end of file +yyyy +jsonNormalisation \ No newline at end of file diff --git a/api/ocm/compdesc/norm_test.go b/api/ocm/compdesc/norm_test.go index 8d3991f076..786f35ea77 100644 --- a/api/ocm/compdesc/norm_test.go +++ b/api/ocm/compdesc/norm_test.go @@ -171,7 +171,7 @@ var _ = Describe("Normalization", func() { Expect(err).To(Succeed()) }) - It("hashes first", func() { + It("normalizes v1", func() { n, err := compdesc.Normalize(cd1, compdesc.JsonNormalisationV1) Expect(err).To(Succeed()) Expect(string(n)).To(StringEqualTrimmedWithContext("[{\"component\":[{\"componentReferences\":[]},{\"name\":\"github.com/vasu1124/introspect\"},{\"provider\":\"internal\"},{\"resources\":[[{\"digest\":[{\"hashAlgorithm\":\"SHA-256\"},{\"normalisationAlgorithm\":\"ociArtifactDigest/v1\"},{\"value\":\"6a1c7637a528ab5957ab60edf73b5298a0a03de02a96be0313ee89b22544840c\"}]},{\"extraIdentity\":null},{\"labels\":[[{\"name\":\"label2\"},{\"signing\":true},{\"value\":\"bar\"}]]},{\"name\":\"introspect-image\"},{\"relation\":\"local\"},{\"type\":\"ociImage\"},{\"version\":\"1.0.0\"}],[{\"digest\":[{\"hashAlgorithm\":\"SHA-256\"},{\"normalisationAlgorithm\":\"genericBlobDigest/v1\"},{\"value\":\"d1187ac17793b2f5fa26175c21cabb6ce388871ae989e16ff9a38bd6b32507bf\"}]},{\"extraIdentity\":null},{\"name\":\"introspect-blueprint\"},{\"relation\":\"local\"},{\"type\":\"landscaper.gardener.cloud/blueprint\"},{\"version\":\"1.0.0\"}],[{\"digest\":[{\"hashAlgorithm\":\"SHA-256\"},{\"normalisationAlgorithm\":\"ociArtifactDigest/v1\"},{\"value\":\"6229be2be7e328f74ba595d93b814b590b1aa262a1b85e49cc1492795a9e564c\"}]},{\"extraIdentity\":null},{\"name\":\"introspect-helm\"},{\"relation\":\"external\"},{\"type\":\"helm\"},{\"version\":\"0.1.0\"}]]},{\"version\":\"1.0.0\"}]},{\"meta\":[{\"schemaVersion\":\"v2\"}]}]")) @@ -180,13 +180,22 @@ var _ = Describe("Normalization", func() { Expect(o).To(Equal(n)) }) - It("hashes v2", func() { + It("normalizes v2", func() { n, err := compdesc.Normalize(cd1, compdesc.JsonNormalisationV2) Expect(err).To(Succeed()) Expect(string(n)).To(Equal(`{"component":{"componentReferences":[],"name":"github.com/vasu1124/introspect","provider":{"name":"internal"},"resources":[{"digest":{"hashAlgorithm":"SHA-256","normalisationAlgorithm":"ociArtifactDigest/v1","value":"6a1c7637a528ab5957ab60edf73b5298a0a03de02a96be0313ee89b22544840c"},"labels":[{"name":"label2","signing":true,"value":"bar"}],"name":"introspect-image","relation":"local","type":"ociImage","version":"1.0.0"},{"digest":{"hashAlgorithm":"SHA-256","normalisationAlgorithm":"genericBlobDigest/v1","value":"d1187ac17793b2f5fa26175c21cabb6ce388871ae989e16ff9a38bd6b32507bf"},"name":"introspect-blueprint","relation":"local","type":"landscaper.gardener.cloud/blueprint","version":"1.0.0"},{"digest":{"hashAlgorithm":"SHA-256","normalisationAlgorithm":"ociArtifactDigest/v1","value":"6229be2be7e328f74ba595d93b814b590b1aa262a1b85e49cc1492795a9e564c"},"name":"introspect-helm","relation":"external","type":"helm","version":"0.1.0"}],"sources":[{"name":"introspect","type":"git","version":"1.0.0"}],"version":"1.0.0"}}`)) }) - It("hashes v1 with none access", func() { + It("normalises v3", func() { + n, err := compdesc.Normalize(cd1, compdesc.JsonNormalisationV3) + Expect(err).To(Succeed()) + Expect(string(n)).To(Equal(`{"component":{"componentReferences":[],"name":"github.com/vasu1124/introspect","provider":{"name":"internal"},"resources":[{"digest":{"hashAlgorithm":"SHA-256","normalisationAlgorithm":"ociArtifactDigest/v1","value":"6a1c7637a528ab5957ab60edf73b5298a0a03de02a96be0313ee89b22544840c"},"labels":[{"name":"label2","signing":true,"value":"bar"}],"name":"introspect-image","relation":"local","type":"ociImage","version":"1.0.0"},{"digest":{"hashAlgorithm":"SHA-256","normalisationAlgorithm":"genericBlobDigest/v1","value":"d1187ac17793b2f5fa26175c21cabb6ce388871ae989e16ff9a38bd6b32507bf"},"name":"introspect-blueprint","relation":"local","type":"landscaper.gardener.cloud/blueprint","version":"1.0.0"},{"digest":{"hashAlgorithm":"SHA-256","normalisationAlgorithm":"ociArtifactDigest/v1","value":"6229be2be7e328f74ba595d93b814b590b1aa262a1b85e49cc1492795a9e564c"},"name":"introspect-helm","relation":"external","type":"helm","version":"0.1.0"}],"sources":[{"name":"introspect","type":"git","version":"1.0.0"}],"version":"1.0.0"}}`)) + o, err := compdesc.Normalize(cd1, compdesc.JsonNormalisationV2) + Expect(err).To(Succeed()) + Expect(o).To(Equal(n)) + }) + + It("normalizes v1 with none access", func() { cd1.Resources = append(cd1.Resources, compdesc.Resource{ ResourceMeta: compdesc.ResourceMeta{ ElementMeta: compdesc.ElementMeta{ @@ -208,7 +217,7 @@ var _ = Describe("Normalization", func() { Expect(string(n)).To(StringEqualWithContext(`[{"component":[{"componentReferences":[]},{"name":"github.com/vasu1124/introspect"},{"provider":"internal"},{"resources":[[{"digest":[{"hashAlgorithm":"SHA-256"},{"normalisationAlgorithm":"ociArtifactDigest/v1"},{"value":"6a1c7637a528ab5957ab60edf73b5298a0a03de02a96be0313ee89b22544840c"}]},{"extraIdentity":null},{"labels":[[{"name":"label2"},{"signing":true},{"value":"bar"}]]},{"name":"introspect-image"},{"relation":"local"},{"type":"ociImage"},{"version":"1.0.0"}],[{"digest":[{"hashAlgorithm":"SHA-256"},{"normalisationAlgorithm":"genericBlobDigest/v1"},{"value":"d1187ac17793b2f5fa26175c21cabb6ce388871ae989e16ff9a38bd6b32507bf"}]},{"extraIdentity":null},{"name":"introspect-blueprint"},{"relation":"local"},{"type":"landscaper.gardener.cloud/blueprint"},{"version":"1.0.0"}],[{"digest":[{"hashAlgorithm":"SHA-256"},{"normalisationAlgorithm":"ociArtifactDigest/v1"},{"value":"6229be2be7e328f74ba595d93b814b590b1aa262a1b85e49cc1492795a9e564c"}]},{"extraIdentity":null},{"name":"introspect-helm"},{"relation":"external"},{"type":"helm"},{"version":"0.1.0"}],[{"extraIdentity":null},{"name":"none"},{"relation":"local"},{"type":"plainText"},{"version":"v1"}]]},{"version":"1.0.0"}]},{"meta":[{"schemaVersion":"v2"}]}]`)) }) - It("hashes v2 with none access", func() { + It("normalizes v2 with none access", func() { cd1.Resources = append(cd1.Resources, compdesc.Resource{ ResourceMeta: compdesc.ResourceMeta{ ElementMeta: compdesc.ElementMeta{ @@ -230,7 +239,7 @@ var _ = Describe("Normalization", func() { Expect(string(n)).To(Equal(`{"component":{"componentReferences":[],"name":"github.com/vasu1124/introspect","provider":{"name":"internal"},"resources":[{"digest":{"hashAlgorithm":"SHA-256","normalisationAlgorithm":"ociArtifactDigest/v1","value":"6a1c7637a528ab5957ab60edf73b5298a0a03de02a96be0313ee89b22544840c"},"labels":[{"name":"label2","signing":true,"value":"bar"}],"name":"introspect-image","relation":"local","type":"ociImage","version":"1.0.0"},{"digest":{"hashAlgorithm":"SHA-256","normalisationAlgorithm":"genericBlobDigest/v1","value":"d1187ac17793b2f5fa26175c21cabb6ce388871ae989e16ff9a38bd6b32507bf"},"name":"introspect-blueprint","relation":"local","type":"landscaper.gardener.cloud/blueprint","version":"1.0.0"},{"digest":{"hashAlgorithm":"SHA-256","normalisationAlgorithm":"ociArtifactDigest/v1","value":"6229be2be7e328f74ba595d93b814b590b1aa262a1b85e49cc1492795a9e564c"},"name":"introspect-helm","relation":"external","type":"helm","version":"0.1.0"},{"name":"none","relation":"local","type":"plainText","version":"v1"}],"sources":[{"name":"introspect","type":"git","version":"1.0.0"}],"version":"1.0.0"}}`)) }) - It("hashes v2 with complex provider", func() { + It("normalizes v2 with complex provider", func() { cd := cd1.Copy() cd.References = nil cd.Resources = nil @@ -248,7 +257,7 @@ var _ = Describe("Normalization", func() { Expect(string(n)).To(Equal(`{"component":{"componentReferences":[],"labels":[{"name":"non-volatile","signing":true,"value":"comp-value2"}],"name":"github.com/vasu1124/introspect","provider":{"labels":[{"name":"non-volatile","signing":true,"value":"prov-value2"}],"name":"internal"},"resources":[],"sources":[],"version":"1.0.0"}}`)) }) - It("hashes v1 with complex provider for CD/v2", func() { + It("normalizes v1 with complex provider for CD/v2", func() { cd := cd1.Copy() cd.References = nil cd.Resources = nil @@ -266,7 +275,7 @@ var _ = Describe("Normalization", func() { Expect(string(n)).To(StringEqualWithContext(`[{"component":[{"componentReferences":[]},{"labels":[[{"name":"non-volatile"},{"signing":true},{"value":"comp-value2"}]]},{"name":"github.com/vasu1124/introspect"},{"provider":[{"labels":[[{"name":"non-volatile"},{"signing":true},{"value":"prov-value2"}]]},{"name":"internal"}]},{"resources":[]},{"version":"1.0.0"}]},{"meta":[{"schemaVersion":"v2"}]}]`)) }) - It("hashes v1 with complex provider for CD/v3", func() { + It("normalizes v1 with complex provider for CD/v3", func() { cd := cd1.Copy() cd.Metadata.ConfiguredVersion = v3alpha1.SchemaVersion cd.References = nil @@ -284,4 +293,51 @@ var _ = Describe("Normalization", func() { Expect(string(n)).To(StringEqualWithContext(`[{"apiVersion":"ocm.software/v3alpha1"},{"kind":"ComponentVersion"},{"metadata":[{"labels":[[{"name":"non-volatile"},{"signing":true},{"value":"comp-value2"}]]},{"name":"github.com/vasu1124/introspect"},{"provider":[{"labels":[[{"name":"volatile"},{"value":"prov-value1"}],[{"name":"non-volatile"},{"signing":true},{"value":"prov-value2"}]]},{"name":"internal"}]},{"version":"1.0.0"}]},{"spec":[]}]`)) }) + + Context("normalization and legacy extra identity defaulting", func() { + var cd *compdesc.ComponentDescriptor + BeforeEach(func() { + cd = Must(compdesc.Decode([]byte(` + component: + version: 1.0.0 + componentReferences: [] + name: ocm.software/duplicate-resource/test + provider: internal + repositoryContexts: [] + resources: + - name: image + relation: local + type: ociImage + version: 1.0.0 + access: + imageReference: ghcr.io/bla:1.0.0 + type: ociRegistry + - name: image + relation: local + type: ociImage + version: 2.0.0 + access: + imageReference: ghcr.io/bla:2.0.0 + type: ociRegistry + sources: [] + meta: + schemaVersion: v2 +`))) + }) + It("normalizes v1 with extra identity defaulting", func() { + n := Must(compdesc.Normalize(cd, compdesc.JsonNormalisationV1)) + Expect(string(n)).To(StringEqualTrimmedWithContext("[{\"component\":[{\"componentReferences\":[]},{\"name\":\"ocm.software/duplicate-resource/test\"},{\"provider\":\"internal\"},{\"resources\":[[{\"extraIdentity\":[{\"version\":\"1.0.0\"}]},{\"name\":\"image\"},{\"relation\":\"local\"},{\"type\":\"ociImage\"},{\"version\":\"1.0.0\"}],[{\"extraIdentity\":null},{\"name\":\"image\"},{\"relation\":\"local\"},{\"type\":\"ociImage\"},{\"version\":\"2.0.0\"}]]},{\"version\":\"1.0.0\"}]},{\"meta\":[{\"schemaVersion\":\"v2\"}]}]")) + Expect(string(n)).To(ContainSubstring("\"extraIdentity\":[{\"version\":\"1.0.0\"}]"), "extra identity should have been defaulted, see api/ocm/compdesc/normalizations/legacy/DefaultingOfVersionIntoExtraIdentity") + }) + It("normalizes v2 with extra identity defaulting", func() { + n := Must(compdesc.Normalize(cd, compdesc.JsonNormalisationV2)) + Expect(string(n)).To(StringEqualTrimmedWithContext("{\"component\":{\"componentReferences\":[],\"name\":\"ocm.software/duplicate-resource/test\",\"provider\":{\"name\":\"internal\"},\"resources\":[{\"extraIdentity\":{\"version\":\"1.0.0\"},\"name\":\"image\",\"relation\":\"local\",\"type\":\"ociImage\",\"version\":\"1.0.0\"},{\"name\":\"image\",\"relation\":\"local\",\"type\":\"ociImage\",\"version\":\"2.0.0\"}],\"sources\":[],\"version\":\"1.0.0\"}}")) + Expect(string(n)).To(ContainSubstring("{\"extraIdentity\":{\"version\":\"1.0.0\"}"), "extra identity should have been defaulted, see api/ocm/compdesc/normalizations/legacy/DefaultingOfVersionIntoExtraIdentity") + }) + It("normalizes v3 without extra identity defaulting", func() { + n := Must(compdesc.Normalize(cd, compdesc.JsonNormalisationV3)) + Expect(string(n)).To(StringEqualTrimmedWithContext("{\"component\":{\"componentReferences\":[],\"name\":\"ocm.software/duplicate-resource/test\",\"provider\":{\"name\":\"internal\"},\"resources\":[{\"name\":\"image\",\"relation\":\"local\",\"type\":\"ociImage\",\"version\":\"1.0.0\"},{\"name\":\"image\",\"relation\":\"local\",\"type\":\"ociImage\",\"version\":\"2.0.0\"}],\"sources\":[],\"version\":\"1.0.0\"}}")) + Expect(string(n)).ToNot(ContainSubstring("{\"extraIdentity\":{\"version\":\"1.0.0\"}"), "extra identity should not have been defaulted") + }) + }) }) diff --git a/api/ocm/compdesc/normalization.go b/api/ocm/compdesc/normalization.go index 5084f1ca89..6e7687d5cb 100644 --- a/api/ocm/compdesc/normalization.go +++ b/api/ocm/compdesc/normalization.go @@ -11,8 +11,11 @@ import ( type NormalisationAlgorithm = string const ( + // Deprecated: use JsonNormalisationV3 instead JsonNormalisationV1 NormalisationAlgorithm = "jsonNormalisation/v1" + // Deprecated: use JsonNormalisationV3 instead JsonNormalisationV2 NormalisationAlgorithm = "jsonNormalisation/v2" + JsonNormalisationV3 NormalisationAlgorithm = "jsonNormalisation/v3" ) type Normalization interface { diff --git a/api/ocm/compdesc/normalizations/init.go b/api/ocm/compdesc/normalizations/init.go index 96dab531de..6b4eeefa3e 100644 --- a/api/ocm/compdesc/normalizations/init.go +++ b/api/ocm/compdesc/normalizations/init.go @@ -3,4 +3,5 @@ package normalizations import ( _ "ocm.software/ocm/api/ocm/compdesc/normalizations/jsonv1" _ "ocm.software/ocm/api/ocm/compdesc/normalizations/jsonv2" + _ "ocm.software/ocm/api/ocm/compdesc/normalizations/jsonv3" ) diff --git a/api/ocm/compdesc/normalizations/jsonv1/norm.go b/api/ocm/compdesc/normalizations/jsonv1/norm.go index 5a6f9bf141..ae24494fa2 100644 --- a/api/ocm/compdesc/normalizations/jsonv1/norm.go +++ b/api/ocm/compdesc/normalizations/jsonv1/norm.go @@ -8,9 +8,11 @@ import ( "github.com/mandelsoft/goutils/errors" "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/compdesc/normalizations/legacy" "ocm.software/ocm/api/utils/errkind" ) +// Deprecated: use compdesc.JsonNormalisationV3 instead const Algorithm = compdesc.JsonNormalisationV1 func init() { @@ -20,11 +22,10 @@ func init() { type normalization struct{} func (m normalization) Normalize(cd *compdesc.ComponentDescriptor) ([]byte, error) { + legacy.DefaultingOfVersionIntoExtraIdentity(cd) cv := compdesc.DefaultSchemes[cd.SchemaVersion()] if cv == nil { - if cv == nil { - return nil, errors.ErrNotSupported(errkind.KIND_SCHEMAVERSION, cd.SchemaVersion()) - } + return nil, errors.ErrNotSupported(errkind.KIND_SCHEMAVERSION, cd.SchemaVersion()) } v, err := cv.ConvertFrom(cd) if err != nil { diff --git a/api/ocm/compdesc/normalizations/jsonv2/norm.go b/api/ocm/compdesc/normalizations/jsonv2/norm.go index 1fcb3a98e8..9fb7b3f284 100644 --- a/api/ocm/compdesc/normalizations/jsonv2/norm.go +++ b/api/ocm/compdesc/normalizations/jsonv2/norm.go @@ -10,11 +10,13 @@ package jsonv2 import ( "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/compdesc/normalizations/legacy" "ocm.software/ocm/api/ocm/compdesc/normalizations/rules" "ocm.software/ocm/api/tech/signing" "ocm.software/ocm/api/tech/signing/norm/jcs" ) +// Deprecated: use compdesc.JsonNormalisationV3 instead const Algorithm = compdesc.JsonNormalisationV2 func init() { @@ -24,6 +26,7 @@ func init() { type normalization struct{} func (m normalization) Normalize(cd *compdesc.ComponentDescriptor) ([]byte, error) { + legacy.DefaultingOfVersionIntoExtraIdentity(cd) data, err := signing.Normalize(jcs.Type, cd, CDExcludes) return data, err } diff --git a/api/ocm/compdesc/normalizations/jsonv3/norm.go b/api/ocm/compdesc/normalizations/jsonv3/norm.go new file mode 100644 index 0000000000..f8092920b0 --- /dev/null +++ b/api/ocm/compdesc/normalizations/jsonv3/norm.go @@ -0,0 +1,31 @@ +// Package jsonv3 provides a normalization which is completely based on the +// abstract (internal) version of the component descriptor and is therefore +// agnostic of the final serialization format. Signatures using this algorithm +// can be transferred among different schema versions, as long as is able to +// handle the complete information using for the normalization. +// jsonv2 is the predecessor of this version but had internal defaulting logic +// that is no longer included as part of this normalization. Thus v3 should be preferred over v2. +// Note that between v2 and v3 differences can occur mainly if the "extra identity" field is not unique, +// in which case the v2 normalization opinionated on how to differentiate these items. This no longer +// happens in v3, meaning the component descriptor is normalized as is. +package jsonv3 + +import ( + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/compdesc/normalizations/jsonv2" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/norm/jcs" +) + +const Algorithm = compdesc.JsonNormalisationV3 + +func init() { + compdesc.Normalizations.Register(Algorithm, normalization{}) +} + +type normalization struct{} + +func (m normalization) Normalize(cd *compdesc.ComponentDescriptor) ([]byte, error) { + data, err := signing.Normalize(jcs.Type, cd, jsonv2.CDExcludes) + return data, err +} diff --git a/api/ocm/compdesc/normalizations/legacy/legacy.go b/api/ocm/compdesc/normalizations/legacy/legacy.go new file mode 100644 index 0000000000..e05c982035 --- /dev/null +++ b/api/ocm/compdesc/normalizations/legacy/legacy.go @@ -0,0 +1,57 @@ +package legacy + +import ( + "fmt" + + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/selectors/accessors" + "ocm.software/ocm/api/utils/logging" +) + +var ( + REALM = logging.DefineSubRealm("component descriptor legacy normalization defaulting", "compdesc", "normalizations", "legacy") + Logger = logging.DynamicLogger(REALM) +) + +// DefaultingOfVersionIntoExtraIdentity normalizes the extra identity of the resources. +// It sets the version of the resource, reference or source as extra identity field if the combination of name+extra identity +// is the same for multiple items. However, the last item in the list will not be updated as it is unique wihout this. +// +// TODO: To be removed once v1 + v2 are removed. +// +// Deprecated: This is a legacy normalization and should only be used as part of JsonNormalisationV1 and JsonNormalisationV2 +// for backwards compatibility of normalization (for example used for signatures). It was needed because the original +// defaulting was made part of the normalization by accident and is now no longer included by default due to +// https://github.com/open-component-model/ocm/pull/1026 +func DefaultingOfVersionIntoExtraIdentity(cd *compdesc.ComponentDescriptor) { + resources := make([]accessors.ElementMeta, len(cd.Resources)) + for i := range cd.Resources { + resources[i] = &cd.Resources[i] + } + defaultingOfVersionIntoExtraIdentity(resources) +} + +func defaultingOfVersionIntoExtraIdentity(meta []accessors.ElementMeta) { + for i := range meta { + for j := range meta { + // don't match with itself and only match with the same name + if meta[j].GetName() != meta[i].GetName() || i == j { + continue + } + + eid := meta[i].GetExtraIdentity() + // if the extra identity is not the same, then there is not a clash + if !meta[j].GetExtraIdentity().Equals(eid) { + continue + } + + eid.Set(compdesc.SystemIdentityVersion, meta[i].GetVersion()) + meta[i].GetMeta().SetExtraIdentity(eid) + + Logger.Warn(fmt.Sprintf("resource identity duplication was normalized for backwards compatibility, "+ + "to avoid this either specify a unique extra identity per item or switch to %s", compdesc.JsonNormalisationV3), + "name", meta[i].GetName(), "index", i, "extra identity", meta[i].GetExtraIdentity()) + break + } + } +} diff --git a/api/ocm/selectors/accessors/accessors.go b/api/ocm/selectors/accessors/accessors.go index fa8b776487..9fa3c8e00a 100644 --- a/api/ocm/selectors/accessors/accessors.go +++ b/api/ocm/selectors/accessors/accessors.go @@ -27,6 +27,7 @@ type ElementMeta interface { GetMeta() ElementMeta // ElementMeta is again a Meta provider SetLabels(labels []v1.Label) + SetExtraIdentity(identity v1.Identity) } // ElementMetaProvider just provides access to element meta data diff --git a/cmds/ocm/commands/ocmcmds/common/options/hashoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/hashoption/option.go index 4eb900f449..04a755e25e 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/hashoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/hashoption/option.go @@ -7,6 +7,8 @@ import ( clictx "ocm.software/ocm/api/cli" "ocm.software/ocm/api/ocm/compdesc" "ocm.software/ocm/api/ocm/compdesc/normalizations/jsonv1" + "ocm.software/ocm/api/ocm/compdesc/normalizations/jsonv2" + "ocm.software/ocm/api/ocm/compdesc/normalizations/jsonv3" "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" ocmsign "ocm.software/ocm/api/ocm/tools/signing" "ocm.software/ocm/api/tech/signing" @@ -34,13 +36,13 @@ type Option struct { } func (o *Option) AddFlags(fs *pflag.FlagSet) { - fs.StringVarP(&o.NormAlgorithm, "normalization", "N", jsonv1.Algorithm, "normalization algorithm") + fs.StringVarP(&o.NormAlgorithm, "normalization", "N", jsonv3.Algorithm, "normalization algorithm") fs.StringVarP(&o.hashAlgorithm, "hash", "H", sha256.Algorithm, "hash algorithm") } func (o *Option) Configure(ctx clictx.Context) error { if o.NormAlgorithm == "" { - o.NormAlgorithm = jsonv1.Algorithm + o.NormAlgorithm = jsonv3.Algorithm } if o.hashAlgorithm == "" { o.hashAlgorithm = sha256.Algorithm @@ -59,7 +61,18 @@ func (o *Option) Configure(ctx clictx.Context) error { func (o *Option) Usage() string { s := ` The following normalization modes are supported with option --normalization: -` + listformat.FormatList(jsonv1.Algorithm, compdesc.Normalizations.Names()...) +` + listformat.FormatList(jsonv3.Algorithm, compdesc.Normalizations.Names()...) + + s += ` + +Note that the normalization algorithm is important to be equivalent when used for signing and verification, otherwise +the verification can fail. Please always migrate to the latest normalization algorithm whenever possible. +New signature algorithms can be used as soon as they are available in the component version after signing it. + +The algorithms ` + jsonv1.Algorithm + ` and ` + jsonv2.Algorithm + ` are deprecated and should not be used anymore. +Please switch to ` + jsonv3.Algorithm + ` as soon as possible. + +` s += ` diff --git a/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go index 14723d097a..a0ac7fdb22 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go @@ -9,7 +9,7 @@ import ( clictx "ocm.software/ocm/api/cli" "ocm.software/ocm/api/ocm/compdesc" - "ocm.software/ocm/api/ocm/compdesc/normalizations/jsonv1" + "ocm.software/ocm/api/ocm/compdesc/normalizations/jsonv3" "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" ocmsign "ocm.software/ocm/api/ocm/tools/signing" "ocm.software/ocm/api/tech/signing" @@ -151,7 +151,7 @@ The following signing types are supported with option --algorithm: s += ` The following normalization modes are supported with option --normalization: -` + listformat.FormatList(jsonv1.Algorithm, compdesc.Normalizations.Names()...) +` + listformat.FormatList(jsonv3.Algorithm, compdesc.Normalizations.Names()...) s += ` diff --git a/cmds/ocm/commands/ocmcmds/components/hash/cmd_test.go b/cmds/ocm/commands/ocmcmds/components/hash/cmd_test.go index f79e2c1c05..6f20c09d0c 100644 --- a/cmds/ocm/commands/ocmcmds/components/hash/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/components/hash/cmd_test.go @@ -9,6 +9,7 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + . "ocm.software/ocm/cmds/ocm/testhelper" "ocm.software/ocm/api/ocm/compdesc" @@ -38,41 +39,47 @@ var _ = Describe("Test Environment", func() { env.Cleanup() }) - It("hash component archive", func() { - env.ComponentArchive(ARCH, accessio.FormatDirectory, COMP, VERSION, func() { - env.Provider(PROVIDER) - }) - - buf := bytes.NewBuffer(nil) - Expect(env.CatchOutput(buf).Execute("hash", "components", ARCH, "-o", "wide")).To(Succeed()) - Expect(buf.String()).To(StringEqualTrimmedWithContext( - ` -COMPONENT VERSION HASH NORMALIZED FORM -test.de/x v1 37f7f500d87f4b0a8765649f7c047db382e272b73e042805131df57279991b2b [{"component":[{"componentReferences":[]},{"name":"test.de/x"},{"provider":"mandelsoft"},{"resources":[]},{"version":"v1"}]},{"meta":[{"schemaVersion":"v2"}]}] -`)) - }) - - It("normalize component archive v1", func() { - env.ComponentArchive(ARCH, accessio.FormatDirectory, COMP, VERSION, func() { - env.Provider(PROVIDER) - }) + DescribeTable("should hash the component archive with specified parameters", + func(normalizationMethod string, expectedOutput string) { + env.ComponentArchive(ARCH, accessio.FormatDirectory, COMP, VERSION, func() { + env.Provider(PROVIDER) + }) - buf := bytes.NewBuffer(nil) - Expect(env.CatchOutput(buf).Execute("hash", "components", ARCH, "-O", "-", "-o", "norm")).To(Succeed()) - Expect(buf.String()).To(Equal(`[{"component":[{"componentReferences":[]},{"name":"test.de/x"},{"provider":"mandelsoft"},{"resources":[]},{"version":"v1"}]},{"meta":[{"schemaVersion":"v2"}]}] -`)) - }) + buf := bytes.NewBuffer(nil) + cmdArgs := []string{"hash", "components", ARCH, "-o", "wide", "--normalization", normalizationMethod} + Expect(env.CatchOutput(buf).Execute(cmdArgs...)).To(Succeed()) + Expect(buf.String()).To(StringEqualTrimmedWithContext(expectedOutput)) + }, - It("normalize component archive v2", func() { + Entry("v1", compdesc.JsonNormalisationV1, + `COMPONENT VERSION HASH NORMALIZED FORM +test.de/x v1 37f7f500d87f4b0a8765649f7c047db382e272b73e042805131df57279991b2b [{"component":[{"componentReferences":[]},{"name":"test.de/x"},{"provider":"mandelsoft"},{"resources":[]},{"version":"v1"}]},{"meta":[{"schemaVersion":"v2"}]}] +`), + Entry("v2", compdesc.JsonNormalisationV2, + `COMPONENT VERSION HASH NORMALIZED FORM +test.de/x v1 33aeb8c46ea4bacbf0bc3ac42c186c7f5e313584601a93bf861c016d73c9e4f1 {"component":{"componentReferences":[],"name":"test.de/x","provider":{"name":"mandelsoft"},"resources":[],"sources":[],"version":"v1"}} +`), + Entry("v3", compdesc.JsonNormalisationV3, + `COMPONENT VERSION HASH NORMALIZED FORM +test.de/x v1 33aeb8c46ea4bacbf0bc3ac42c186c7f5e313584601a93bf861c016d73c9e4f1 {"component":{"componentReferences":[],"name":"test.de/x","provider":{"name":"mandelsoft"},"resources":[],"sources":[],"version":"v1"}} +`), + ) + + DescribeTable("normalize component archive", func(normalizationMethod string, expectedOutput string) { env.ComponentArchive(ARCH, accessio.FormatDirectory, COMP, VERSION, func() { env.Provider(PROVIDER) }) buf := bytes.NewBuffer(nil) - Expect(env.CatchOutput(buf).Execute("hash", "components", ARCH, "-N", "jsonNormalisation/v2", "-o", "norm")).To(Succeed()) - Expect(buf.String()).To(StringEqualTrimmedWithContext(`{"component":{"componentReferences":[],"name":"test.de/x","provider":{"name":"mandelsoft"},"resources":[],"sources":[],"version":"v1"}} + Expect(env.CatchOutput(buf).Execute("hash", "components", ARCH, "-O", "-", "-o", "norm", "-N", normalizationMethod)).To(Succeed()) + Expect(buf.String()).To(Equal(expectedOutput)) + }, + Entry("v1", compdesc.JsonNormalisationV1, `[{"component":[{"componentReferences":[]},{"name":"test.de/x"},{"provider":"mandelsoft"},{"resources":[]},{"version":"v1"}]},{"meta":[{"schemaVersion":"v2"}]}] +`), + Entry("v2", compdesc.JsonNormalisationV2, `{"component":{"componentReferences":[],"name":"test.de/x","provider":{"name":"mandelsoft"},"resources":[],"sources":[],"version":"v1"}} +`), + Entry("v3", compdesc.JsonNormalisationV3, `{"component":{"componentReferences":[],"name":"test.de/x","provider":{"name":"mandelsoft"},"resources":[],"sources":[],"version":"v1"}} `)) - }) It("check hash", func() { env.ComponentArchive(ARCH, accessio.FormatDirectory, COMP, VERSION, func() { @@ -85,16 +92,16 @@ test.de/x v1 37f7f500d87f4b0a8765649f7c047db382e272b73e042805131df57279991b --- component: test.de/x context: [] -hash: 37f7f500d87f4b0a8765649f7c047db382e272b73e042805131df57279991b2b -normalized: '[{"component":[{"componentReferences":[]},{"name":"test.de/x"},{"provider":"mandelsoft"},{"resources":[]},{"version":"v1"}]},{"meta":[{"schemaVersion":"v2"}]}]' +hash: 33aeb8c46ea4bacbf0bc3ac42c186c7f5e313584601a93bf861c016d73c9e4f1 +normalized: '{"component":{"componentReferences":[],"name":"test.de/x","provider":{"name":"mandelsoft"},"resources":[],"sources":[],"version":"v1"}}' version: v1 `)) - h := sha256.Sum256([]byte(`[{"component":[{"componentReferences":[]},{"name":"test.de/x"},{"provider":"mandelsoft"},{"resources":[]},{"version":"v1"}]},{"meta":[{"schemaVersion":"v2"}]}]`)) - Expect(hex.EncodeToString(h[:])).To(Equal("37f7f500d87f4b0a8765649f7c047db382e272b73e042805131df57279991b2b")) + h := sha256.Sum256([]byte(`{"component":{"componentReferences":[],"name":"test.de/x","provider":{"name":"mandelsoft"},"resources":[],"sources":[],"version":"v1"}}`)) + Expect(hex.EncodeToString(h[:])).To(Equal("33aeb8c46ea4bacbf0bc3ac42c186c7f5e313584601a93bf861c016d73c9e4f1")) }) - It("hash component archive with resources", func() { + DescribeTable("hash component archive with resources", func(normalizationMethod string, expectedOutput string) { env.ComponentArchive(ARCH, accessio.FormatDirectory, COMP, VERSION, func() { env.Provider(PROVIDER) env.Resource("test", VERSION, resourcetypes.PLAIN_TEXT, metav1.LocalRelation, func() { @@ -103,34 +110,27 @@ version: v1 }) buf := bytes.NewBuffer(nil) - Expect(env.CatchOutput(buf).Execute("hash", "components", ARCH, "-o", "wide")).To(Succeed()) - Expect(buf.String()).To(StringEqualTrimmedWithContext( - ` + Expect(env.CatchOutput(buf).Execute("hash", "components", ARCH, "-o", "wide", "-N", normalizationMethod)).To(Succeed()) + Expect(buf.String()).To(StringEqualTrimmedWithContext(expectedOutput)) + }, + Entry("v1", compdesc.JsonNormalisationV1, ` COMPONENT : test.de/x VERSION : v1 HASH : 9d8fc24cf27d1092f58098286d9f63c6824c2daf739c19789f64c062d1f30cc5 NORMALIZED FORM: [{"component":[{"componentReferences":[]},{"name":"test.de/x"},{"provider":"mandelsoft"},{"resources":[[{"digest":[{"hashAlgorithm":"SHA-256"},{"normalisationAlgorithm":"genericBlobDigest/v1"},{"value":"810ff2fb242a5dee4220f2cb0e6a519891fb67f2f828a6cab4ef8894633b1f50"}]},{"extraIdentity":null},{"name":"test"},{"relation":"local"},{"type":"plainText"},{"version":"v1"}]]},{"version":"v1"}]},{"meta":[{"schemaVersion":"v2"}]}] ----`)) - }) - - It("hash component archive with resources", func() { - env.ComponentArchive(ARCH, accessio.FormatDirectory, COMP, VERSION, func() { - env.Provider(PROVIDER) - env.Resource("test", VERSION, resourcetypes.PLAIN_TEXT, metav1.LocalRelation, func() { - env.BlobStringData(mime.MIME_TEXT, "testdata") - }) - }) - - buf := bytes.NewBuffer(nil) - Expect(env.CatchOutput(buf).Execute("hash", "components", ARCH, "--actual", "-o", "wide")).To(Succeed()) - Expect(buf.String()).To(StringEqualTrimmedWithContext( - ` +---`), + Entry("v2", compdesc.JsonNormalisationV2, ` COMPONENT : test.de/x VERSION : v1 -HASH : 9d8fc24cf27d1092f58098286d9f63c6824c2daf739c19789f64c062d1f30cc5 -NORMALIZED FORM: [{"component":[{"componentReferences":[]},{"name":"test.de/x"},{"provider":"mandelsoft"},{"resources":[[{"digest":[{"hashAlgorithm":"SHA-256"},{"normalisationAlgorithm":"genericBlobDigest/v1"},{"value":"810ff2fb242a5dee4220f2cb0e6a519891fb67f2f828a6cab4ef8894633b1f50"}]},{"extraIdentity":null},{"name":"test"},{"relation":"local"},{"type":"plainText"},{"version":"v1"}]]},{"version":"v1"}]},{"meta":[{"schemaVersion":"v2"}]}] +HASH : 6e8e9eb0af1c4c0b9dcc4161168b3f0ad913bc85e4234688dd6d4b283fe4b956 +NORMALIZED FORM: {"component":{"componentReferences":[],"name":"test.de/x","provider":{"name":"mandelsoft"},"resources":[{"digest":{"hashAlgorithm":"SHA-256","normalisationAlgorithm":"genericBlobDigest/v1","value":"810ff2fb242a5dee4220f2cb0e6a519891fb67f2f828a6cab4ef8894633b1f50"},"name":"test","relation":"local","type":"plainText","version":"v1"}],"sources":[],"version":"v1"}} +---`), + Entry("v3", compdesc.JsonNormalisationV3, ` +COMPONENT : test.de/x +VERSION : v1 +HASH : 6e8e9eb0af1c4c0b9dcc4161168b3f0ad913bc85e4234688dd6d4b283fe4b956 +NORMALIZED FORM: {"component":{"componentReferences":[],"name":"test.de/x","provider":{"name":"mandelsoft"},"resources":[{"digest":{"hashAlgorithm":"SHA-256","normalisationAlgorithm":"genericBlobDigest/v1","value":"810ff2fb242a5dee4220f2cb0e6a519891fb67f2f828a6cab4ef8894633b1f50"},"name":"test","relation":"local","type":"plainText","version":"v1"}],"sources":[],"version":"v1"}} ---`)) - }) It("hash component archive with v2", func() { env.ComponentArchive(ARCH, accessio.FormatDirectory, COMP, VERSION, func() { @@ -151,7 +151,7 @@ NORMALIZED FORM: {"component":{"componentReferences":[],"name":"test.de/x","prov ---`)) }) - It("hash component recursively", func() { + It("hash partial component archive recursively", func() { env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { env.ComponentVersion(COMP2, VERSION, func() { env.Provider(PROVIDER) @@ -166,32 +166,12 @@ NORMALIZED FORM: {"component":{"componentReferences":[],"name":"test.de/x","prov Expect(env.CatchOutput(buf).Execute("hash", "components", "-r", ARCH+"//test.de/x:v1")).To(Succeed()) Expect(buf.String()).To(StringEqualTrimmedWithContext(` REFERENCEPATH COMPONENT VERSION HASH IDENTITY - test.de/x v1 b74cee6c6b8215f470efd0e3c49618bb98610fc80de36a2e121d0550650b9cdc -test.de/x:v1 test.de/y v1 e60c791a20091abcf8d35742a134b3a99ce811d874fd721870b28ea90ef5ad2a "name"="ref" -`)) - }) - - It("hash component recursively", func() { - env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { - env.ComponentVersion(COMP2, VERSION, func() { - env.Provider(PROVIDER) - }) - env.ComponentVersion(COMP, VERSION, func() { - env.Provider(PROVIDER) - env.Reference("ref", COMP2, VERSION) - }) - }) - - buf := bytes.NewBuffer(nil) - Expect(env.CatchOutput(buf).Execute("hash", "components", "-r", "--repo", ARCH, "test.de/x:v1")).To(Succeed()) - Expect(buf.String()).To(StringEqualTrimmedWithContext(` -REFERENCEPATH COMPONENT VERSION HASH IDENTITY - test.de/x v1 b74cee6c6b8215f470efd0e3c49618bb98610fc80de36a2e121d0550650b9cdc -test.de/x:v1 test.de/y v1 e60c791a20091abcf8d35742a134b3a99ce811d874fd721870b28ea90ef5ad2a "name"="ref" + test.de/x v1 4ca827281d94cdfee77e7ab2d89164f5e0d38890d12f035c2cbe19d72851fb17 +test.de/x:v1 test.de/y v1 bd420aea257660f444b39165cee42b905e48165021781e2468dcf8e1cbc0151d "name"="ref" `)) }) - It("hash components recursively", func() { + It("hash component archive recursively", func() { env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { env.ComponentVersion(COMP2, VERSION, func() { env.Provider(PROVIDER) @@ -206,9 +186,9 @@ test.de/x:v1 test.de/y v1 e60c791a20091abcf8d35742a134b3a99ce811d874fd7218 Expect(env.CatchOutput(buf).Execute("hash", "components", "-r", ARCH)).To(Succeed()) Expect(buf.String()).To(StringEqualTrimmedWithContext(` REFERENCEPATH COMPONENT VERSION HASH IDENTITY - test.de/x v1 b74cee6c6b8215f470efd0e3c49618bb98610fc80de36a2e121d0550650b9cdc -test.de/x:v1 test.de/y v1 e60c791a20091abcf8d35742a134b3a99ce811d874fd721870b28ea90ef5ad2a "name"="ref" - test.de/y v1 e60c791a20091abcf8d35742a134b3a99ce811d874fd721870b28ea90ef5ad2a + test.de/x v1 4ca827281d94cdfee77e7ab2d89164f5e0d38890d12f035c2cbe19d72851fb17 +test.de/x:v1 test.de/y v1 bd420aea257660f444b39165cee42b905e48165021781e2468dcf8e1cbc0151d "name"="ref" + test.de/y v1 bd420aea257660f444b39165cee42b905e48165021781e2468dcf8e1cbc0151d `)) }) @@ -227,8 +207,8 @@ test.de/x:v1 test.de/y v1 e60c791a20091abcf8d35742a134b3a99ce811d874fd7218 Expect(env.CatchOutput(buf).Execute("hash", "components", "-r", "--repo", ARCH, "-U", "test.de/x:v1")).To(Succeed()) Expect(buf.String()).To(StringEqualTrimmedWithContext(` REFERENCEPATH COMPONENT VERSION HASH IDENTITY - test.de/x v1 b74cee6c6b8215f470efd0e3c49618bb98610fc80de36a2e121d0550650b9cdc -test.de/x:v1 test.de/y v1 e60c791a20091abcf8d35742a134b3a99ce811d874fd721870b28ea90ef5ad2a "name"="ref" + test.de/x v1 4ca827281d94cdfee77e7ab2d89164f5e0d38890d12f035c2cbe19d72851fb17 +test.de/x:v1 test.de/y v1 bd420aea257660f444b39165cee42b905e48165021781e2468dcf8e1cbc0151d "name"="ref" `)) repo := Must(ctf.Open(env, ctf.ACC_READONLY, ARCH, 0, env)) @@ -247,6 +227,6 @@ test.de/x:v1 test.de/y v1 e60c791a20091abcf8d35742a134b3a99ce811d874fd7218 ref := Must(cv.GetReferenceByIndex(0)) d := ref.GetDigest() Expect(d).NotTo(BeNil()) - Expect(d.Value).To(Equal("e60c791a20091abcf8d35742a134b3a99ce811d874fd721870b28ea90ef5ad2a")) + Expect(d.Value).To(Equal("bd420aea257660f444b39165cee42b905e48165021781e2468dcf8e1cbc0151d")) }) }) diff --git a/cmds/ocm/commands/ocmcmds/components/sign/cmd_test.go b/cmds/ocm/commands/ocmcmds/components/sign/cmd_test.go index 0537457516..7991b3f843 100644 --- a/cmds/ocm/commands/ocmcmds/components/sign/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/components/sign/cmd_test.go @@ -7,7 +7,9 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + . "ocm.software/ocm/api/oci/testhelper" + "ocm.software/ocm/api/ocm/compdesc" . "ocm.software/ocm/api/ocm/testhelper" . "ocm.software/ocm/cmds/ocm/testhelper" @@ -52,19 +54,50 @@ const ( ) const ( - D_COMPONENTA = "01de99400030e8336020059a435cea4e7fe8f21aad4faf619da882134b85569d" - D_COMPONENTB = "5f416ec59629d6af91287e2ba13c6360339b6a0acf624af2abd2a810ce4aefce" + D_COMPONENTA_V1 = "01de99400030e8336020059a435cea4e7fe8f21aad4faf619da882134b85569d" + D_COMPONENTB_V1 = "5f416ec59629d6af91287e2ba13c6360339b6a0acf624af2abd2a810ce4aefce" ) const VERIFIED_FILE = "verified.yaml" -var substitutions = Substitutions{ - "test": D_COMPONENTA, - "r0": D_TESTDATA, - "r1": DS_OCIMANIFEST1.Value, - "r2": DS_OCIMANIFEST2.Value, - "ref": D_COMPONENTB, - "rb0": D_OTHERDATA, +var substitutionsV1 = Substitutions{ + "test": D_COMPONENTA_V1, + "r0": D_TESTDATA, + "r1": DS_OCIMANIFEST1.Value, + "r2": DS_OCIMANIFEST2.Value, + "ref": D_COMPONENTB_V1, + "rb0": D_OTHERDATA, + "normAlgo": compdesc.JsonNormalisationV1, +} + +const ( + D_COMPONENTA_V2 = "10ac0b3a850e1f1becf56d5d45e9742fa0a91103d25ba93cc3a509f68797e90f" + D_COMPONENTB_V2 = "1ae74420ef29436ad75133d81bceb59fa8ef1e2ce083a45b5f4baaec641a4266" +) + +var substitutionsV2 = Substitutions{ + "test": D_COMPONENTA_V2, + "r0": D_TESTDATA, + "r1": DS_OCIMANIFEST1.Value, + "r2": DS_OCIMANIFEST2.Value, + "ref": D_COMPONENTB_V2, + "rb0": D_OTHERDATA, + "normAlgo": compdesc.JsonNormalisationV2, +} + +const ( + D_COMPONENTA_V3 = D_COMPONENTA_V2 + D_COMPONENTB_V3 = "766f26b09237f9647714e85fac914f115d0b4c3277b01ec00cfeb3b50a68cde9" +) + +var substitutionsV3 = Substitutions{ + "test": D_COMPONENTA_V3, + "r0": D_TESTDATA, + "r1": DS_OCIMANIFEST1.Value, + "r2": DS_OCIMANIFEST2.Value, + "ref": D_COMPONENTB_V3, + "rb0": D_OTHERDATA, + "normAlgo": compdesc.JsonNormalisationV3, } var _ = Describe("access method", func() { @@ -116,7 +149,7 @@ var _ = Describe("access method", func() { prepareEnv(env, ARCH, "") buf := bytes.NewBuffer(nil) - Expect(env.CatchOutput(buf).Execute("sign", "components", "-s", SIGNATURE, "-K", PRIVKEY, "--repo", ARCH, COMPONENTA+":"+VERSION)).To(Succeed()) + Expect(env.CatchOutput(buf).Execute("sign", "components", "-s", SIGNATURE, "-K", PRIVKEY, "--repo", ARCH, COMPONENTA+":"+VERSION, "--normalization", compdesc.JsonNormalisationV1)).To(Succeed()) Expect(buf.String()).To(StringEqualTrimmedWithContext(` applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v1]... @@ -124,7 +157,7 @@ applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v resource 1: "name"="value": digest SHA-256:${r1}[ociArtifactDigest/v1] resource 2: "name"="ref": digest SHA-256:${r2}[ociArtifactDigest/v1] successfully signed github.com/mandelsoft/test:v1 (digest SHA-256:${test})`, - substitutions), + substitutionsV1), ) session := datacontext.NewSession() @@ -136,14 +169,14 @@ successfully signed github.com/mandelsoft/test:v1 (digest SHA-256:${test})`, cv, err := src.LookupComponentVersion(COMPONENTA, VERSION) Expect(err).To(Succeed()) session.AddCloser(cv) - Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(D_COMPONENTA)) + Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(D_COMPONENTA_V1)) }) It("signs transport archive", func() { prepareEnv(env, ARCH, ARCH) buf := bytes.NewBuffer(nil) - Expect(env.CatchOutput(buf).Execute("sign", "components", "-s", SIGNATURE, "-K", PRIVKEY, "--repo", ARCH, COMPONENTB+":"+VERSION)).To(Succeed()) + Expect(env.CatchOutput(buf).Execute("sign", "components", "-s", SIGNATURE, "-K", PRIVKEY, "--repo", ARCH, COMPONENTB+":"+VERSION, "--normalization", compdesc.JsonNormalisationV1)).To(Succeed()) Expect(buf.String()).To(StringEqualTrimmedWithContext(` applying to version "github.com/mandelsoft/ref:v1"[github.com/mandelsoft/ref:v1]... @@ -155,7 +188,7 @@ applying to version "github.com/mandelsoft/ref:v1"[github.com/mandelsoft/ref:v1] reference 0: github.com/mandelsoft/test:v1: digest SHA-256:${test}[jsonNormalisation/v1] resource 0: "name"="otherdata": digest SHA-256:${rb0}[genericBlobDigest/v1] successfully signed github.com/mandelsoft/ref:v1 (digest SHA-256:${ref}) -`, substitutions)) +`, substitutionsV1)) session := datacontext.NewSession() defer session.Close() @@ -166,14 +199,14 @@ successfully signed github.com/mandelsoft/ref:v1 (digest SHA-256:${ref}) cv, err := src.LookupComponentVersion(COMPONENTB, VERSION) Expect(err).To(Succeed()) session.AddCloser(cv) - Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(D_COMPONENTB)) + Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(D_COMPONENTB_V1)) }) It("signs transport archive with --lookup option", func() { prepareEnv(env, ARCH2, ARCH) buf := bytes.NewBuffer(nil) - Expect(env.CatchOutput(buf).Execute("sign", "components", "--lookup", ARCH2, "-s", SIGNATURE, "-K", PRIVKEY, "--repo", ARCH, COMPONENTB+":"+VERSION)).To(Succeed()) + Expect(env.CatchOutput(buf).Execute("sign", "components", "--lookup", ARCH2, "-s", SIGNATURE, "-K", PRIVKEY, "--repo", ARCH, COMPONENTB+":"+VERSION, "--normalization", compdesc.JsonNormalisationV1)).To(Succeed()) Expect(buf.String()).To(StringEqualTrimmedWithContext(` applying to version "github.com/mandelsoft/ref:v1"[github.com/mandelsoft/ref:v1]... @@ -185,7 +218,7 @@ applying to version "github.com/mandelsoft/ref:v1"[github.com/mandelsoft/ref:v1] reference 0: github.com/mandelsoft/test:v1: digest SHA-256:${test}[jsonNormalisation/v1] resource 0: "name"="otherdata": digest SHA-256:${rb0}[genericBlobDigest/v1] successfully signed github.com/mandelsoft/ref:v1 (digest SHA-256:${ref}) -`, substitutions)) +`, substitutionsV1)) session := datacontext.NewSession() defer session.Close() @@ -196,7 +229,7 @@ successfully signed github.com/mandelsoft/ref:v1 (digest SHA-256:${ref}) cv, err := src.LookupComponentVersion(COMPONENTB, VERSION) Expect(err).To(Succeed()) session.AddCloser(cv) - Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(D_COMPONENTB)) + Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(D_COMPONENTB_V1)) }) }) @@ -281,7 +314,7 @@ Error: signing: github.com/mandelsoft/ref:v1: failed resolving component referen It("signs comp arch with lookup", func() { buf := bytes.NewBuffer(nil) - MustBeSuccessful(env.CatchOutput(buf).Execute("sign", "components", "-s", SIGNATURE, "-K", PRIVKEY, "--lookup", ARCH, "--repo", COMPARCH)) + MustBeSuccessful(env.CatchOutput(buf).Execute("sign", "components", "-s", SIGNATURE, "-K", PRIVKEY, "--lookup", ARCH, "--repo", COMPARCH, "--normalization", compdesc.JsonNormalisationV1)) Expect(buf.String()).To(StringEqualTrimmedWithContext(` applying to version "github.com/mandelsoft/ref:v1"[github.com/mandelsoft/ref:v1]... no digest found for "github.com/mandelsoft/test:v1" @@ -330,7 +363,7 @@ created rsa key pair key.priv[key.cert] // sigh component with certificate buf.Reset() - Expect(env.CatchOutput(buf).Execute("sign", "component", ARCH, "-K", "key.priv", "-k", "key.cert", "--ca-cert", "root.cert", "-s", "mandelsoft", "-I", "CN=mandelsoft")).To(Succeed()) + Expect(env.CatchOutput(buf).Execute("sign", "component", ARCH, "-K", "key.priv", "-k", "key.cert", "--ca-cert", "root.cert", "-s", "mandelsoft", "-I", "CN=mandelsoft", "--normalization", compdesc.JsonNormalisationV1)).To(Succeed()) Expect(buf.String()).To(StringEqualTrimmedWithContext(` applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v1]... successfully signed github.com/mandelsoft/test:v1 (digest SHA-256:5ed8bb27309c3c2fff43f3b0f3ebb56a5737ad6db4bc8ace73c5455cb86faf54) @@ -371,11 +404,11 @@ successfully verified github.com/mandelsoft/test:v1 (digest SHA-256:5ed8bb27309c }) }) - It("signs transport archive", func() { + DescribeTable("signs transport archive", func(substitutions Substitutions, normAlgo string) { prepareEnv(env, ARCH, ARCH) buf := bytes.NewBuffer(nil) - Expect(env.CatchOutput(buf).Execute("sign", "components", "--verified", VERIFIED_FILE, "-s", SIGNATURE, "-K", PRIVKEY, "--repo", ARCH, COMPONENTB+":"+VERSION)).To(Succeed()) + Expect(env.CatchOutput(buf).Execute("sign", "components", "--verified", VERIFIED_FILE, "-s", SIGNATURE, "-K", PRIVKEY, "--repo", ARCH, COMPONENTB+":"+VERSION, "--normalization", normAlgo)).To(Succeed()) Expect(buf.String()).To(StringEqualTrimmedWithContext(` applying to version "github.com/mandelsoft/ref:v1"[github.com/mandelsoft/ref:v1]... @@ -384,7 +417,7 @@ applying to version "github.com/mandelsoft/ref:v1"[github.com/mandelsoft/ref:v1] resource 0: "name"="testdata": digest SHA-256:${r0}[genericBlobDigest/v1] resource 1: "name"="value": digest SHA-256:${r1}[ociArtifactDigest/v1] resource 2: "name"="ref": digest SHA-256:${r2}[ociArtifactDigest/v1] - reference 0: github.com/mandelsoft/test:v1: digest SHA-256:${test}[jsonNormalisation/v1] + reference 0: github.com/mandelsoft/test:v1: digest SHA-256:${test}[${normAlgo}] resource 0: "name"="otherdata": digest SHA-256:${rb0}[genericBlobDigest/v1] successfully signed github.com/mandelsoft/ref:v1 (digest SHA-256:${ref}) `, substitutions)) @@ -395,7 +428,11 @@ successfully signed github.com/mandelsoft/ref:v1 (digest SHA-256:${ref}) CheckStore(store, common.NewNameVersion(COMPONENTA, VERSION)) CheckStore(store, common.NewNameVersion(COMPONENTB, VERSION)) - }) + }, + Entry("v1", substitutionsV1, compdesc.JsonNormalisationV1), + Entry("v2", substitutionsV2, compdesc.JsonNormalisationV2), + Entry("v3", substitutionsV3, compdesc.JsonNormalisationV3), + ) }) }) diff --git a/docs/reference/ocm_hash_componentversions.md b/docs/reference/ocm_hash_componentversions.md index 1e49ba07f4..4c5e46bdca 100644 --- a/docs/reference/ocm_hash_componentversions.md +++ b/docs/reference/ocm_hash_componentversions.md @@ -21,7 +21,7 @@ componentversions, componentversion, cv, components, component, comps, comp, c -h, --help help for componentversions --latest restrict component versions to latest --lookup stringArray repository name or spec for closure lookup fallback - -N, --normalization string normalization algorithm (default "jsonNormalisation/v1") + -N, --normalization string normalization algorithm (default "jsonNormalisation/v3") -O, --outfile string Output file for normalized component descriptor (default "-") -o, --output string output mode (JSON, json, norm, wide, yaml) -r, --recursive follow component reference nesting @@ -63,8 +63,18 @@ references. The following normalization modes are supported with option --normalization: - - jsonNormalisation/v1 (default) + - jsonNormalisation/v1 - jsonNormalisation/v2 + - jsonNormalisation/v3 (default) + + +Note that the normalization algorithm is important to be equivalent when used for signing and verification, otherwise +the verification can fail. Please always migrate to the latest normalization algorithm whenever possible. +New signature algorithms can be used as soon as they are available in the component version after signing it. + +The algorithms jsonNormalisation/v1 and jsonNormalisation/v2 are deprecated and should not be used anymore. +Please switch to jsonNormalisation/v3 as soon as possible. + The following hash modes are supported with option --hash: diff --git a/docs/reference/ocm_logging.md b/docs/reference/ocm_logging.md index 9a1c45800b..3e579d5488 100644 --- a/docs/reference/ocm_logging.md +++ b/docs/reference/ocm_logging.md @@ -21,6 +21,7 @@ The following *realms* are used by the command line tool: - ocm/accessmethod/wget: access method for wget - ocm/blobaccess/wget: blob access for wget - ocm/compdesc: component descriptor handling + - ocm/compdesc/normalizations/legacy: component descriptor legacy normalization defaulting - ocm/config: configuration management - ocm/context: context lifecycle - ocm/credentials: Credentials diff --git a/docs/reference/ocm_sign_componentversions.md b/docs/reference/ocm_sign_componentversions.md index b1d3f582bf..fa9a0d7d17 100644 --- a/docs/reference/ocm_sign_componentversions.md +++ b/docs/reference/ocm_sign_componentversions.md @@ -25,7 +25,7 @@ componentversions, componentversion, cv, components, component, comps, comp, c --keyless use keyless signing --latest restrict component versions to latest --lookup stringArray repository name or spec for closure lookup fallback - -N, --normalization string normalization algorithm (default "jsonNormalisation/v1") + -N, --normalization string normalization algorithm (default "jsonNormalisation/v3") -K, --private-key stringArray private key setting -k, --public-key stringArray public key setting -R, --recursive recursively sign component versions @@ -124,8 +124,9 @@ The following signing types are supported with option --algorithm: The following normalization modes are supported with option --normalization: - - jsonNormalisation/v1 (default) + - jsonNormalisation/v1 - jsonNormalisation/v2 + - jsonNormalisation/v3 (default) The following hash modes are supported with option --hash: