Skip to content

Commit

Permalink
feat: jsonNormalisation/v3 and old fixes avoiding broken sigs
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jakobmoellerdev committed Jan 3, 2025
1 parent a6e2e9c commit 4cc3c4f
Show file tree
Hide file tree
Showing 14 changed files with 208 additions and 18 deletions.
14 changes: 14 additions & 0 deletions .github/config/golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -173,4 +173,18 @@ 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: jsonv2.Algorithm is deprecated"
- path: "cmds/.*|api/.*"
linters:
- staticcheck
text: "SA1019: legacy.DefaultingOfVersionIntoExtraIdentity is deprecated"

70 changes: 63 additions & 7 deletions api/ocm/compdesc/norm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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\"}]}]"))
Expand All @@ -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{
Expand All @@ -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{
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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")
})
})
})
1 change: 1 addition & 0 deletions api/ocm/compdesc/normalization.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type NormalisationAlgorithm = string
const (
JsonNormalisationV1 NormalisationAlgorithm = "jsonNormalisation/v1"
JsonNormalisationV2 NormalisationAlgorithm = "jsonNormalisation/v2"
JsonNormalisationV3 NormalisationAlgorithm = "jsonNormalisation/v3"
)

type Normalization interface {
Expand Down
1 change: 1 addition & 0 deletions api/ocm/compdesc/normalizations/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
7 changes: 4 additions & 3 deletions api/ocm/compdesc/normalizations/jsonv1/norm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions api/ocm/compdesc/normalizations/jsonv2/norm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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
}
Expand Down
31 changes: 31 additions & 0 deletions api/ocm/compdesc/normalizations/jsonv3/norm.go
Original file line number Diff line number Diff line change
@@ -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
}
57 changes: 57 additions & 0 deletions api/ocm/compdesc/normalizations/legacy/legacy.go
Original file line number Diff line number Diff line change
@@ -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
}
}
}
1 change: 1 addition & 0 deletions api/ocm/selectors/accessors/accessors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 4cc3c4f

Please sign in to comment.