From 7b82be10a37b96db3abedcddc075384fa7a5aa82 Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Tue, 6 Aug 2024 10:56:20 +0200 Subject: [PATCH 1/9] rename reftype + resource resolution on CD set --- api/helper/builder/ocm_reference.go | 2 +- api/ocm/compdesc/componentdescriptor.go | 44 +++--- api/ocm/compdesc/copy_test.go | 2 +- api/ocm/compdesc/default.go | 2 +- api/ocm/compdesc/deprecated.go | 10 +- api/ocm/compdesc/equal_test.go | 14 +- api/ocm/compdesc/helper.go | 12 +- api/ocm/compdesc/helper_test.go | 10 +- api/ocm/compdesc/meta/v1/identity.go | 16 ++ api/ocm/compdesc/resolver.go | 96 ++++++++++++ api/ocm/compdesc/resourceref.go | 85 +++++++++++ api/ocm/compdesc/resourceref_test.go | 143 ++++++++++++++++++ api/ocm/compdesc/selector.go | 6 +- api/ocm/compdesc/selectors.go | 4 +- .../versions/ocm.software/v3alpha1/version.go | 8 +- api/ocm/compdesc/versions/v2/version.go | 8 +- api/ocm/cpi/repocpi/view_cv.go | 6 +- api/ocm/elements/common.go | 2 +- api/ocm/elements/digests.go | 2 +- api/ocm/elements/references.go | 4 +- api/ocm/internal/errors.go | 11 +- api/ocm/internal/repository.go | 2 +- api/ocm/tools/signing/handle.go | 2 +- api/ocm/tools/transfer/merge_test.go | 2 +- .../transfer/transferhandler/spiff/handler.go | 2 +- .../transferhandler/standard/handler.go | 2 +- .../transferhandler/transferhandler.go | 2 +- api/ocm/utils.go | 2 +- .../ocmcmds/common/addhdlrs/refs/elements.go | 2 +- .../ocmcmds/references/add/cmd_test.go | 12 +- .../ocmcmds/references/common/typehandler.go | 4 +- 31 files changed, 435 insertions(+), 84 deletions(-) create mode 100644 api/ocm/compdesc/resolver.go create mode 100644 api/ocm/compdesc/resourceref.go create mode 100644 api/ocm/compdesc/resourceref_test.go diff --git a/api/helper/builder/ocm_reference.go b/api/helper/builder/ocm_reference.go index c6a43ad83..93fc02f93 100644 --- a/api/helper/builder/ocm_reference.go +++ b/api/helper/builder/ocm_reference.go @@ -7,7 +7,7 @@ import ( type ocmReference struct { base - meta compdesc.ComponentReference + meta compdesc.Reference } const T_OCMREF = "reference" diff --git a/api/ocm/compdesc/componentdescriptor.go b/api/ocm/compdesc/componentdescriptor.go index f8fe31ea5..bf111c802 100644 --- a/api/ocm/compdesc/componentdescriptor.go +++ b/api/ocm/compdesc/componentdescriptor.go @@ -10,6 +10,7 @@ import ( "ocm.software/ocm/api/ocm/compdesc/equivalent" metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" "ocm.software/ocm/api/ocm/selectors/accessors" + "ocm.software/ocm/api/utils/errkind" "ocm.software/ocm/api/utils/runtime" "ocm.software/ocm/api/utils/semverutils" ) @@ -18,7 +19,13 @@ const InternalSchemaVersion = "internal" var NotFound = errors.ErrNotFound() -const KIND_REFERENCE = "component reference" +const ( + KIND_COMPONENT = errkind.KIND_COMPONENT + KIND_COMPONENTVERSION = "component version" + KIND_RESOURCE = "component resource" + KIND_SOURCE = "component source" + KIND_REFERENCE = "component reference" +) const ComponentDescriptorFileName = "component-descriptor.yaml" @@ -716,7 +723,7 @@ func NewResourceMeta(name string, typ string, relation metav1.ResourceRelation) } } -type References []ComponentReference +type References []Reference func (r References) Equivalent(o References) equivalent.EqualState { return EquivalentElems(r, o) @@ -753,10 +760,10 @@ func (r References) Copy() References { return out } -// ComponentReference describes the reference to another component in the registry. +// Reference describes the reference to another component in the registry. // +k8s:deepcopy-gen=true // +k8s:openapi-gen=true -type ComponentReference struct { +type Reference struct { ElementMeta `json:",inline"` // ComponentName describes the remote name of the referenced object ComponentName string `json:"componentName"` @@ -765,8 +772,11 @@ type ComponentReference struct { Digest *metav1.DigestSpec `json:"digest,omitempty"` } -func NewComponentReference(name, componentName, version string, extraIdentity metav1.Identity) *ComponentReference { - return &ComponentReference{ +// Deprecated: use Reference. +type ComponentReference = Reference + +func NewComponentReference(name, componentName, version string, extraIdentity metav1.Identity) *Reference { + return &Reference{ ElementMeta: ElementMeta{ Name: name, Version: version, @@ -776,41 +786,41 @@ func NewComponentReference(name, componentName, version string, extraIdentity me } } -func (r ComponentReference) String() string { +func (r Reference) String() string { return fmt.Sprintf("%s[%s:%s]", r.Name, r.ComponentName, r.Version) } // WithVersion returns a new reference with a dedicated version. -func (o *ComponentReference) WithVersion(v string) *ComponentReference { +func (o *Reference) WithVersion(v string) *Reference { n := o.Copy() n.Version = v return n } // WithExtraIdentity returns a new reference with a dedicated version. -func (o *ComponentReference) WithExtraIdentity(extras ...string) *ComponentReference { +func (o *Reference) WithExtraIdentity(extras ...string) *Reference { n := o.Copy() n.AddExtraIdentity(NewExtraIdentity(extras...)) return n } // Fresh returns a digest-free copy. -func (o *ComponentReference) Fresh() *ComponentReference { +func (o *Reference) Fresh() *Reference { n := o.Copy() n.Digest = nil return n } -func (r *ComponentReference) GetDigest() *metav1.DigestSpec { +func (r *Reference) GetDigest() *metav1.DigestSpec { return r.Digest } -func (r *ComponentReference) SetDigest(d *metav1.DigestSpec) { +func (r *Reference) SetDigest(d *metav1.DigestSpec) { r.Digest = d } -func (r *ComponentReference) Equivalent(e ElementMetaAccessor) equivalent.EqualState { - if o, ok := e.(*ComponentReference); !ok { +func (r *Reference) Equivalent(e ElementMetaAccessor) equivalent.EqualState { + if o, ok := e.(*Reference); !ok { state := equivalent.StateNotLocalHashEqual() if r.Digest != nil { state = state.Apply(equivalent.StateNotArtifactEqual(true)) @@ -831,12 +841,12 @@ func (r *ComponentReference) Equivalent(e ElementMetaAccessor) equivalent.EqualS } } -func (r *ComponentReference) GetComponentName() string { +func (r *Reference) GetComponentName() string { return r.ComponentName } -func (r *ComponentReference) Copy() *ComponentReference { - return &ComponentReference{ +func (r *Reference) Copy() *Reference { + return &Reference{ ElementMeta: *r.ElementMeta.Copy(), ComponentName: r.ComponentName, Digest: r.Digest.Copy(), diff --git a/api/ocm/compdesc/copy_test.go b/api/ocm/compdesc/copy_test.go index 1cb1c69f2..f854d774f 100644 --- a/api/ocm/compdesc/copy_test.go +++ b/api/ocm/compdesc/copy_test.go @@ -70,7 +70,7 @@ var _ = Describe("Component Descripor Copy Test Suitet", func() { }, } cd.References = compdesc.References{ - compdesc.ComponentReference{ + compdesc.Reference{ ElementMeta: compdesc.ElementMeta{}, ComponentName: "", Digest: nil, diff --git a/api/ocm/compdesc/default.go b/api/ocm/compdesc/default.go index bb9879a8e..af504034f 100644 --- a/api/ocm/compdesc/default.go +++ b/api/ocm/compdesc/default.go @@ -14,7 +14,7 @@ func DefaultComponent(component *ComponentDescriptor) *ComponentDescriptor { component.Sources = make([]Source, 0) } if component.References == nil { - component.References = make([]ComponentReference, 0) + component.References = make([]Reference, 0) } if component.Resources == nil { component.Resources = make([]Resource, 0) diff --git a/api/ocm/compdesc/deprecated.go b/api/ocm/compdesc/deprecated.go index 9b10b32d5..cea854ea0 100644 --- a/api/ocm/compdesc/deprecated.go +++ b/api/ocm/compdesc/deprecated.go @@ -200,8 +200,8 @@ func (cd *ComponentDescriptor) GetSourcesByName(name string, selectors ...Identi // GetComponentReferences returns all component references that matches the given selectors. // // Deprectated: use GetReferences with appropriate selectors. -func (cd *ComponentDescriptor) GetComponentReferences(selectors ...IdentitySelector) ([]ComponentReference, error) { - refs := make([]ComponentReference, 0) +func (cd *ComponentDescriptor) GetComponentReferences(selectors ...IdentitySelector) ([]Reference, error) { + refs := make([]Reference, 0) for _, ref := range cd.References { ok, err := selector.MatchSelectors(ref.GetIdentity(cd.References), selectors...) if err != nil { @@ -220,13 +220,13 @@ func (cd *ComponentDescriptor) GetComponentReferences(selectors ...IdentitySelec // GetComponentReferenceIndex returns the index of a given component reference. // If the index is not found -1 is returned. // Deprecated: use GetReferenceIndex. -func (cd *ComponentDescriptor) GetComponentReferenceIndex(ref ComponentReference) int { +func (cd *ComponentDescriptor) GetComponentReferenceIndex(ref Reference) int { return cd.GetReferenceIndex(ref.GetMeta()) } // GetReferenceAccessByIdentity returns a pointer to the reference that matches the given identity. // Deprectated: use GetReferenceByIdentity. -func (cd *ComponentDescriptor) GetReferenceAccessByIdentity(id v1.Identity) *ComponentReference { +func (cd *ComponentDescriptor) GetReferenceAccessByIdentity(id v1.Identity) *Reference { dig := id.Digest() for i, ref := range cd.References { if bytes.Equal(ref.GetIdentityDigest(cd.Resources), dig) { @@ -270,7 +270,7 @@ func (cd *ComponentDescriptor) GetReferencesBySelectors(selectors []IdentitySele if !ok { continue } - references = append(references, *selctx.ComponentReference) + references = append(references, *selctx.Reference) } if len(references) == 0 { return references, NotFound diff --git a/api/ocm/compdesc/equal_test.go b/api/ocm/compdesc/equal_test.go index 9007cb91e..7363032ad 100644 --- a/api/ocm/compdesc/equal_test.go +++ b/api/ocm/compdesc/equal_test.go @@ -487,10 +487,10 @@ var _ = Describe("equivalence", func() { }) Context("reference", func() { - var a, b *compdesc.ComponentReference + var a, b *compdesc.Reference BeforeEach(func() { - a = &compdesc.ComponentReference{ + a = &compdesc.Reference{ ElementMeta: compdesc.ElementMeta{ Name: "r1", Version: "v1", @@ -536,7 +536,7 @@ var _ = Describe("equivalence", func() { BeforeEach(func() { a = compdesc.References{ - compdesc.ComponentReference{ + compdesc.Reference{ ElementMeta: compdesc.ElementMeta{ Name: "s1", Version: "v1", @@ -544,7 +544,7 @@ var _ = Describe("equivalence", func() { }, ComponentName: "c1", }, - compdesc.ComponentReference{ + compdesc.Reference{ ElementMeta: compdesc.ElementMeta{ Name: "s2", Version: "v1", @@ -583,7 +583,7 @@ var _ = Describe("equivalence", func() { }) It("handles additional entry", func() { - b = append(b, compdesc.ComponentReference{ + b = append(b, compdesc.Reference{ ElementMeta: compdesc.ElementMeta{ Name: "s3", ExtraIdentity: compdesc.NewExtraIdentity("platform", "linux"), @@ -596,7 +596,7 @@ var _ = Describe("equivalence", func() { }) It("handles additional entry without any other metadata", func() { - b = append(b, compdesc.ComponentReference{ + b = append(b, compdesc.Reference{ ElementMeta: compdesc.ElementMeta{ Name: "s3", ExtraIdentity: compdesc.NewExtraIdentity("platform", "linux"), @@ -608,7 +608,7 @@ var _ = Describe("equivalence", func() { }) It("handles additional entry without any other metadata", func() { - b = append(b, compdesc.ComponentReference{ + b = append(b, compdesc.Reference{ ElementMeta: compdesc.ElementMeta{ Name: "s3", ExtraIdentity: compdesc.NewExtraIdentity("platform", "linux"), diff --git a/api/ocm/compdesc/helper.go b/api/ocm/compdesc/helper.go index e6cc28fd5..fa48889fe 100644 --- a/api/ocm/compdesc/helper.go +++ b/api/ocm/compdesc/helper.go @@ -175,24 +175,24 @@ func (cd *ComponentDescriptor) GetSourceIndex(src *SourceMeta) int { } // GetReferenceByIdentity returns reference that matches the given identity. -func (cd *ComponentDescriptor) GetReferenceByIdentity(id v1.Identity) (ComponentReference, error) { +func (cd *ComponentDescriptor) GetReferenceByIdentity(id v1.Identity) (Reference, error) { dig := id.Digest() for _, ref := range cd.References { if bytes.Equal(ref.GetIdentityDigest(cd.Resources), dig) { return ref, nil } } - return ComponentReference{}, errors.ErrNotFound(KIND_REFERENCE, id.String()) + return Reference{}, errors.ErrNotFound(KIND_REFERENCE, id.String()) } -func (cd *ComponentDescriptor) SelectReferences(sel ...refsel.Selector) ([]ComponentReference, error) { +func (cd *ComponentDescriptor) SelectReferences(sel ...refsel.Selector) ([]Reference, error) { err := selectors.ValidateSelectors(sel...) if err != nil { return nil, err } list := MapToSelectorElementList(cd.References) - result := []ComponentReference{} + result := []Reference{} for _, r := range cd.References { if len(sel) > 0 { mr := MapToSelectorReference(&r) @@ -207,8 +207,8 @@ func (cd *ComponentDescriptor) SelectReferences(sel ...refsel.Selector) ([]Compo return result, nil } -func (cd *ComponentDescriptor) GetReferences() []ComponentReference { - result := []ComponentReference{} +func (cd *ComponentDescriptor) GetReferences() []Reference { + result := []Reference{} for _, r := range cd.References { result = append(result, r) } diff --git a/api/ocm/compdesc/helper_test.go b/api/ocm/compdesc/helper_test.go index e4385d42a..3ba37220a 100644 --- a/api/ocm/compdesc/helper_test.go +++ b/api/ocm/compdesc/helper_test.go @@ -281,7 +281,7 @@ var _ = Describe("helper", func() { Context("reference selection", func() { cd := &compdesc.ComponentDescriptor{} - r1v1 := compdesc.ComponentReference{ + r1v1 := compdesc.Reference{ ElementMeta: compdesc.ElementMeta{ Name: "r1", Version: "v1", @@ -296,14 +296,14 @@ var _ = Describe("helper", func() { }, ComponentName: "c1", } - r1v2 := compdesc.ComponentReference{ + r1v2 := compdesc.Reference{ ElementMeta: compdesc.ElementMeta{ Name: "r1", Version: "v2", }, ComponentName: "c1", } - r2v1 := compdesc.ComponentReference{ + r2v1 := compdesc.Reference{ ElementMeta: compdesc.ElementMeta{ Name: "r2", Version: "v1", @@ -318,7 +318,7 @@ var _ = Describe("helper", func() { }, ComponentName: "c2", } - r3v2 := compdesc.ComponentReference{ + r3v2 := compdesc.Reference{ ElementMeta: compdesc.ElementMeta{ Name: "r3", Version: "v2", @@ -345,7 +345,7 @@ var _ = Describe("helper", func() { ComponentName: "c3", } - r4v3 := compdesc.ComponentReference{ + r4v3 := compdesc.Reference{ ElementMeta: compdesc.ElementMeta{ Name: "r4", Version: "v3", diff --git a/api/ocm/compdesc/meta/v1/identity.go b/api/ocm/compdesc/meta/v1/identity.go index 80ab79d00..bbea5a607 100644 --- a/api/ocm/compdesc/meta/v1/identity.go +++ b/api/ocm/compdesc/meta/v1/identity.go @@ -103,6 +103,22 @@ func (i Identity) Remove(name string) bool { return false } +// ExtraIdentity extracts the extra identity part of an identity. +func (i Identity) ExtraIdentity() Identity { + if i == nil { + return nil + } + if _, ok := i[SystemIdentityName]; !ok { + return i + } + if len(i) == 1 { + return nil + } + r := i.Copy() + delete(r, SystemIdentityName) + return r +} + func (i Identity) String() string { if i == nil { return "" diff --git a/api/ocm/compdesc/resolver.go b/api/ocm/compdesc/resolver.go new file mode 100644 index 000000000..df7692c92 --- /dev/null +++ b/api/ocm/compdesc/resolver.go @@ -0,0 +1,96 @@ +package compdesc + +import ( + "sync" + + "github.com/mandelsoft/goutils/errors" + + common "ocm.software/ocm/api/utils/misc" +) + +type ComponentVersionResolver interface { + LookupComponentVersion(name string, version string) (*ComponentDescriptor, error) +} + +//////////////////////////////////////////////////////////////////////////////// + +type ComponentVersionSet struct { + lock sync.RWMutex + cds map[common.NameVersion]*ComponentDescriptor +} + +var _ ComponentVersionResolver = (*ComponentVersionSet)(nil) + +func NewComponentVersionSet(cds ...*ComponentDescriptor) *ComponentVersionSet { + r := map[common.NameVersion]*ComponentDescriptor{} + for _, cd := range cds { + r[common.NewNameVersion(cd.Name, cd.Version)] = cd.Copy() + } + return &ComponentVersionSet{cds: r} +} + +func (c *ComponentVersionSet) LookupComponentVersion(name string, version string) (*ComponentDescriptor, error) { + c.lock.RLock() + defer c.lock.RUnlock() + + nv := common.NewNameVersion(name, version) + + cd := c.cds[nv] + if cd == nil { + return nil, errors.ErrNotFound(KIND_COMPONENTVERSION, nv.String()) + } + return cd, nil +} + +func (c *ComponentVersionSet) AddVersion(cd *ComponentDescriptor) { + c.lock.Lock() + defer c.lock.Unlock() + + c.cds[common.NewNameVersion(cd.Name, cd.Version)] = cd.Copy() +} + +//////////////////////////////////////////////////////////////////////////////// + +type CompoundResolver struct { + lock sync.RWMutex + resolvers []ComponentVersionResolver +} + +var _ ComponentVersionResolver = (*CompoundResolver)(nil) + +func NewCompoundResolver(res ...ComponentVersionResolver) ComponentVersionResolver { + for i := 0; i < len(res); i++ { + if res[i] == nil { + res = append(res[:i], res[i+1:]...) + i-- + } + } + if len(res) == 1 { + return res[0] + } + return &CompoundResolver{resolvers: res} +} + +func (c *CompoundResolver) LookupComponentVersion(name string, version string) (*ComponentDescriptor, error) { + c.lock.RLock() + defer c.lock.RUnlock() + for _, r := range c.resolvers { + if r == nil { + continue + } + cv, err := r.LookupComponentVersion(name, version) + if err == nil && cv != nil { + return cv, nil + } + if !errors.IsErrNotFoundKind(err, KIND_COMPONENTVERSION) && !errors.IsErrNotFoundKind(err, KIND_COMPONENT) { + return nil, err + } + } + return nil, errors.ErrNotFound(KIND_REFERENCE, common.NewNameVersion(name, version).String()) +} + +func (c *CompoundResolver) AddResolver(r ComponentVersionResolver) { + c.lock.Lock() + defer c.lock.Unlock() + c.resolvers = append(c.resolvers, r) +} diff --git a/api/ocm/compdesc/resourceref.go b/api/ocm/compdesc/resourceref.go new file mode 100644 index 000000000..91a467b38 --- /dev/null +++ b/api/ocm/compdesc/resourceref.go @@ -0,0 +1,85 @@ +package compdesc + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + common "ocm.software/ocm/api/utils/misc" +) + +func ResolveReferencePath(cv *ComponentDescriptor, path []metav1.Identity, resolver ComponentVersionResolver) (*ComponentDescriptor, error) { + if cv == nil { + return nil, fmt.Errorf("no component version specified") + } + + eff := cv + for _, cr := range path { + cref, err := eff.GetReferenceByIdentity(cr) + if err != nil { + return nil, errors.Wrapf(err, "%s", common.VersionedElementKey(cv)) + } + + compoundResolver := NewCompoundResolver(NewComponentVersionSet(cv), resolver) + eff, err = compoundResolver.LookupComponentVersion(cref.GetComponentName(), cref.GetVersion()) + if err != nil { + return nil, errors.Wrapf(err, "cannot resolve component version for reference %s", cr.String()) + } + if eff == nil { + return nil, errors.ErrNotFound(KIND_COMPONENTVERSION, cref.String()) + } + } + return eff, nil +} + +func MatchResourceReference(cv *ComponentDescriptor, typ string, ref metav1.ResourceReference, resolver ComponentVersionResolver) (*Resource, *ComponentDescriptor, error) { + eff, err := ResolveReferencePath(cv, ref.ReferencePath, resolver) + if err != nil { + return nil, nil, err + } + + if len(eff.Resources) == 0 && len(ref.Resource) == 0 { + return nil, nil, errors.ErrNotFound(KIND_RESOURCE) + } +outer: + for i, r := range eff.Resources { + if r.Type != typ && typ != "" { + continue + } + for k, v := range ref.Resource { + switch k { + case metav1.SystemIdentityName: + if v != r.Name { + continue outer + } + case metav1.SystemIdentityVersion: + if v != r.Version { + continue outer + } + default: + if r.ExtraIdentity == nil || r.ExtraIdentity[k] != v { + continue outer + } + } + } + return &eff.Resources[i], eff, nil + } + return nil, nil, errors.ErrNotFound(KIND_RESOURCE, ref.Resource.String()) +} + +func ResolveResourceReference(cv *ComponentDescriptor, ref metav1.ResourceReference, resolver ComponentVersionResolver) (*Resource, *ComponentDescriptor, error) { + if len(ref.Resource) == 0 || len(ref.Resource["name"]) == 0 { + return nil, nil, errors.Newf("at least resource name must be specified for resource reference") + } + + eff, err := ResolveReferencePath(cv, ref.ReferencePath, resolver) + if err != nil { + return nil, nil, err + } + r, err := eff.GetResourceByIdentity(ref.Resource) + if err != nil { + return nil, nil, err + } + return &r, eff, nil +} diff --git a/api/ocm/compdesc/resourceref_test.go b/api/ocm/compdesc/resourceref_test.go new file mode 100644 index 000000000..077b65162 --- /dev/null +++ b/api/ocm/compdesc/resourceref_test.go @@ -0,0 +1,143 @@ +package compdesc_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" +) + +const ( + VERSION = "v1" + COMPONENT = "github.com/mandelsoft/test" + COMPONENT2 = "github.com/mandelsoft/test2" + COMPONENT3 = "github.com/mandelsoft/test3" + + EXTRA_ATTR = "platform" + EXTRA_VAL = "test" +) + +func CheckResourceRef(cv *compdesc.ComponentDescriptor, resolver compdesc.ComponentVersionResolver, comp string, rsc metav1.Identity, path ...metav1.Identity) { + ref := metav1.NewNestedResourceRef(rsc, path) + + r, cd := Must2(compdesc.ResolveResourceReference(cv, ref, resolver)) + ExpectWithOffset(1, r).NotTo(BeNil()) + ExpectWithOffset(1, cd).NotTo(BeNil()) + + ExpectWithOffset(1, cd.Name).To(Equal(comp)) + ExpectWithOffset(1, r.Name).To(Equal(rsc.Get(compdesc.SystemIdentityName))) + ExpectWithOffset(1, r.ExtraIdentity).To(Equal(rsc.ExtraIdentity())) +} + +var _ = Describe("resolving local resource references", func() { + var set *compdesc.ComponentVersionSet + + BeforeEach(func() { + set = compdesc.NewComponentVersionSet() + + cd := compdesc.New(COMPONENT, VERSION) + cd.Resources = append(cd.Resources, compdesc.Resource{ + ResourceMeta: compdesc.ResourceMeta{ + ElementMeta: compdesc.ElementMeta{ + Name: "testata", + Version: "", + }, + Type: "PlainText", + Relation: metav1.LocalRelation, + }, + }) + set.AddVersion(cd) + + cd = compdesc.New(COMPONENT2, VERSION) + cd.Resources = append(cd.Resources, compdesc.Resource{ + ResourceMeta: compdesc.ResourceMeta{ + ElementMeta: compdesc.ElementMeta{ + Name: "otherdata", + Version: "", + }, + Type: "PlainText", + Relation: metav1.LocalRelation, + }, + }) + cd.Resources = append(cd.Resources, compdesc.Resource{ + ResourceMeta: compdesc.ResourceMeta{ + ElementMeta: compdesc.ElementMeta{ + Name: "otherdata", + Version: "", + ExtraIdentity: metav1.NewExtraIdentity(EXTRA_ATTR, EXTRA_VAL), + }, + Type: "PlainText", + Relation: metav1.LocalRelation, + }, + }) + cd.References = append(cd.References, compdesc.Reference{ + ElementMeta: compdesc.ElementMeta{ + Name: "ref", + Version: VERSION, + }, + ComponentName: COMPONENT, + }) + set.AddVersion(cd) + + cd = compdesc.New(COMPONENT3, VERSION) + cd.Resources = append(cd.Resources, compdesc.Resource{ + ResourceMeta: compdesc.ResourceMeta{ + ElementMeta: compdesc.ElementMeta{ + Name: "topdata", + Version: "", + }, + Type: "PlainText", + Relation: metav1.LocalRelation, + }, + }) + cd.References = append(cd.References, compdesc.Reference{ + ElementMeta: compdesc.ElementMeta{ + Name: "nested", + Version: VERSION, + }, + ComponentName: COMPONENT2, + }) + cd.References = append(cd.References, compdesc.Reference{ + ElementMeta: compdesc.ElementMeta{ + Name: "nested", + Version: VERSION, + ExtraIdentity: metav1.NewExtraIdentity(EXTRA_ATTR, EXTRA_VAL), + }, + ComponentName: COMPONENT2, + }) + set.AddVersion(cd) + }) + + It("resolves a direct local resource", func() { + CheckResourceRef(Must(set.LookupComponentVersion(COMPONENT3, VERSION)), set, COMPONENT3, metav1.NewIdentity("topdata")) + }) + + It("resolves an indirect resource", func() { + CheckResourceRef(Must(set.LookupComponentVersion(COMPONENT3, VERSION)), set, COMPONENT2, metav1.NewIdentity("otherdata"), metav1.NewIdentity("nested")) + }) + It("resolves an indirect resource with extra id", func() { + CheckResourceRef(Must(set.LookupComponentVersion(COMPONENT3, VERSION)), set, COMPONENT2, metav1.NewIdentity("otherdata", EXTRA_ATTR, EXTRA_VAL), metav1.NewIdentity("nested")) + }) + + It("fails resolving an indirect resource with non existing extra id", func() { + ref := metav1.NewNestedResourceRef(metav1.NewIdentity("otherdata", EXTRA_ATTR, "dummy"), []metav1.Identity{metav1.NewIdentity("nested")}) + ExpectError(compdesc.ResolveResourceReference(Must(set.LookupComponentVersion(COMPONENT3, VERSION)), ref, set)).To( + MatchError("not found")) + }) + + It("skips an intermediate component version", func() { + CheckResourceRef(Must(set.LookupComponentVersion(COMPONENT3, VERSION)), set, COMPONENT, metav1.NewIdentity("testata"), metav1.NewIdentity("nested"), metav1.NewIdentity("ref")) + }) + + It("skips an intermediate component version with extra id", func() { + CheckResourceRef(Must(set.LookupComponentVersion(COMPONENT3, VERSION)), set, COMPONENT, metav1.NewIdentity("testata"), metav1.NewIdentity("nested", EXTRA_ATTR, EXTRA_VAL), metav1.NewIdentity("ref")) + }) + + It("fails resolving an indirect resource with non existing intermediate ref", func() { + ref := metav1.NewNestedResourceRef(metav1.NewIdentity("testdata"), []metav1.Identity{metav1.NewIdentity("nested", EXTRA_ATTR, "dummy")}) + ExpectError(compdesc.ResolveResourceReference(Must(set.LookupComponentVersion(COMPONENT3, VERSION)), ref, set)).To( + MatchError("github.com/mandelsoft/test3:v1: component reference \"\"name\"=\"nested\",\"platform\"=\"dummy\"\" not found")) + }) +}) diff --git a/api/ocm/compdesc/selector.go b/api/ocm/compdesc/selector.go index 568f67bbb..ef70176b2 100644 --- a/api/ocm/compdesc/selector.go +++ b/api/ocm/compdesc/selector.go @@ -49,13 +49,13 @@ func MapToSelectorSource(r *Source) accessors.SourceAccessor { //////////////////////////////////////////////////////////////////////////////// type refAcc struct { - *ComponentReference + *Reference } func (a refAcc) GetMeta() accessors.ElementMeta { - return a.ComponentReference.GetMeta() + return a.Reference.GetMeta() } -func MapToSelectorReference(r *ComponentReference) accessors.ReferenceAccessor { +func MapToSelectorReference(r *Reference) accessors.ReferenceAccessor { return refAcc{r} } diff --git a/api/ocm/compdesc/selectors.go b/api/ocm/compdesc/selectors.go index ce3ec3776..0e079d3ec 100644 --- a/api/ocm/compdesc/selectors.go +++ b/api/ocm/compdesc/selectors.go @@ -589,14 +589,14 @@ func (s ReferenceSelectorFunc) MatchReference(obj ReferenceSelectionContext) (bo } type referenceSelectionContext struct { - *ComponentReference + *Reference identity } // Deprecated: use package selectors and its sub packages. func NewReferenceSelectionContext(index int, refs References) ReferenceSelectionContext { return &referenceSelectionContext{ - ComponentReference: &refs[index], + Reference: &refs[index], identity: identity{ accessor: refs, index: index, diff --git a/api/ocm/compdesc/versions/ocm.software/v3alpha1/version.go b/api/ocm/compdesc/versions/ocm.software/v3alpha1/version.go index f39556867..6c36971e5 100644 --- a/api/ocm/compdesc/versions/ocm.software/v3alpha1/version.go +++ b/api/ocm/compdesc/versions/ocm.software/v3alpha1/version.go @@ -91,8 +91,8 @@ func (v *DescriptorVersion) ConvertTo(obj compdesc.ComponentDescriptorVersion) ( return out, nil } -func convertReferenceTo(in Reference) compdesc.ComponentReference { - return compdesc.ComponentReference{ +func convertReferenceTo(in Reference) compdesc.Reference { + return compdesc.Reference{ ElementMeta: convertElementmetaTo(in.ElementMeta), ComponentName: in.ComponentName, Digest: in.Digest.Copy(), @@ -212,7 +212,7 @@ func (v *DescriptorVersion) ConvertFrom(in *compdesc.ComponentDescriptor) (compd return out, nil } -func convertReferenceFrom(in compdesc.ComponentReference) Reference { +func convertReferenceFrom(in compdesc.Reference) Reference { return Reference{ ElementMeta: convertElementmetaFrom(in.ElementMeta), ComponentName: in.ComponentName, @@ -220,7 +220,7 @@ func convertReferenceFrom(in compdesc.ComponentReference) Reference { } } -func convertReferencesFrom(in []compdesc.ComponentReference) []Reference { +func convertReferencesFrom(in []compdesc.Reference) []Reference { if in == nil { return nil } diff --git a/api/ocm/compdesc/versions/v2/version.go b/api/ocm/compdesc/versions/v2/version.go index 3d2ea687b..108d47273 100644 --- a/api/ocm/compdesc/versions/v2/version.go +++ b/api/ocm/compdesc/versions/v2/version.go @@ -97,8 +97,8 @@ func (v *DescriptorVersion) ConvertTo(obj compdesc.ComponentDescriptorVersion) ( return out, nil } -func convertComponentReferenceTo(in ComponentReference) compdesc.ComponentReference { - return compdesc.ComponentReference{ +func convertComponentReferenceTo(in ComponentReference) compdesc.Reference { + return compdesc.Reference{ ElementMeta: convertElementMetaTo(in.ElementMeta), ComponentName: in.ComponentName, Digest: in.Digest.Copy(), @@ -234,7 +234,7 @@ func (v *DescriptorVersion) ConvertFrom(in *compdesc.ComponentDescriptor) (compd return out, nil } -func convertComponentReferenceFrom(in compdesc.ComponentReference) ComponentReference { +func convertComponentReferenceFrom(in compdesc.Reference) ComponentReference { return ComponentReference{ ElementMeta: convertElementMetaFrom(in.ElementMeta), ComponentName: in.ComponentName, @@ -242,7 +242,7 @@ func convertComponentReferenceFrom(in compdesc.ComponentReference) ComponentRefe } } -func convertComponentReferencesFrom(in []compdesc.ComponentReference) []ComponentReference { +func convertComponentReferencesFrom(in []compdesc.Reference) []ComponentReference { if in == nil { return nil } diff --git a/api/ocm/cpi/repocpi/view_cv.go b/api/ocm/cpi/repocpi/view_cv.go index 43916063b..0320314fe 100644 --- a/api/ocm/cpi/repocpi/view_cv.go +++ b/api/ocm/cpi/repocpi/view_cv.go @@ -791,7 +791,7 @@ func (c *componentVersionAccessView) GetSources() []cpi.SourceAccess { return result } -func (c *componentVersionAccessView) SelectReferences(sel ...refsel.Selector) ([]compdesc.ComponentReference, error) { +func (c *componentVersionAccessView) SelectReferences(sel ...refsel.Selector) ([]compdesc.Reference, error) { err := selectors.ValidateSelectors(sel...) if err != nil { return nil, err @@ -799,7 +799,7 @@ func (c *componentVersionAccessView) SelectReferences(sel ...refsel.Selector) ([ return c.GetDescriptor().SelectReferences(sel...) } -func (c *componentVersionAccessView) GetReferences() []compdesc.ComponentReference { +func (c *componentVersionAccessView) GetReferences() []compdesc.Reference { return c.GetDescriptor().GetReferences() } @@ -861,7 +861,7 @@ func (c *componentVersionAccessView) GetReferencesBySelectors(selectors []compde if !ok { continue } - references = append(references, *selctx.ComponentReference) + references = append(references, *selctx.Reference) } if len(references) == 0 { return references, compdesc.NotFound diff --git a/api/ocm/elements/common.go b/api/ocm/elements/common.go index 1b210eeab..8d5d2eeb9 100644 --- a/api/ocm/elements/common.go +++ b/api/ocm/elements/common.go @@ -31,7 +31,7 @@ func (o commonOption) ApplyToSourceMeta(m *compdesc.SourceMeta) error { return o.apply(&m.ElementMeta) } -func (o commonOption) ApplyToReference(m *compdesc.ComponentReference) error { +func (o commonOption) ApplyToReference(m *compdesc.Reference) error { return o.apply(&m.ElementMeta) } diff --git a/api/ocm/elements/digests.go b/api/ocm/elements/digests.go index 4aa372761..cd32e3be6 100644 --- a/api/ocm/elements/digests.go +++ b/api/ocm/elements/digests.go @@ -14,7 +14,7 @@ type ResourceReferenceOption interface { type digest metav1.DigestSpec -func (o *digest) ApplyToReference(m *compdesc.ComponentReference) error { +func (o *digest) ApplyToReference(m *compdesc.Reference) error { if !(*metav1.DigestSpec)(o).IsNone() { m.Digest = (*metav1.DigestSpec)(o).Copy() } diff --git a/api/ocm/elements/references.go b/api/ocm/elements/references.go index 64bb1619f..60f1b5fb3 100644 --- a/api/ocm/elements/references.go +++ b/api/ocm/elements/references.go @@ -7,10 +7,10 @@ import ( ) type ReferenceOption interface { - ApplyToReference(reference *compdesc.ComponentReference) error + ApplyToReference(reference *compdesc.Reference) error } -func Reference(name, comp, vers string, opts ...ReferenceOption) (*compdesc.ComponentReference, error) { +func Reference(name, comp, vers string, opts ...ReferenceOption) (*compdesc.Reference, error) { m := compdesc.NewComponentReference(name, comp, vers, nil) list := errors.ErrList() for _, o := range opts { diff --git a/api/ocm/internal/errors.go b/api/ocm/internal/errors.go index 8c52e130d..eabcd3233 100644 --- a/api/ocm/internal/errors.go +++ b/api/ocm/internal/errors.go @@ -10,13 +10,14 @@ import ( ) const ( - KIND_REPOSITORY = "ocm repository" + KIND_REPOSITORY = "ocm repository" + KIND_REPOSITORYSPEC = "repository specification" + KIND_COMPONENT = errkind.KIND_COMPONENT - KIND_COMPONENTVERSION = "component version" - KIND_RESOURCE = "component resource" - KIND_SOURCE = "component source" + KIND_COMPONENTVERSION = compdesc.KIND_COMPONENTVERSION + KIND_RESOURCE = compdesc.KIND_RESOURCE + KIND_SOURCE = compdesc.KIND_SOURCE KIND_REFERENCE = compdesc.KIND_REFERENCE - KIND_REPOSITORYSPEC = "repository specification" KIND_OCM_REFERENCE = "ocm reference" ) diff --git a/api/ocm/internal/repository.go b/api/ocm/internal/repository.go index b45d3d7cf..f79646c1b 100644 --- a/api/ocm/internal/repository.go +++ b/api/ocm/internal/repository.go @@ -109,7 +109,7 @@ type ( SourceAccess = ArtifactAccess[SourceMeta] ) -type ComponentReference = compdesc.ComponentReference +type ComponentReference = compdesc.Reference type ComponentVersionAccess interface { resource.ResourceView[ComponentVersionAccess] diff --git a/api/ocm/tools/signing/handle.go b/api/ocm/tools/signing/handle.go index 172f18a22..39023241a 100644 --- a/api/ocm/tools/signing/handle.go +++ b/api/ocm/tools/signing/handle.go @@ -392,7 +392,7 @@ func checkDigest(orig *metav1.DigestSpec, act *metav1.DigestSpec) bool { return true } -func refMsg(ref compdesc.ComponentReference, msg string, args ...interface{}) string { +func refMsg(ref compdesc.Reference, msg string, args ...interface{}) string { return fmt.Sprintf("%s %s", fmt.Sprintf(msg, args...), ref) } diff --git a/api/ocm/tools/transfer/merge_test.go b/api/ocm/tools/transfer/merge_test.go index 0cdd8d8c8..2a7892890 100644 --- a/api/ocm/tools/transfer/merge_test.go +++ b/api/ocm/tools/transfer/merge_test.go @@ -204,7 +204,7 @@ var _ = Describe("basic merge operations for transport", func() { }, }, References: compdesc.References{ - compdesc.ComponentReference{ + compdesc.Reference{ ElementMeta: compdesc.ElementMeta{ Name: "ref1", Version: "v1.0.0", diff --git a/api/ocm/tools/transfer/transferhandler/spiff/handler.go b/api/ocm/tools/transfer/transferhandler/spiff/handler.go index 0a8aafff4..4044ffdf9 100644 --- a/api/ocm/tools/transfer/transferhandler/spiff/handler.go +++ b/api/ocm/tools/transfer/transferhandler/spiff/handler.go @@ -76,7 +76,7 @@ func (h *Handler) OverwriteVersion(src ocm.ComponentVersionAccess, tgt ocm.Compo return h.EvalBool("overwrite", binding, "process") } -func (h *Handler) TransferVersion(repo ocm.Repository, src ocm.ComponentVersionAccess, meta *compdesc.ComponentReference, tgt ocm.Repository) (ocm.ComponentVersionAccess, transferhandler.TransferHandler, error) { +func (h *Handler) TransferVersion(repo ocm.Repository, src ocm.ComponentVersionAccess, meta *compdesc.Reference, tgt ocm.Repository) (ocm.ComponentVersionAccess, transferhandler.TransferHandler, error) { if src == nil || h.opts.IsRecursive() { if h.opts.GetScript() == nil { return h.Handler.TransferVersion(repo, src, meta, tgt) diff --git a/api/ocm/tools/transfer/transferhandler/standard/handler.go b/api/ocm/tools/transfer/transferhandler/standard/handler.go index 06020464e..5792f2cd2 100644 --- a/api/ocm/tools/transfer/transferhandler/standard/handler.go +++ b/api/ocm/tools/transfer/transferhandler/standard/handler.go @@ -47,7 +47,7 @@ func (h *Handler) OverwriteVersion(src ocm.ComponentVersionAccess, tgt ocm.Compo return h.opts.IsOverwrite(), nil } -func (h *Handler) TransferVersion(repo ocm.Repository, src ocm.ComponentVersionAccess, meta *compdesc.ComponentReference, tgt ocm.Repository) (ocm.ComponentVersionAccess, transferhandler.TransferHandler, error) { +func (h *Handler) TransferVersion(repo ocm.Repository, src ocm.ComponentVersionAccess, meta *compdesc.Reference, tgt ocm.Repository) (ocm.ComponentVersionAccess, transferhandler.TransferHandler, error) { if src == nil || h.opts.IsRecursive() { if h.opts.IsStopOnExistingVersion() && tgt != nil { if found, err := tgt.ExistsComponentVersion(meta.ComponentName, meta.Version); found || err != nil { diff --git a/api/ocm/tools/transfer/transferhandler/transferhandler.go b/api/ocm/tools/transfer/transferhandler/transferhandler.go index 1a5f72314..260d3f921 100644 --- a/api/ocm/tools/transfer/transferhandler/transferhandler.go +++ b/api/ocm/tools/transfer/transferhandler/transferhandler.go @@ -93,7 +93,7 @@ type TransferHandler interface { OverwriteVersion(src ocm.ComponentVersionAccess, tgt ocm.ComponentVersionAccess) (bool, error) // TransferVersion decides on continuing with a component version (reference). - TransferVersion(repo ocm.Repository, src ocm.ComponentVersionAccess, meta *compdesc.ComponentReference, tgt ocm.Repository) (ocm.ComponentVersionAccess, TransferHandler, error) + TransferVersion(repo ocm.Repository, src ocm.ComponentVersionAccess, meta *compdesc.Reference, tgt ocm.Repository) (ocm.ComponentVersionAccess, TransferHandler, error) // TransferResource decides on the value transport of a resource. TransferResource(src ocm.ComponentVersionAccess, a ocm.AccessSpec, r ocm.ResourceAccess) (bool, error) // TransferSource decides on the value transport of a source. diff --git a/api/ocm/utils.go b/api/ocm/utils.go index 964f22879..67a4256da 100644 --- a/api/ocm/utils.go +++ b/api/ocm/utils.go @@ -95,7 +95,7 @@ func IsIntermediate(spec RepositorySpec) bool { return false } -func ComponentRefKey(ref *compdesc.ComponentReference) common.NameVersion { +func ComponentRefKey(ref *compdesc.Reference) common.NameVersion { return common.NewNameVersion(ref.GetComponentName(), ref.GetVersion()) } diff --git a/cmds/ocm/commands/ocmcmds/common/addhdlrs/refs/elements.go b/cmds/ocm/commands/ocmcmds/common/addhdlrs/refs/elements.go index edea237dd..3c529accd 100644 --- a/cmds/ocm/commands/ocmcmds/common/addhdlrs/refs/elements.go +++ b/cmds/ocm/commands/ocmcmds/common/addhdlrs/refs/elements.go @@ -57,7 +57,7 @@ func (h *ResourceSpecHandler) Set(v ocm.ComponentVersionAccess, r addhdlrs.Eleme if vers == "" { vers = v.GetVersion() } - meta := &compdesc.ComponentReference{ + meta := &compdesc.Reference{ ElementMeta: compdesc.ElementMeta{ Name: spec.Name, Version: vers, diff --git a/cmds/ocm/commands/ocmcmds/references/add/cmd_test.go b/cmds/ocm/commands/ocmcmds/references/add/cmd_test.go index cde4c0db6..db064e9f3 100644 --- a/cmds/ocm/commands/ocmcmds/references/add/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/references/add/cmd_test.go @@ -20,7 +20,7 @@ const ( REF = "github.com/mandelsoft/ref" ) -func CheckReference(env *TestEnv, cd *compdesc.ComponentDescriptor, name string, add ...func(compdesc.ComponentReference)) { +func CheckReference(env *TestEnv, cd *compdesc.ComponentDescriptor, name string, add ...func(compdesc.Reference)) { rs, _ := cd.GetReferencesByName(name) if len(rs) != 1 { Fail(fmt.Sprintf("%d reference(s) with name %s found", len(rs), name), 1) @@ -64,7 +64,7 @@ var _ = Describe("Add references", func() { Expect(err).To(Succeed()) Expect(len(cd.References)).To(Equal(1)) - CheckReference(env, cd, "testdata", func(r compdesc.ComponentReference) { + CheckReference(env, cd, "testdata", func(r compdesc.Reference) { Expect(r.ExtraIdentity).To(Equal(metav1.Identity{"purpose": "test", "label": "local"})) }) }) @@ -127,7 +127,7 @@ labels: labels := metav1.Labels{} labels.Set("test", "value") - CheckReference(env, cd, "testdata", func(r compdesc.ComponentReference) { + CheckReference(env, cd, "testdata", func(r compdesc.Reference) { ExpectWithOffset(2, r.GetLabels()).To(Equal(labels)) }) }) @@ -148,7 +148,7 @@ labels: labels := metav1.Labels{} labels.Set("test", "value") - CheckReference(env, cd, "testdata", func(r compdesc.ComponentReference) { + CheckReference(env, cd, "testdata", func(r compdesc.Reference) { Expect(r.GetLabels()).To(Equal(labels)) }) }) @@ -163,7 +163,7 @@ labels: labels := metav1.Labels{} labels.Set("test", "value") - CheckReference(env, cd, "testdata", func(r compdesc.ComponentReference) { + CheckReference(env, cd, "testdata", func(r compdesc.Reference) { Expect(r.ExtraIdentity).To(Equal(metav1.Identity{"purpose": "test", "label": "local"})) }) }) @@ -179,7 +179,7 @@ labels: labels := metav1.Labels{} labels.Set("purpose", "test", metav1.WithSigning()) labels.Set("label", map[string]interface{}{"local": true}, metav1.WithVersion("v1")) - CheckReference(env, cd, "testdata", func(r compdesc.ComponentReference) { + CheckReference(env, cd, "testdata", func(r compdesc.Reference) { Expect(r.GetLabels()).To(Equal(labels)) }) }) diff --git a/cmds/ocm/commands/ocmcmds/references/common/typehandler.go b/cmds/ocm/commands/ocmcmds/references/common/typehandler.go index 5bb267644..4adeb7a00 100644 --- a/cmds/ocm/commands/ocmcmds/references/common/typehandler.go +++ b/cmds/ocm/commands/ocmcmds/references/common/typehandler.go @@ -9,8 +9,8 @@ import ( "ocm.software/ocm/cmds/ocm/common/utils" ) -func Elem(e interface{}) *compdesc.ComponentReference { - return e.(*elemhdlr.Object).Element.(*compdesc.ComponentReference) +func Elem(e interface{}) *compdesc.Reference { + return e.(*elemhdlr.Object).Element.(*compdesc.Reference) } var OptionsFor = elemhdlr.OptionsFor From 9bdae8ecc44c7114c32129b038856b9fda467e36 Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Fri, 9 Aug 2024 14:19:07 +0200 Subject: [PATCH 2/9] verify digest of retrieved resource --- api/helper/builder/oci_artifactset.go | 49 +++++++++++++++ api/oci/interface.go | 1 + api/ocm/tools/signing/digest.go | 90 +++++++++++++++++++++++++++ api/ocm/tools/signing/digest_test.go | 80 ++++++++++++++++++++++++ 4 files changed, 220 insertions(+) create mode 100644 api/ocm/tools/signing/digest.go create mode 100644 api/ocm/tools/signing/digest_test.go diff --git a/api/helper/builder/oci_artifactset.go b/api/helper/builder/oci_artifactset.go index 55a77fe93..d306ced23 100644 --- a/api/helper/builder/oci_artifactset.go +++ b/api/helper/builder/oci_artifactset.go @@ -1,9 +1,18 @@ package builder import ( + "fmt" + "slices" + + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/cpi" "ocm.software/ocm/api/oci/extensions/repositories/artifactset" "ocm.software/ocm/api/utils/accessio" "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/file" ) const T_OCIARTIFACTSET = "artifact set" @@ -18,3 +27,43 @@ func (b *Builder) ArtifactSet(path string, fmt accessio.FileFormat, f ...func()) r.Annotate(name, value) }}, f) } + +func (b *Builder) ArtifactSetBlob(main string, f ...func()) { + b.expect(b.blob, T_BLOBACCESS) + arch, err := vfs.TempFile(b.FileSystem(), "", "artifact-*.tgz") + b.failOn(err) + b.FileSystem().Remove(arch.Name()) + + r, err := artifactset.Open(accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, arch.Name(), 0o777, accessio.FormatTGZ, accessio.PathFileSystem(b.FileSystem())) + b.failOn(err) + + b.configure(&ociNamespace{NamespaceAccess: &artifactSink{b, arch.Name(), main, r, ""}, kind: T_OCIARTIFACTSET, annofunc: func(name, value string) { + r.Annotate(name, value) + }}, append(f, func() { + b.Annotation(artifactset.MAINARTIFACT_ANNOTATION, main) + })) +} + +type artifactSink struct { + builder *Builder + arch string + main string + oci.NamespaceAccess + mime string +} + +func (s *artifactSink) AddArtifact(art cpi.Artifact, tags ...string) (blobaccess.BlobAccess, error) { + if slices.Contains(tags, s.main) { + s.mime = art.Artifact().MimeType() + } + return s.NamespaceAccess.AddArtifact(art, tags...) +} + +func (s *artifactSink) Close() error { + if s.mime == "" { + s.NamespaceAccess.Close() + return fmt.Errorf("main artifact not defined") + } + *s.builder.blob = blobaccess.ForTemporaryFilePath(artifactset.MediaType(s.mime), s.arch, file.WithFileSystem(s.builder.FileSystem())) + return s.NamespaceAccess.Close() +} diff --git a/api/oci/interface.go b/api/oci/interface.go index 3cde1e783..4c78a52b4 100644 --- a/api/oci/interface.go +++ b/api/oci/interface.go @@ -30,6 +30,7 @@ type ( IntermediateRepositorySpecAspect = internal.IntermediateRepositorySpecAspect GenericRepositorySpec = internal.GenericRepositorySpec ArtifactAccess = internal.ArtifactAccess + Artifact = internal.Artifact NamespaceLister = internal.NamespaceLister NamespaceAccess = internal.NamespaceAccess ManifestAccess = internal.ManifestAccess diff --git a/api/ocm/tools/signing/digest.go b/api/ocm/tools/signing/digest.go new file mode 100644 index 000000000..b313af78f --- /dev/null +++ b/api/ocm/tools/signing/digest.go @@ -0,0 +1,90 @@ +package signing + +import ( + "io" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/accessmethods/none" + "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" +) + +// VerifyResourceDigest verify the digest of a resource taken from a component version. +// The data of the resources (typically after fetching the content) is given by a ocm.DataAccess. +// The digest info is table from the resource described by a component version, which has +// been used to retrieve the data. +// The function returns true if the verification has been executed. If an error occurs, or +// the verification has been failed, an appropriate error occurs. +// If the resource is not signature relevant (false,nil) is returned. +func VerifyResourceDigest(cv ocm.ComponentVersionAccess, i int, bacc ocm.DataAccess) (bool, error) { + octx := cv.GetContext() + cd := cv.GetDescriptor() + raw := &cd.Resources[i] + + acc, err := octx.AccessSpecForSpec(raw.Access) + if err != nil { + return false, errors.Wrapf(err, resMsg(raw, "", "failed getting access for resource")) + } + + if none.IsNone(acc.GetKind()) { + return false, nil + } + if raw.Digest == nil { + return false, errors.ErrNotFound("digest") + } + // special digest notation indicates to not digest the content + if raw.Digest.IsExcluded() { + return false, nil + } + + meth, err := acc.AccessMethod(cv) + if err != nil { + return false, errors.Wrapf(err, resMsg(raw, acc.Describe(octx), "failed creating access for resource")) + } + defer meth.Close() + + meth = NewRedirectedAccessMethod(meth, bacc) + rdigest := raw.Digest + + dtype := DigesterType(rdigest) + req := []ocm.DigesterType{dtype} + + registry := signingattr.Get(octx).HandlerRegistry() + hasher := registry.GetHasher(dtype.HashAlgorithm) + digest, err := octx.BlobDigesters().DetermineDigests(raw.Type, hasher, registry, meth, req...) + if err != nil { + return false, errors.Wrapf(err, resMsg(raw, acc.Describe(octx), "failed determining digest for resource")) + } + if len(digest) == 0 { + return false, errors.Newf(resMsg(raw, acc.Describe(octx), "no digester accepts resource")) + } + if !checkDigest(rdigest, &digest[0]) { + return true, errors.Newf(resMsg(raw, acc.Describe(octx), "calculated resource digest (%+v) mismatches existing digest (%+v) for", &digest[0], rdigest)) + } + return true, nil +} + +type redirectedAccessMethod struct { + ocm.AccessMethod + acc ocm.DataAccess +} + +func NewRedirectedAccessMethod(m ocm.AccessMethod, bacc ocm.DataAccess) ocm.AccessMethod { + return &redirectedAccessMethod{m, bacc} +} + +func (m *redirectedAccessMethod) Close() error { + list := errors.ErrList() + list.Add(m.acc.Close()) + list.Add(m.AccessMethod.Close()) + return list.Result() +} + +func (m *redirectedAccessMethod) Reader() (io.ReadCloser, error) { + return m.acc.Reader() +} + +func (m *redirectedAccessMethod) Get() ([]byte, error) { + return m.acc.Get() +} diff --git a/api/ocm/tools/signing/digest_test.go b/api/ocm/tools/signing/digest_test.go new file mode 100644 index 000000000..13cd48522 --- /dev/null +++ b/api/ocm/tools/signing/digest_test.go @@ -0,0 +1,80 @@ +package signing_test + +import ( + "strings" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + + ociv1 "github.com/opencontainers/image-spec/specs-go/v1" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/digester/digesters/artifact" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/tools/signing" + "ocm.software/ocm/api/tech/signing/hasher/sha256" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" +) + +var _ = Describe("Digest Test Environment", func() { + var env *Builder + + BeforeEach(func() { + env = NewBuilder() + env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { + env.ComponentVersion(COMPONENTA, VERSION, func() { + env.Resource("image", VERSION, resourcetypes.OCI_ARTIFACT, metav1.LocalRelation, func() { + env.ArtifactSetBlob(VERSION, func() { + env.Manifest(VERSION, func() { + env.Config(func() { + env.BlobStringData(ociv1.MediaTypeImageConfig, "{}") + }) + env.Layer(func() { + env.BlobStringData(ociv1.MediaTypeImageLayerGzip, "fake") + }) + }) + }) + }) + }) + }) + }) + + AfterEach(func() { + env.Cleanup() + }) + + It("created oci artifact", func() { + repo := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) + defer Close(repo, "repo") + + cv := Must(repo.LookupComponentVersion(COMPONENTA, VERSION)) + defer Close(cv, "cv") + + Expect(len(cv.GetDescriptor().Resources)).To(Equal(1)) + + rsc := Must(cv.GetResourceByIndex(0)) + + m := Must(rsc.AccessMethod()) + defer Close(m, "meth") + Expect(m.MimeType()).To(Equal(artifactset.MediaType(artdesc.MediaTypeImageManifest))) + + dig := rsc.Meta().Digest + Expect(dig).NotTo(BeNil()) + Expect(dig.HashAlgorithm).To(Equal(sha256.Algorithm)) + Expect(dig.NormalisationAlgorithm).To(Equal(artifact.OciArtifactDigestV1)) + + Expect(Must(signing.VerifyResourceDigest(cv, 0, m))).To(BeTrue()) + + orig := dig.Value + dig.Value = strings.Replace(dig.Value, "a", "b", -1) + done, err := signing.VerifyResourceDigest(cv, 0, m) + dig.Value = orig + Expect(done).To(BeTrue()) + Expect(err).To(HaveOccurred()) + }) +}) From f105cd49c36ac9de8d7603198583e2dac99d98d9 Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Tue, 13 Aug 2024 13:30:12 +0200 Subject: [PATCH 3/9] verification store --- api/ocm/compdesc/generic.go | 31 +++ api/ocm/tools/signing/handle.go | 21 ++ api/ocm/tools/signing/handler_test.go | 13 +- api/ocm/tools/signing/options.go | 19 ++ api/ocm/tools/signing/signing_test.go | 60 ++++++ api/ocm/tools/signing/store.go | 192 ++++++++++++++++++ api/ocm/tools/signing/store_test.go | 83 ++++++++ .../common/options/signoption/option.go | 20 ++ 8 files changed, 437 insertions(+), 2 deletions(-) create mode 100644 api/ocm/compdesc/generic.go create mode 100644 api/ocm/tools/signing/store.go create mode 100644 api/ocm/tools/signing/store_test.go diff --git a/api/ocm/compdesc/generic.go b/api/ocm/compdesc/generic.go new file mode 100644 index 000000000..85fface5b --- /dev/null +++ b/api/ocm/compdesc/generic.go @@ -0,0 +1,31 @@ +package compdesc + +import ( + "encoding/json" + + "github.com/mandelsoft/goutils/generics" +) + +type GenericComponentDescriptor ComponentDescriptor + +var ( + _ json.Marshaler = (*GenericComponentDescriptor)(nil) + _ json.Unmarshaler = (*GenericComponentDescriptor)(nil) +) + +func (g GenericComponentDescriptor) MarshalJSON() ([]byte, error) { + return Encode(generics.Pointer(ComponentDescriptor(g))) +} + +func (g *GenericComponentDescriptor) UnmarshalJSON(bytes []byte) error { + cd, err := Decode(bytes) + if err != nil { + return err + } + *g = *((*GenericComponentDescriptor)(cd)) + return nil +} + +func (g *GenericComponentDescriptor) Descriptor() *ComponentDescriptor { + return (*ComponentDescriptor)(g) +} diff --git a/api/ocm/tools/signing/handle.go b/api/ocm/tools/signing/handle.go index 39023241a..a683e9f8b 100644 --- a/api/ocm/tools/signing/handle.go +++ b/api/ocm/tools/signing/handle.go @@ -277,6 +277,8 @@ func _apply(state WalkingState, nv common.NameVersion, cv ocm.ComponentVersionAc if dig != nil { spec = dig } + + addVerified(state, cd, opts, signatureNames...) } err := ctx.Propagate(spec) if err != nil { @@ -350,6 +352,7 @@ func _apply(state WalkingState, nv common.NameVersion, cv ocm.ComponentVersionAc } else { cd.Signatures = append(cd.Signatures, signature) } + addVerified(state, cd, opts, signatureNames...) } state.Closure[nv] = vi @@ -717,3 +720,21 @@ func GetDigestMode(cd *compdesc.ComponentDescriptor, def ...string) string { } return general.Optional(def...) } + +func addVerified(state WalkingState, cd *compdesc.ComponentDescriptor, opts *Options, signatures ...string) { + if opts.VerifiedStore != nil { + _addVerified(state, common.VersionedElementKey(cd), cd, opts, signatures...) + } +} + +func _addVerified(state WalkingState, ctx common.NameVersion, cd *compdesc.ComponentDescriptor, opts *Options, signatures ...string) { + opts.VerifiedStore.Add(cd, signatures...) + for _, ref := range cd.References { + nv := common.NewNameVersion(ref.ComponentName, ref.Version) + s := state.Get(nv) + rs := s.GetContext(ctx) + if rs != nil { + _addVerified(state, ctx, rs.Descriptor, opts) + } + } +} diff --git a/api/ocm/tools/signing/handler_test.go b/api/ocm/tools/signing/handler_test.go index bfe41265c..6707f84a8 100644 --- a/api/ocm/tools/signing/handler_test.go +++ b/api/ocm/tools/signing/handler_test.go @@ -28,19 +28,28 @@ var _ = Describe("Simple signing handlers", func() { priv, pub = Must2(rsa.CreateKeyPair()) }) - Context("", func() { + Context("standard", func() { BeforeEach(func() { cv = composition.NewComponentVersion(ctx, COMPONENTA, VERSION) MustBeSuccessful(cv.SetResourceBlob(ocm.NewResourceMeta("blob", resourcetypes.PLAIN_TEXT, v1.LocalRelation), blobaccess.ForString(mime.MIME_TEXT, "test data"), "", nil)) }) DescribeTable("rsa handlers", func(kind string) { - Must(signing.SignComponentVersion(cv, "signature", signing.PrivateKey("signature", priv))) + Must(signing.SignComponentVersion(cv, "signature", signing.PrivateKey("signature", priv), signing.SignerByAlgo(kind))) Must(signing.VerifyComponentVersion(cv, "signature", signing.PublicKey("signature", pub))) }, Entry("rsa", rsa.Algorithm), Entry("rsapss", rsa_pss.Algorithm), ) + + It("uses verified store", func() { + store := signing.NewLocalVerifiedStore() + Must(signing.SignComponentVersion(cv, "signature", signing.PrivateKey("signature", priv), signing.UseVerifiedStore(store))) + + cd := store.Get(cv) + Expect(cd).NotTo(BeNil()) + Expect(len(cd.Signatures)).To(Equal(1)) + }) }) Context("non-unique resources", func() { diff --git a/api/ocm/tools/signing/options.go b/api/ocm/tools/signing/options.go index b7fe9a72e..e5a90e1e7 100644 --- a/api/ocm/tools/signing/options.go +++ b/api/ocm/tools/signing/options.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/general" "github.com/mandelsoft/goutils/generics" "ocm.software/ocm/api/datacontext/attrs/rootcertsattr" @@ -431,6 +432,22 @@ func (o *tsaOpt) ApplySigningOption(opts *Options) { //////////////////////////////////////////////////////////////////////////////// +type verifyedstore struct { + store VerifiedStore +} + +// UseVerifiedStore configures a store for providing verify component decariptors. +// If no store is given, a local store is created. +func UseVerifiedStore(s ...VerifiedStore) Option { + return &verifyedstore{general.OptionalDefaulted(NewLocalVerifiedStore(), s...)} +} + +func (o *verifyedstore) ApplySigningOption(opts *Options) { + opts.VerifiedStore = o.store +} + +//////////////////////////////////////////////////////////////////////////////// + type Options struct { Printer common.Printer Update bool @@ -455,6 +472,8 @@ type Options struct { UseTSA bool effectiveRegistry signing.Registry + + VerifiedStore VerifiedStore } var _ Option = (*Options)(nil) diff --git a/api/ocm/tools/signing/signing_test.go b/api/ocm/tools/signing/signing_test.go index 32c988daa..c31c81187 100644 --- a/api/ocm/tools/signing/signing_test.go +++ b/api/ocm/tools/signing/signing_test.go @@ -1232,8 +1232,68 @@ applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v RootCertificates(ca), PKIXIssuer(*issuer)) }) }) + + FContext("verified store", func() { + BeforeEach(func() { + env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { + env.ComponentVersion(COMPONENTA, VERSION, func() { + env.Provider(PROVIDER) + }) + env.ComponentVersion(COMPONENTB, VERSION, func() { + env.Provider(PROVIDER) + env.Reference("refa", COMPONENTA, VERSION) + }) + env.ComponentVersion(COMPONENTC, VERSION, func() { + env.Provider(PROVIDER) + env.Reference("refb", COMPONENTB, VERSION) + }) + }) + }) + + It("rembers all indirectly signed component descriptors", func() { + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) + defer Close(src, "ctf") + + resolver := ocm.NewCompoundResolver(src) + + cv := Must(resolver.LookupComponentVersion(COMPONENTC, VERSION)) + defer Close(cv, "cv") + + store := NewLocalVerifiedStore() + opts := NewOptions( + Sign(signing.DefaultHandlerRegistry().GetSigner(SIGN_ALGO), SIGNATURE), + Resolver(resolver), + VerifyDigests(), + UseVerifiedStore(store), + ) + MustBeSuccessful(opts.Complete(env)) + + pr, buf := common.NewBufferedPrinter() + Must(Apply(pr, nil, cv, opts)) + + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +applying to version "github.com/mandelsoft/ref2:v1"[github.com/mandelsoft/ref2:v1]... + no digest found for "github.com/mandelsoft/ref:v1" + applying to version "github.com/mandelsoft/ref:v1"[github.com/mandelsoft/ref2:v1]... + no digest found for "github.com/mandelsoft/test:v1" + applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/ref2:v1]... + reference 0: github.com/mandelsoft/test:v1: digest SHA-256:5ed8bb27309c3c2fff43f3b0f3ebb56a5737ad6db4bc8ace73c5455cb86faf54[jsonNormalisation/v1] + reference 0: github.com/mandelsoft/ref:v1: digest SHA-256:e85e324ff16bafe26db235567d9232319c36f48ce995aa3f4957e55002207277[jsonNormalisation/v1] +`)) + + CheckStore(store, cv) + CheckStore(store, common.NewNameVersion(COMPONENTB, VERSION)) + CheckStore(store, common.NewNameVersion(COMPONENTA, VERSION)) + }) + }) }) +func CheckStore(store VerifiedStore, ve common.VersionedElement) { + e := store.Get(ve) + ExpectWithOffset(1, e).NotTo(BeNil()) + ExpectWithOffset(1, common.VersionedElementKey(e)).To(Equal(common.VersionedElementKey(ve))) +} + func HashComponent(resolver ocm.ComponentVersionResolver, name string, digest string, other ...Option) string { cv, err := resolver.LookupComponentVersion(name, VERSION) Expect(err).To(Succeed()) diff --git a/api/ocm/tools/signing/store.go b/api/ocm/tools/signing/store.go new file mode 100644 index 000000000..88d70baea --- /dev/null +++ b/api/ocm/tools/signing/store.go @@ -0,0 +1,192 @@ +package signing + +import ( + "io" + "os" + "sync" + + "github.com/mandelsoft/filepath/pkg/filepath" + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/sliceutils" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/utils" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" +) + +type VerifiedStore interface { + Add(cd *compdesc.ComponentDescriptor, signatures ...string) + Remove(n common.VersionedElement) + Get(n common.VersionedElement) *compdesc.ComponentDescriptor + + GetResourceDigest(n common.VersionedElement, id metav1.Identity) *metav1.DigestSpec + GetResourceDigestByIndex(n common.VersionedElement, idx int) *metav1.DigestSpec + + Load() error + Save() error +} + +type verifiedStore struct { + lock sync.Mutex + storage *StorageDescriptor + fs vfs.FileSystem + file string +} + +var _ VerifiedStore = (*verifiedStore)(nil) + +func NewLocalVerifiedStore() VerifiedStore { + return &verifiedStore{storage: &StorageDescriptor{}} +} + +func NewVerifiedStore(path string, fss ...vfs.FileSystem) (VerifiedStore, error) { + eff, err := utils.ResolvePath(path) + if err != nil { + return nil, err + } + + fs := utils.FileSystem(fss...) + + s := &verifiedStore{ + fs: fs, + file: eff, + } + + err = s.Load() + if err != nil { + return nil, err + } + return s, nil +} + +func (v *verifiedStore) Load() error { + v.lock.Lock() + defer v.lock.Unlock() + if v.fs == nil { + return nil + } + + dir := filepath.Dir(v.file) + + if ok, err := vfs.DirExists(v.fs, dir); !ok || err != nil { + if err != nil { + return err + } + return errors.ErrNotFound("directory", dir) + } + + var storage StorageDescriptor + f, err := v.fs.Open(v.file) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return err + } + } else { + defer f.Close() + data, err := io.ReadAll(f) + if err != nil { + return err + } + err = runtime.DefaultYAMLEncoding.Unmarshal(data, &storage) + if err != nil { + return err + } + } + v.storage = &storage + return nil +} + +func (v *verifiedStore) Save() error { + v.lock.Lock() + defer v.lock.Unlock() + if v.fs == nil { + return nil + } + + data, err := runtime.DefaultYAMLEncoding.Marshal(v.storage) + if err != nil { + return err + } + + err = vfs.WriteFile(v.fs, v.file, data, 0o600) + if err != nil { + return err + } + return nil +} + +func (v *verifiedStore) Add(cd *compdesc.ComponentDescriptor, signatures ...string) { + v.lock.Lock() + defer v.lock.Unlock() + + if v.storage == nil { + v.storage = &StorageDescriptor{} + } + if v.storage.ComponentVersions == nil { + v.storage.ComponentVersions = map[string]*StorageEntry{} + } + key := common.VersionedElementKey(cd).String() + old := v.storage.ComponentVersions[key] + if old == nil || !old.Descriptor.Descriptor().Equal(cd) { + old = &StorageEntry{ + Descriptor: (*compdesc.GenericComponentDescriptor)(cd), + } + } + for _, e := range signatures { + old.Signatures = sliceutils.AppendUnique(old.Signatures, e) + } + v.storage.ComponentVersions[key] = old +} + +func (v *verifiedStore) Remove(n common.VersionedElement) { + v.lock.Lock() + defer v.lock.Unlock() + + delete(v.storage.ComponentVersions, common.VersionedElementKey(n).String()) +} + +func (v *verifiedStore) Get(n common.VersionedElement) *compdesc.ComponentDescriptor { + v.lock.Lock() + defer v.lock.Unlock() + + entry := v.storage.ComponentVersions[common.VersionedElementKey(n).String()] + if entry == nil { + return nil + } + return entry.Descriptor.Descriptor() +} + +func (v *verifiedStore) GetResourceDigest(n common.VersionedElement, id metav1.Identity) *metav1.DigestSpec { + cd := v.Get(n) + if cd == nil { + return nil + } + r, err := cd.GetResourceByIdentity(id) + if err != nil { + return nil + } + return r.Digest +} + +func (v *verifiedStore) GetResourceDigestByIndex(n common.VersionedElement, idx int) *metav1.DigestSpec { + cd := v.Get(n) + if cd == nil { + return nil + } + if idx < 0 || idx >= len(cd.Resources) { + return nil + } + return cd.Resources[idx].Digest +} + +type StorageEntry struct { + Signatures []string `json:"signatures,omitempty"` + Descriptor *compdesc.GenericComponentDescriptor `json:"descriptor"` +} + +type StorageDescriptor struct { + ComponentVersions map[string]*StorageEntry `json:"componentVersions,omitempty"` +} diff --git a/api/ocm/tools/signing/store_test.go b/api/ocm/tools/signing/store_test.go new file mode 100644 index 000000000..fba1aada4 --- /dev/null +++ b/api/ocm/tools/signing/store_test.go @@ -0,0 +1,83 @@ +package signing_test + +import ( + "os" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/compdesc/versions/ocm.software/v3alpha1" + v2 "ocm.software/ocm/api/ocm/compdesc/versions/v2" + common "ocm.software/ocm/api/utils/misc" + + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/tools/signing" +) + +var _ = Describe("Store", func() { + Context("persistence", func() { + It("", func() { + file := Must(os.CreateTemp("", "store-*")) + + os.Remove(file.Name()) + store := Must(signing.NewVerifiedStore(file.Name())) + Expect(store).NotTo(BeNil()) + + cd1 := &compdesc.ComponentDescriptor{ + Metadata: compdesc.Metadata{ + ConfiguredVersion: v3alpha1.SchemaVersion, + }, + ComponentSpec: compdesc.ComponentSpec{ + ObjectMeta: metav1.ObjectMeta{ + Name: COMPONENTA, + Version: VERSION, + }, + }, + } + cd2 := &compdesc.ComponentDescriptor{ + Metadata: compdesc.Metadata{ + ConfiguredVersion: v2.SchemaVersion, + }, + ComponentSpec: compdesc.ComponentSpec{ + ObjectMeta: metav1.ObjectMeta{ + Name: COMPONENTA, + Version: VERSION, + }, + }, + } + + store.Add(cd1, "a") + store.Add(cd2, "b") + store.Add(cd2, "c") + + MustBeSuccessful(store.Save()) + + desc := signing.StorageDescriptor{ + ComponentVersions: map[string]*signing.StorageEntry{ + common.VersionedElementKey(cd1).String(): &signing.StorageEntry{ + Signatures: []string{"a"}, + Descriptor: (*compdesc.GenericComponentDescriptor)(cd1), + }, + common.VersionedElementKey(cd2).String(): &signing.StorageEntry{ + Signatures: []string{"b", "c"}, + Descriptor: (*compdesc.GenericComponentDescriptor)(cd2), + }, + }, + } + + data := Must(os.ReadFile(file.Name())) + + Expect(data).To(YAMLEqual(desc)) + + store = Must(signing.NewVerifiedStore(file.Name())) + + Expect(store.Get(cd1)).To(YAMLEqual(cd1)) + Expect(store.Get(cd1).Equal(cd1)).To(BeTrue()) + + Expect(store.Get(cd2)).To(YAMLEqual(cd2)) + Expect(store.Get(cd2).Equal(cd2)).To(BeTrue()) + + }) + }) +}) diff --git a/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go index 6e27d1d0d..aeb6fe4ec 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go @@ -8,6 +8,7 @@ import ( "github.com/spf13/pflag" clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/datacontext/attrs/vfsattr" "ocm.software/ocm/api/ocm/compdesc" "ocm.software/ocm/api/ocm/compdesc/normalizations/jsonv1" "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" @@ -58,6 +59,11 @@ type Option struct { Hash hashoption.Option Keyless bool + + // VerifiedFile is used to remember verify component versions. + VerifiedFile string + RememberVerification bool + Store ocmsign.VerifiedStore } func (o *Option) AddFlags(fs *pflag.FlagSet) { @@ -75,6 +81,8 @@ func (o *Option) AddFlags(fs *pflag.FlagSet) { } fs.BoolVarP(&o.Verify, "verify", "V", o.SignMode, "verify existing digests") fs.BoolVar(&o.Keyless, "keyless", false, "use keyless signing") + fs.BoolVar(&o.RememberVerification, "remember", false, "enable verification store") + fs.StringVarP(&o.VerifiedFile, "verified", "", "~/.ocm/verified", "file used to remember verifications for downloads") } func (o *Option) Configure(ctx clictx.Context) error { @@ -116,6 +124,12 @@ func (o *Option) Configure(ctx clictx.Context) error { return err } + if o.Store == nil && o.RememberVerification { + o.Store, err = ocmsign.NewVerifiedStore(o.VerifiedFile, vfsattr.Get(ctx)) + if err != nil { + return err + } + } return nil } @@ -130,6 +144,10 @@ signature name specified with the option --signature. Alternatively a key can be specified as base64 encoded string if the argument start with the prefix ! or as direct string with the prefix =. + +If the verification store is enabled signed and verified component version are +stored. The digest information of those component versions can then be used +to verify downloaded resources. ` if o.SignMode { @@ -194,4 +212,6 @@ func (o *Option) ApplySigningOption(opts *ocmsign.Options) { } opts.Update = o.Update opts.Keyless = o.Keyless + + opts.VerifiedStore = o.Store } From 9c684a77e393a0b9a0b8ed8aee910ede78fbb07a Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Tue, 13 Aug 2024 20:44:38 +0200 Subject: [PATCH 4/9] verify downloads --- api/ocm/compdesc/generic.go | 4 +- api/ocm/testhelper/resources.go | 14 +- api/ocm/tools/signing/digest.go | 15 ++- api/ocm/tools/signing/signing_test.go | 2 +- api/ocm/tools/signing/store.go | 2 +- api/ocm/tools/signing/store_test.go | 26 ++-- cmds/ocm/commands/ocmcmds/cli/download/cmd.go | 3 +- .../ocmcmds/common/cmds/signing/cmd.go | 9 +- .../common/handlers/elemhdlr/typehandler.go | 5 + .../common/options/signoption/option.go | 28 ++-- .../common/options/storeoption/option.go | 72 +++++++++++ .../ocmcmds/components/sign/cmd_test.go | 53 +++++++- .../ocmcmds/components/verify/cmd_test.go | 112 ++++++++++++---- .../ocmcmds/resources/download/action.go | 26 ++++ .../ocmcmds/resources/download/cmd.go | 10 +- .../ocmcmds/resources/download/cmd_test.go | 120 ++++++++++++++++++ .../ocmcmds/resources/download/options.go | 2 + 17 files changed, 433 insertions(+), 70 deletions(-) create mode 100644 cmds/ocm/commands/ocmcmds/common/options/storeoption/option.go diff --git a/api/ocm/compdesc/generic.go b/api/ocm/compdesc/generic.go index 85fface5b..e9a37171b 100644 --- a/api/ocm/compdesc/generic.go +++ b/api/ocm/compdesc/generic.go @@ -14,11 +14,11 @@ var ( ) func (g GenericComponentDescriptor) MarshalJSON() ([]byte, error) { - return Encode(generics.Pointer(ComponentDescriptor(g))) + return Encode(generics.Pointer(ComponentDescriptor(g)), DefaultJSONCodec) } func (g *GenericComponentDescriptor) UnmarshalJSON(bytes []byte) error { - cd, err := Decode(bytes) + cd, err := Decode(bytes, DefaultJSONCodec) if err != nil { return err } diff --git a/api/ocm/testhelper/resources.go b/api/ocm/testhelper/resources.go index 0d608ad17..ebf19e7dc 100644 --- a/api/ocm/testhelper/resources.go +++ b/api/ocm/testhelper/resources.go @@ -23,9 +23,10 @@ var Digests = testutils.Substitutions{ "D_OTHERDATA": D_OTHERDATA, } -const S_TESTDATA = "testdata" - -const D_TESTDATA = "810ff2fb242a5dee4220f2cb0e6a519891fb67f2f828a6cab4ef8894633b1f50" +const ( + S_TESTDATA = "testdata" + D_TESTDATA = "810ff2fb242a5dee4220f2cb0e6a519891fb67f2f828a6cab4ef8894633b1f50" +) var DS_TESTDATA = TextResourceDigestSpec(D_TESTDATA) @@ -36,9 +37,10 @@ func TestDataResource(env *builder.Builder, funcs ...func()) { }) } -const S_OTHERDATA = "otherdata" - -const D_OTHERDATA = "54b8007913ec5a907ca69001d59518acfd106f7b02f892eabf9cae3f8b2414b4" +const ( + S_OTHERDATA = "otherdata" + D_OTHERDATA = "54b8007913ec5a907ca69001d59518acfd106f7b02f892eabf9cae3f8b2414b4" +) var DS_OTHERDATA = TextResourceDigestSpec(D_OTHERDATA) diff --git a/api/ocm/tools/signing/digest.go b/api/ocm/tools/signing/digest.go index b313af78f..8636166a8 100644 --- a/api/ocm/tools/signing/digest.go +++ b/api/ocm/tools/signing/digest.go @@ -1,13 +1,16 @@ package signing import ( + "fmt" "io" "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/general" "ocm.software/ocm/api/ocm" "ocm.software/ocm/api/ocm/extensions/accessmethods/none" "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" + common "ocm.software/ocm/api/utils/misc" ) // VerifyResourceDigest verify the digest of a resource taken from a component version. @@ -17,11 +20,21 @@ import ( // The function returns true if the verification has been executed. If an error occurs, or // the verification has been failed, an appropriate error occurs. // If the resource is not signature relevant (false,nil) is returned. -func VerifyResourceDigest(cv ocm.ComponentVersionAccess, i int, bacc ocm.DataAccess) (bool, error) { +func VerifyResourceDigest(cv ocm.ComponentVersionAccess, i int, bacc ocm.DataAccess, ostore ...VerifiedStore) (bool, error) { octx := cv.GetContext() cd := cv.GetDescriptor() raw := &cd.Resources[i] + store := general.Optional(ostore...) + if store != nil { + vcd := store.Get(cv) + if vcd == nil { + return false, fmt.Errorf("component version %s not verified", common.VersionedElementKey(cv)) + } + if !vcd.Resources[i].Digest.Equal(raw.Digest) { + return false, fmt.Errorf("component version %s corrupted", common.VersionedElementKey(cv)) + } + } acc, err := octx.AccessSpecForSpec(raw.Access) if err != nil { return false, errors.Wrapf(err, resMsg(raw, "", "failed getting access for resource")) diff --git a/api/ocm/tools/signing/signing_test.go b/api/ocm/tools/signing/signing_test.go index c31c81187..b8dc351ae 100644 --- a/api/ocm/tools/signing/signing_test.go +++ b/api/ocm/tools/signing/signing_test.go @@ -1233,7 +1233,7 @@ applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v }) }) - FContext("verified store", func() { + Context("verified store", func() { BeforeEach(func() { env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { env.ComponentVersion(COMPONENTA, VERSION, func() { diff --git a/api/ocm/tools/signing/store.go b/api/ocm/tools/signing/store.go index 88d70baea..ecf55adfb 100644 --- a/api/ocm/tools/signing/store.go +++ b/api/ocm/tools/signing/store.go @@ -81,7 +81,7 @@ func (v *verifiedStore) Load() error { var storage StorageDescriptor f, err := v.fs.Open(v.file) if err != nil { - if errors.Is(err, os.ErrNotExist) { + if !errors.Is(err, os.ErrNotExist) { return err } } else { diff --git a/api/ocm/tools/signing/store_test.go b/api/ocm/tools/signing/store_test.go index fba1aada4..df2ff1d6b 100644 --- a/api/ocm/tools/signing/store_test.go +++ b/api/ocm/tools/signing/store_test.go @@ -6,13 +6,14 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + + "ocm.software/ocm/api/ocm/compdesc" metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" "ocm.software/ocm/api/ocm/compdesc/versions/ocm.software/v3alpha1" v2 "ocm.software/ocm/api/ocm/compdesc/versions/v2" - common "ocm.software/ocm/api/utils/misc" - - "ocm.software/ocm/api/ocm/compdesc" "ocm.software/ocm/api/ocm/tools/signing" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" ) var _ = Describe("Store", func() { @@ -24,7 +25,7 @@ var _ = Describe("Store", func() { store := Must(signing.NewVerifiedStore(file.Name())) Expect(store).NotTo(BeNil()) - cd1 := &compdesc.ComponentDescriptor{ + cd1 := compdesc.DefaultComponent(&compdesc.ComponentDescriptor{ Metadata: compdesc.Metadata{ ConfiguredVersion: v3alpha1.SchemaVersion, }, @@ -32,20 +33,26 @@ var _ = Describe("Store", func() { ObjectMeta: metav1.ObjectMeta{ Name: COMPONENTA, Version: VERSION, + Provider: metav1.Provider{ + Name: "acme.org", + }, }, }, - } - cd2 := &compdesc.ComponentDescriptor{ + }) + cd2 := compdesc.DefaultComponent(&compdesc.ComponentDescriptor{ Metadata: compdesc.Metadata{ ConfiguredVersion: v2.SchemaVersion, }, ComponentSpec: compdesc.ComponentSpec{ ObjectMeta: metav1.ObjectMeta{ - Name: COMPONENTA, + Name: COMPONENTB, Version: VERSION, + Provider: metav1.Provider{ + Name: "acme.org", + }, }, }, - } + }) store.Add(cd1, "a") store.Add(cd2, "b") @@ -68,7 +75,8 @@ var _ = Describe("Store", func() { data := Must(os.ReadFile(file.Name())) - Expect(data).To(YAMLEqual(desc)) + exp := Must(runtime.DefaultYAMLEncoding.Marshal(desc)) + Expect(data).To(YAMLEqual(exp)) store = Must(signing.NewVerifiedStore(file.Name())) diff --git a/cmds/ocm/commands/ocmcmds/cli/download/cmd.go b/cmds/ocm/commands/ocmcmds/cli/download/cmd.go index 7f4b757c6..4193db9ea 100644 --- a/cmds/ocm/commands/ocmcmds/cli/download/cmd.go +++ b/cmds/ocm/commands/ocmcmds/cli/download/cmd.go @@ -9,6 +9,7 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" "github.com/spf13/cobra" "github.com/spf13/pflag" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/storeoption" clictx "ocm.software/ocm/api/cli" "ocm.software/ocm/api/ocm" @@ -52,7 +53,7 @@ func NewCommand(ctx clictx.Context, names ...string) *cobra.Command { return utils.SetupCommand(&Command{BaseCommand: utils.NewBaseCommand(ctx, versionconstraintsoption.New(true).SetLatest(), repooption.New(), - output.OutputOptions(output.NewOutputs(f), downloadcmd.NewOptions(true).SetUseHandlers(), destoption.New()), + output.OutputOptions(output.NewOutputs(f), downloadcmd.NewOptions(true).SetUseHandlers(), destoption.New(), storeoption.New("use-verified")), )}, utils.Names(Names, names...)...) } diff --git a/cmds/ocm/commands/ocmcmds/common/cmds/signing/cmd.go b/cmds/ocm/commands/ocmcmds/common/cmds/signing/cmd.go index 3cbfafc31..131385546 100644 --- a/cmds/ocm/commands/ocmcmds/common/cmds/signing/cmd.go +++ b/cmds/ocm/commands/ocmcmds/common/cmds/signing/cmd.go @@ -97,7 +97,14 @@ func (o *SignatureCommand) Run() (rerr error) { if err != nil { return err } - return utils.HandleOutput(NewAction(o.spec.terms, o.Context.OCMContext(), common.NewPrinter(o.Context.StdOut()), sopts), handler, utils.StringElemSpecs(o.Refs...)...) + err = utils.HandleOutput(NewAction(o.spec.terms, o.Context.OCMContext(), common.NewPrinter(o.Context.StdOut()), sopts), handler, utils.StringElemSpecs(o.Refs...)...) + if err != nil { + return err + } + if sopts.VerifiedStore != nil { + return errors.Wrapf(sopts.VerifiedStore.Save(), "cannot save verified store %q", signoption.From(o).Verified.File) + } + return nil } ///////////////////////////////////////////////////////////////////////////// diff --git a/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr/typehandler.go b/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr/typehandler.go index dd335e9ad..a6631821c 100644 --- a/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr/typehandler.go +++ b/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr/typehandler.go @@ -21,6 +21,7 @@ type Object struct { VersionId metav1.Identity Spec metav1.Identity + Index int Id metav1.Identity Node *common.NameVersion Element compdesc.ElementMetaAccessor @@ -150,6 +151,7 @@ func (h *TypeHandler) all(c *comphdlr.Object) ([]output.Object, error) { History: c.History.Append(common.VersionedElementKey(c.ComponentVersion)), Version: c.ComponentVersion, VersionId: c.Identity, + Index: i, Id: e.GetMeta().GetIdentity(elemaccess), Element: e, }) @@ -161,6 +163,7 @@ func (h *TypeHandler) all(c *comphdlr.Object) ([]output.Object, error) { History: c.History.Append(common.VersionedElementKey(c.ComponentVersion)), Version: c.ComponentVersion, VersionId: c.Identity, + Index: -1, Id: metav1.Identity{}, Element: nil, }) @@ -203,6 +206,7 @@ func (h *TypeHandler) get(c *comphdlr.Object, elemspec utils.ElemSpec) ([]output History: c.History.Append(common.VersionedElementKey(c.ComponentVersion)), Version: c.ComponentVersion, VersionId: c.Identity, + Index: i, Spec: selector, Id: m.GetIdentity(elemaccess), Element: e, @@ -214,6 +218,7 @@ func (h *TypeHandler) get(c *comphdlr.Object, elemspec utils.ElemSpec) ([]output History: c.History.Append(common.VersionedElementKey(c.ComponentVersion)), Version: c.ComponentVersion, VersionId: c.Identity, + Index: -1, Id: metav1.Identity{}, Element: nil, }) diff --git a/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go index aeb6fe4ec..14723d097 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go @@ -8,7 +8,6 @@ import ( "github.com/spf13/pflag" clictx "ocm.software/ocm/api/cli" - "ocm.software/ocm/api/datacontext/attrs/vfsattr" "ocm.software/ocm/api/ocm/compdesc" "ocm.software/ocm/api/ocm/compdesc/normalizations/jsonv1" "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" @@ -20,6 +19,7 @@ import ( "ocm.software/ocm/api/utils/listformat" "ocm.software/ocm/cmds/ocm/commands/common/options/keyoption" "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/hashoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/storeoption" "ocm.software/ocm/cmds/ocm/common/options" ) @@ -60,14 +60,14 @@ type Option struct { Keyless bool - // VerifiedFile is used to remember verify component versions. - VerifiedFile string - RememberVerification bool - Store ocmsign.VerifiedStore + Verified storeoption.Option } +const DEFAULT_VERIFIED_FILE = "~/.ocm/verified" + func (o *Option) AddFlags(fs *pflag.FlagSet) { o.Option.AddFlags(fs) + o.Verified.AddFlags(fs) fs.StringArrayVarP(&o.SignatureNames, "signature", "s", nil, "signature name") if o.SignMode { o.Hash.AddFlags(fs) @@ -81,8 +81,6 @@ func (o *Option) AddFlags(fs *pflag.FlagSet) { } fs.BoolVarP(&o.Verify, "verify", "V", o.SignMode, "verify existing digests") fs.BoolVar(&o.Keyless, "keyless", false, "use keyless signing") - fs.BoolVar(&o.RememberVerification, "remember", false, "enable verification store") - fs.StringVarP(&o.VerifiedFile, "verified", "", "~/.ocm/verified", "file used to remember verifications for downloads") } func (o *Option) Configure(ctx clictx.Context) error { @@ -124,13 +122,7 @@ func (o *Option) Configure(ctx clictx.Context) error { return err } - if o.Store == nil && o.RememberVerification { - o.Store, err = ocmsign.NewVerifiedStore(o.VerifiedFile, vfsattr.Get(ctx)) - if err != nil { - return err - } - } - return nil + return o.Verified.Configure(ctx) } func (o *Option) Usage() string { @@ -144,11 +136,7 @@ signature name specified with the option --signature. Alternatively a key can be specified as base64 encoded string if the argument start with the prefix ! or as direct string with the prefix =. - -If the verification store is enabled signed and verified component version are -stored. The digest information of those component versions can then be used -to verify downloaded resources. -` +` + o.Verified.Usage() if o.SignMode { s += ` @@ -213,5 +201,5 @@ func (o *Option) ApplySigningOption(opts *ocmsign.Options) { opts.Update = o.Update opts.Keyless = o.Keyless - opts.VerifiedStore = o.Store + opts.VerifiedStore = o.Verified.Store } diff --git a/cmds/ocm/commands/ocmcmds/common/options/storeoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/storeoption/option.go new file mode 100644 index 000000000..64fabc80c --- /dev/null +++ b/cmds/ocm/commands/ocmcmds/common/options/storeoption/option.go @@ -0,0 +1,72 @@ +package storeoption + +import ( + "github.com/mandelsoft/goutils/general" + "github.com/spf13/pflag" + + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + ocmsign "ocm.software/ocm/api/ocm/tools/signing" + "ocm.software/ocm/cmds/ocm/common/options" +) + +func From(o options.OptionSetProvider) *Option { + var opt *Option + o.AsOptionSet().Get(&opt) + return opt +} + +var _ options.Options = (*Option)(nil) + +func New(name ...string) *Option { + return &Option{name: general.OptionalDefaulted("remember-verified", name...)} +} + +type Option struct { + name string + + // File is used to remember verify component versions. + File string + RememberVerification bool + Store ocmsign.VerifiedStore +} + +const DEFAULT_VERIFIED_FILE = "~/.ocm/verified" + +func (o *Option) AddFlags(fs *pflag.FlagSet) { + fs.BoolVar(&o.RememberVerification, o.name, false, "enable verification store") + fs.StringVarP(&o.File, "verified", "", DEFAULT_VERIFIED_FILE, "file used to remember verifications for downloads") +} + +func (o *Option) Configure(ctx clictx.Context) error { + var err error + if o.Store == nil && (o.RememberVerification || o.File != DEFAULT_VERIFIED_FILE) { + o.Store, err = ocmsign.NewVerifiedStore(o.File, vfsattr.Get(ctx)) + if err != nil { + return err + } + } + if o.Store != nil { + o.RememberVerification = true + } + return nil +} + +func (o *Option) Usage() string { + s := ` +If the verification store is enabled, resources downloaded from +signed or verified component versions are verified against their digests +provided by the component version.(not supported for using downloaders for the +resource download). + +The usage of the verification store is enabled by --` + o.name + ` or by +specifying a verification file with --verified. +` + return s +} + +var _ ocmsign.Option = (*Option)(nil) + +func (o *Option) ApplySigningOption(opts *ocmsign.Options) { + opts.VerifiedStore = o.Store +} diff --git a/cmds/ocm/commands/ocmcmds/components/sign/cmd_test.go b/cmds/ocm/commands/ocmcmds/components/sign/cmd_test.go index df586218a..9ff7b3612 100644 --- a/cmds/ocm/commands/ocmcmds/components/sign/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/components/sign/cmd_test.go @@ -9,6 +9,7 @@ import ( . "github.com/onsi/gomega" . "ocm.software/ocm/api/oci/testhelper" . "ocm.software/ocm/api/ocm/testhelper" + common "ocm.software/ocm/api/utils/misc" . "ocm.software/ocm/cmds/ocm/testhelper" "github.com/mandelsoft/vfs/pkg/vfs" @@ -19,6 +20,7 @@ import ( "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/tools/signing" "ocm.software/ocm/api/tech/signing/handlers/rsa" "ocm.software/ocm/api/tech/signing/signutils" "ocm.software/ocm/api/utils/accessio" @@ -54,6 +56,8 @@ const ( D_COMPONENTB = "5f416ec59629d6af91287e2ba13c6360339b6a0acf624af2abd2a810ce4aefce" ) +const VERIFIED_FILE = "verified.yaml" + var substitutions = Substitutions{ "test": D_COMPONENTA, "r0": D_TESTDATA, @@ -108,7 +112,7 @@ var _ = Describe("access method", func() { Expect(r.Meta().Digest).To(Equal(DS_OCIMANIFEST2)) }) - It("sign single component in component archive", func() { + It("signs single component in component archive", func() { prepareEnv(env, ARCH, "") buf := bytes.NewBuffer(nil) @@ -135,7 +139,7 @@ successfully signed github.com/mandelsoft/test:v1 (digest SHA-256:${test})`, Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(D_COMPONENTA)) }) - It("sign component archive", func() { + It("signs transport archive", func() { prepareEnv(env, ARCH, ARCH) buf := bytes.NewBuffer(nil) @@ -165,7 +169,7 @@ successfully signed github.com/mandelsoft/ref:v1 (digest SHA-256:${ref}) Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(D_COMPONENTB)) }) - It("sign component archive with --lookup option", func() { + It("signs transport archive with --lookup option", func() { prepareEnv(env, ARCH2, ARCH) buf := bytes.NewBuffer(nil) @@ -356,8 +360,51 @@ successfully verified github.com/mandelsoft/test:v1 (digest SHA-256:5ed8bb27309c Expect(len(certs)).To(Equal(3)) Expect(algo).To(Equal(rsa.Algorithm)) }) + + Context("verified store", func() { + BeforeEach(func() { + FakeOCIRepo(env.Builder, OCIPATH, OCIHOST) + + env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { + OCIManifest1(env.Builder) + OCIManifest2(env.Builder) + }) + }) + + It("signs transport archive", func() { + 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(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" + applying to version "github.com/mandelsoft/test: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] + resource 0: "name"="otherdata": digest SHA-256:${rb0}[genericBlobDigest/v1] +successfully signed github.com/mandelsoft/ref:v1 (digest SHA-256:${ref}) +`, substitutions)) + + Expect(Must(env.FileExists(VERIFIED_FILE))).To(BeTrue()) + + store := Must(signing.NewVerifiedStore(VERIFIED_FILE, env.FileSystem())) + + CheckStore(store, common.NewNameVersion(COMPONENTA, VERSION)) + CheckStore(store, common.NewNameVersion(COMPONENTB, VERSION)) + }) + }) }) +func CheckStore(store signing.VerifiedStore, ve common.VersionedElement) { + e := store.Get(ve) + ExpectWithOffset(1, e).NotTo(BeNil()) + ExpectWithOffset(1, common.VersionedElementKey(e)).To(Equal(common.VersionedElementKey(ve))) +} + func prepareEnv(env *TestEnv, componentAArchive, componentBArchive string) { env.OCMCommonTransport(componentAArchive, accessio.FormatDirectory, func() { env.Component(COMPONENTA, func() { diff --git a/cmds/ocm/commands/ocmcmds/components/verify/cmd_test.go b/cmds/ocm/commands/ocmcmds/components/verify/cmd_test.go index 2633d1722..ee27993b6 100644 --- a/cmds/ocm/commands/ocmcmds/components/verify/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/components/verify/cmd_test.go @@ -27,6 +27,7 @@ import ( "ocm.software/ocm/api/utils/accessio" "ocm.software/ocm/api/utils/accessobj" "ocm.software/ocm/api/utils/mime" + common "ocm.software/ocm/api/utils/misc" ) const ( @@ -50,6 +51,32 @@ const ( PRIVKEY = "/tmp/priv" ) +const ( + S_TESTDATA = "testdata" + D_TESTDATA = "810ff2fb242a5dee4220f2cb0e6a519891fb67f2f828a6cab4ef8894633b1f50" +) + +const ( + S_OTHERDATA = "otherdata" + D_OTHERDATA = "54b8007913ec5a907ca69001d59518acfd106f7b02f892eabf9cae3f8b2414b4" +) + +const VERIFIED_FILE = "verified.yaml" + +const ( + D_COMPONENTA = "01de99400030e8336020059a435cea4e7fe8f21aad4faf619da882134b85569d" + D_COMPONENTB = "5f416ec59629d6af91287e2ba13c6360339b6a0acf624af2abd2a810ce4aefce" +) + +var substitutions = Substitutions{ + "test": D_COMPONENTA, + "r0": D_TESTDATA, + "r1": DS_OCIMANIFEST1.Value, + "r2": DS_OCIMANIFEST2.Value, + "ref": D_COMPONENTB, + "rb0": D_OTHERDATA, +} + var _ = Describe("access method", func() { var ( env *TestEnv @@ -83,7 +110,7 @@ var _ = Describe("access method", func() { env.Version(VERSION, func() { env.Provider(PROVIDER) env.Resource("testdata", "", "PlainText", metav1.LocalRelation, func() { - env.BlobStringData(mime.MIME_TEXT, "testdata") + env.BlobStringData(mime.MIME_TEXT, S_TESTDATA) }) env.Resource("value", "", resourcetypes.OCI_IMAGE, metav1.LocalRelation, func() { env.Access( @@ -102,7 +129,7 @@ var _ = Describe("access method", func() { env.Version(VERSION, func() { env.Provider(PROVIDER) env.Resource("otherdata", "", "PlainText", metav1.LocalRelation, func() { - env.BlobStringData(mime.MIME_TEXT, "otherdata") + env.BlobStringData(mime.MIME_TEXT, S_OTHERDATA) }) env.Reference("ref", COMPONENTA, VERSION) }) @@ -114,21 +141,16 @@ var _ = Describe("access method", func() { env.Cleanup() }) - It("sign component archive", func() { - buf := bytes.NewBuffer(nil) - digest := "5f416ec59629d6af91287e2ba13c6360339b6a0acf624af2abd2a810ce4aefce" - + Prepare := func() { session := datacontext.NewSession() defer session.Close() - src, err := ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env) - Expect(err).To(Succeed()) - archcloser := session.AddCloser(src) + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) + session.AddCloser(src) resolver := resolvers.NewCompoundResolver(src) - cv, err := resolver.LookupComponentVersion(COMPONENTB, VERSION) - Expect(err).To(Succeed()) - closer := session.AddCloser(cv) + cv := Must(resolver.LookupComponentVersion(COMPONENTB, VERSION)) + session.AddCloser(cv) opts := NewOptions( Sign(signingattr.Get(env.OCMContext()).GetSigner(SIGN_ALGO), SIGNATURE), @@ -137,12 +159,19 @@ var _ = Describe("access method", func() { Update(), VerifyDigests(), ) Expect(opts.Complete(DefaultContext)).To(Succeed()) - dig, err := Apply(nil, nil, cv, opts) - Expect(err).To(Succeed()) - closer.Close() - archcloser.Close() + + dig := Must(Apply(nil, nil, cv, opts)) log.Info("dig result", "dig", dig.String()) - Expect(dig.Value).To(Equal(digest)) + Expect(dig.Value).To(Equal(D_COMPONENTB)) + } + + It("verifies transport archive", func() { + buf := bytes.NewBuffer(nil) + + Prepare() + + session := datacontext.NewSession() + defer session.Close() Expect(env.CatchOutput(buf).Execute("verify", "components", "-V", "-s", SIGNATURE, "-k", PUBKEY, "--repo", ARCH, COMPONENTB+":"+VERSION)).To(Succeed()) @@ -150,12 +179,47 @@ var _ = Describe("access method", func() { applying to version "github.com/mandelsoft/ref:v1"[github.com/mandelsoft/ref:v1]... no digest found for "github.com/mandelsoft/test:v1" applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/ref:v1]... - resource 0: "name"="testdata": digest SHA-256:810ff2fb242a5dee4220f2cb0e6a519891fb67f2f828a6cab4ef8894633b1f50[genericBlobDigest/v1] - resource 1: "name"="value": digest SHA-256:0c4abdb72cf59cb4b77f4aacb4775f9f546ebc3face189b2224a966c8826ca9f[ociArtifactDigest/v1] - resource 2: "name"="ref": digest SHA-256:c2d2dca275c33c1270dea6168a002d67c0e98780d7a54960758139ae19984bd7[ociArtifactDigest/v1] - reference 0: github.com/mandelsoft/test:v1: digest SHA-256:01de99400030e8336020059a435cea4e7fe8f21aad4faf619da882134b85569d[jsonNormalisation/v1] - resource 0: "name"="otherdata": digest SHA-256:54b8007913ec5a907ca69001d59518acfd106f7b02f892eabf9cae3f8b2414b4[genericBlobDigest/v1] -successfully verified github.com/mandelsoft/ref:v1 (digest SHA-256:` + digest + `) -`)) + 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] + resource 0: "name"="otherdata": digest SHA-256:${rb0}[genericBlobDigest/v1] +successfully verified github.com/mandelsoft/ref:v1 (digest SHA-256:${ref}) +`, substitutions)) + }) + + Context("verified store", func() { + + It("signs transport archive", func() { + Prepare() + + buf := bytes.NewBuffer(nil) + Expect(env.CatchOutput(buf).Execute("verify", "components", "--verified", VERIFIED_FILE, "-s", SIGNATURE, "-k", PUBKEY, "--repo", ARCH, COMPONENTB+":"+VERSION)).To(Succeed()) + + 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" + applying to version "github.com/mandelsoft/test: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] + resource 0: "name"="otherdata": digest SHA-256:${rb0}[genericBlobDigest/v1] +successfully verified github.com/mandelsoft/ref:v1 (digest SHA-256:${ref}) +`, substitutions)) + + Expect(Must(env.FileExists(VERIFIED_FILE))).To(BeTrue()) + + store := Must(NewVerifiedStore(VERIFIED_FILE, env.FileSystem())) + + CheckStore(store, common.NewNameVersion(COMPONENTA, VERSION)) + CheckStore(store, common.NewNameVersion(COMPONENTB, VERSION)) + }) }) }) + +func CheckStore(store VerifiedStore, ve common.VersionedElement) { + e := store.Get(ve) + ExpectWithOffset(1, e).NotTo(BeNil()) + ExpectWithOffset(1, common.VersionedElementKey(e)).To(Equal(common.VersionedElementKey(ve))) +} diff --git a/cmds/ocm/commands/ocmcmds/resources/download/action.go b/cmds/ocm/commands/ocmcmds/resources/download/action.go index 27d36e72a..8fada4b0b 100644 --- a/cmds/ocm/commands/ocmcmds/resources/download/action.go +++ b/cmds/ocm/commands/ocmcmds/resources/download/action.go @@ -12,10 +12,13 @@ import ( "ocm.software/ocm/api/ocm" v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" "ocm.software/ocm/api/ocm/extensions/download" + "ocm.software/ocm/api/ocm/tools/signing" + "ocm.software/ocm/api/utils/blobaccess" common2 "ocm.software/ocm/api/utils/misc" "ocm.software/ocm/api/utils/out" "ocm.software/ocm/cmds/ocm/commands/common/options/destoption" "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/storeoption" "ocm.software/ocm/cmds/ocm/commands/ocmcmds/resources/common" "ocm.software/ocm/cmds/ocm/common/options" "ocm.software/ocm/cmds/ocm/common/output" @@ -94,11 +97,16 @@ func (d *Action) Save(o *elemhdlr.Object, f string) error { printer := common2.NewPrinter(d.opts.Context.StdOut()) dest := destoption.From(d.opts) local := From(d.opts) + verify := storeoption.From(d.opts) pathIn := true r := common.Elem(o) if f == "" { pathIn = false } + if verify.Store != nil { + local.Verify = true + } + var tmp vfs.File var err error if f == "-" { @@ -129,6 +137,24 @@ func (d *Action) Save(o *elemhdlr.Object, f string) error { ok, eff, err = d.downloaders.Download(printer, racc, f, dest.PathFilesystem) } else { ok, eff, err = d.downloaders.DownloadAsBlob(printer, racc, f, dest.PathFilesystem) + + if local.Verify { + var done bool + done, err = signing.VerifyResourceDigest(o.Version, o.Index, blobaccess.DataAccessForFile(dest.PathFilesystem, f), verify.Store) + if err != nil { + if done { + printer.Printf("%s: verification failed: %s\n", eff, err) + } else { + printer.Printf("%s: cannot verify: %s\n", eff, err) + } + } else { + if done { + printer.Printf("%s: resource content verified\n", eff) + } else { + printer.Printf("%s: no resource content verification possible\n", eff) + } + } + } } if err != nil { return err diff --git a/cmds/ocm/commands/ocmcmds/resources/download/cmd.go b/cmds/ocm/commands/ocmcmds/resources/download/cmd.go index ec6117660..1f895840c 100644 --- a/cmds/ocm/commands/ocmcmds/resources/download/cmd.go +++ b/cmds/ocm/commands/ocmcmds/resources/download/cmd.go @@ -3,6 +3,7 @@ package download import ( "runtime" + "github.com/mandelsoft/goutils/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -18,6 +19,7 @@ import ( "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/downloaderoption" "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/storeoption" "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" "ocm.software/ocm/cmds/ocm/commands/ocmcmds/resources/common" @@ -50,7 +52,7 @@ func NewCommand(ctx clictx.Context, names ...string) *cobra.Command { versionconstraintsoption.New(), repooption.New(), downloaderoption.New(ctx.OCMContext()), - output.OutputOptions(output.NewOutputs(f), NewOptions(), closureoption.New("component reference"), lookupoption.New(), destoption.New()), + output.OutputOptions(output.NewOutputs(f), NewOptions(), closureoption.New("component reference"), lookupoption.New(), destoption.New(), storeoption.New("check-verified")), )}, utils.Names(Names, names...)...) } @@ -145,6 +147,12 @@ func (o *Command) Run() error { From(opts).UseHandlers = true } + if storeoption.From(o).Store != nil { + if From(opts).UseHandlers { + return errors.Newf("verification fot supported together with download handlers") + } + } + hdlr, err := common.NewTypeHandler(o.Context.OCM(), opts, repooption.From(o).Repository, session, []string{o.Comp}, o.handlerOptions()...) if err != nil { return err diff --git a/cmds/ocm/commands/ocmcmds/resources/download/cmd_test.go b/cmds/ocm/commands/ocmcmds/resources/download/cmd_test.go index f7ed44fe2..c8f52b480 100644 --- a/cmds/ocm/commands/ocmcmds/resources/download/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/resources/download/cmd_test.go @@ -2,10 +2,19 @@ package download_test import ( "bytes" + "os" . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/tools/signing" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/api/utils/accessobj" + common "ocm.software/ocm/api/utils/misc" . "ocm.software/ocm/cmds/ocm/testhelper" "github.com/mandelsoft/vfs/pkg/vfs" @@ -24,6 +33,18 @@ const ( OUT = "/tmp/res" ) +const ( + PUBKEY = "/tmp/pub" + PRIVKEY = "/tmp/priv" +) + +const ( + SIGNATURE = "test" + SIGN_ALGO = rsa.Algorithm +) + +const VERIFIED_FILE = "verified.yaml" + var _ = Describe("Test Environment", func() { var env *TestEnv @@ -117,4 +138,103 @@ var _ = Describe("Test Environment", func() { Expect(env.ReadFile(vfs.Join(env.FileSystem(), OUT, COMP2+"/"+VERSION+"/"+COMP+"/"+VERSION+"/testdata"))).To(Equal([]byte("testdata"))) }) }) + + Context("verification", func() { + priv, pub, err := rsa.Handler{}.CreateKeyPair() + Expect(err).To(Succeed()) + + BeforeEach(func() { + env = NewTestEnv() + data, err := rsa.KeyData(pub) + Expect(err).To(Succeed()) + Expect(vfs.WriteFile(env.FileSystem(), PUBKEY, data, os.ModePerm)).To(Succeed()) + data, err = rsa.KeyData(priv) + Expect(err).To(Succeed()) + Expect(vfs.WriteFile(env.FileSystem(), PRIVKEY, data, os.ModePerm)).To(Succeed()) + + env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { + env.Component(COMP, func() { + env.Version(VERSION, func() { + env.Provider(PROVIDER) + env.Resource("testdata", "", "PlainText", metav1.LocalRelation, func() { + env.BlobStringData(mime.MIME_TEXT, "testdata") + }) + }) + }) + }) + }) + + Prepare := func() { + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) + defer Close(src, "repo") + + resolver := ocm.NewCompoundResolver(src) + cv := Must(resolver.LookupComponentVersion(COMP, VERSION)) + defer Close(cv, "cv") + + opts := signing.NewOptions( + signing.Sign(signingattr.Get(env.OCMContext()).GetSigner(SIGN_ALGO), SIGNATURE), + signing.Resolver(resolver), + signing.PrivateKey(SIGNATURE, priv), + signing.Update(), signing.VerifyDigests(), + ) + Expect(opts.Complete(env.OCMContext())).To(Succeed()) + + Must(signing.Apply(nil, nil, cv, opts)) + } + + It("verifies download after component verification", func() { + buf := bytes.NewBuffer(nil) + + Prepare() + + session := datacontext.NewSession() + defer session.Close() + + Expect(env.CatchOutput(buf).Execute("verify", "components", "--verified", VERIFIED_FILE, "-V", "-s", SIGNATURE, "-k", PUBKEY, "--repo", ARCH, COMP+":"+VERSION)).To(Succeed()) + + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +applying to version "test.de/x:v1"[test.de/x:v1]... + resource 0: "name"="testdata": digest SHA-256:810ff2fb242a5dee4220f2cb0e6a519891fb67f2f828a6cab4ef8894633b1f50[genericBlobDigest/v1] +successfully verified test.de/x:v1 (digest SHA-256:ba5b4af72fcb707a4a8ebc48b088c0d8ed772cb021995b9b1fc7a01fbac29cd2) +`)) + + buf.Reset() + Expect(env.CatchOutput(buf).Execute("download", "resources", "--verified", VERIFIED_FILE, "-O", OUT, ARCH)).To(Succeed()) + Expect(buf.String()).To(StringEqualTrimmedWithContext( + ` +/tmp/res: 8 byte(s) written +/tmp/res: resource content verified +`)) + Expect(env.FileExists(OUT)).To(BeTrue()) + Expect(env.ReadFile(OUT)).To(Equal([]byte("testdata"))) + }) + + It("detects manipulated download after component verification", func() { + buf := bytes.NewBuffer(nil) + + Prepare() + + session := datacontext.NewSession() + defer session.Close() + + Expect(env.CatchOutput(buf).Execute("verify", "components", "--verified", VERIFIED_FILE, "-V", "-s", SIGNATURE, "-k", PUBKEY, "--repo", ARCH, COMP+":"+VERSION)).To(Succeed()) + + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +applying to version "test.de/x:v1"[test.de/x:v1]... + resource 0: "name"="testdata": digest SHA-256:810ff2fb242a5dee4220f2cb0e6a519891fb67f2f828a6cab4ef8894633b1f50[genericBlobDigest/v1] +successfully verified test.de/x:v1 (digest SHA-256:ba5b4af72fcb707a4a8ebc48b088c0d8ed772cb021995b9b1fc7a01fbac29cd2) +`)) + + store := Must(signing.NewVerifiedStore(VERIFIED_FILE, env.FileSystem())) + + cd := store.Get(common.NewNameVersion(COMP, VERSION)) + + cd.Resources[0].Digest.Value = "b" + cd.Resources[0].Digest.Value[1:] + store.Add(cd) + MustBeSuccessful(store.Save()) + + ExpectError(env.Execute("download", "resources", "--verified", VERIFIED_FILE, "-O", OUT, ARCH)).To(MatchError("component version test.de/x:v1 corrupted")) + }) + }) }) diff --git a/cmds/ocm/commands/ocmcmds/resources/download/options.go b/cmds/ocm/commands/ocmcmds/resources/download/options.go index f411d4b4c..4942477e1 100644 --- a/cmds/ocm/commands/ocmcmds/resources/download/options.go +++ b/cmds/ocm/commands/ocmcmds/resources/download/options.go @@ -20,6 +20,7 @@ func NewOptions(silent ...bool) *Option { type Option struct { SilentOption bool UseHandlers bool + Verify bool } func (o *Option) SetUseHandlers(ok ...bool) *Option { @@ -31,6 +32,7 @@ func (o *Option) AddFlags(fs *pflag.FlagSet) { if !o.SilentOption { fs.BoolVarP(&o.UseHandlers, "download-handlers", "d", false, "use download handler if possible") } + fs.BoolVarP(&o.Verify, "verify", "", false, "verify downloads") } func (o *Option) Usage() string { From 1ee415eeee359206b206854bc1c6175220e48c68 Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Thu, 15 Aug 2024 19:06:05 +0200 Subject: [PATCH 5/9] CLI command for verified list --- api/ocm/tools/signing/store.go | 19 +++ cmds/ocm/commands/ocmcmds/cli/download/cmd.go | 2 +- cmds/ocm/commands/ocmcmds/cmd.go | 2 + .../handlers/verifiedhdlr/typehandler.go | 91 +++++++++++++ cmds/ocm/commands/ocmcmds/names/names.go | 1 + .../ocmcmds/resources/download/cmd_test.go | 14 +- cmds/ocm/commands/ocmcmds/verified/cmd.go | 21 +++ cmds/ocm/commands/ocmcmds/verified/get/cmd.go | 106 +++++++++++++++ .../commands/ocmcmds/verified/get/cmd_test.go | 124 ++++++++++++++++++ .../ocmcmds/verified/get/suite_test.go | 13 ++ cmds/ocm/commands/verbs/get/cmd.go | 2 + cmds/ocm/common/output/options.go | 33 +++-- 12 files changed, 407 insertions(+), 21 deletions(-) create mode 100644 cmds/ocm/commands/ocmcmds/common/handlers/verifiedhdlr/typehandler.go create mode 100644 cmds/ocm/commands/ocmcmds/verified/cmd.go create mode 100644 cmds/ocm/commands/ocmcmds/verified/get/cmd.go create mode 100644 cmds/ocm/commands/ocmcmds/verified/get/cmd_test.go create mode 100644 cmds/ocm/commands/ocmcmds/verified/get/suite_test.go diff --git a/api/ocm/tools/signing/store.go b/api/ocm/tools/signing/store.go index ecf55adfb..62d4cd37d 100644 --- a/api/ocm/tools/signing/store.go +++ b/api/ocm/tools/signing/store.go @@ -9,6 +9,7 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/goutils/sliceutils" "github.com/mandelsoft/vfs/pkg/vfs" + "golang.org/x/exp/maps" "ocm.software/ocm/api/ocm/compdesc" metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" @@ -21,10 +22,13 @@ type VerifiedStore interface { Add(cd *compdesc.ComponentDescriptor, signatures ...string) Remove(n common.VersionedElement) Get(n common.VersionedElement) *compdesc.ComponentDescriptor + GetEntry(n common.VersionedElement) *StorageEntry GetResourceDigest(n common.VersionedElement, id metav1.Identity) *metav1.DigestSpec GetResourceDigestByIndex(n common.VersionedElement, idx int) *metav1.DigestSpec + Entries() []common.NameVersion + Load() error Save() error } @@ -118,6 +122,14 @@ func (v *verifiedStore) Save() error { return nil } +func (v *verifiedStore) Entries() []common.NameVersion { + v.lock.Lock() + defer v.lock.Unlock() + + return sliceutils.Transform(maps.Values(v.storage.ComponentVersions), + func(cd *StorageEntry) common.NameVersion { return common.VersionedElementKey(cd.Descriptor) }) +} + func (v *verifiedStore) Add(cd *compdesc.ComponentDescriptor, signatures ...string) { v.lock.Lock() defer v.lock.Unlock() @@ -148,6 +160,13 @@ func (v *verifiedStore) Remove(n common.VersionedElement) { delete(v.storage.ComponentVersions, common.VersionedElementKey(n).String()) } +func (v *verifiedStore) GetEntry(n common.VersionedElement) *StorageEntry { + v.lock.Lock() + defer v.lock.Unlock() + + return v.storage.ComponentVersions[common.VersionedElementKey(n).String()] +} + func (v *verifiedStore) Get(n common.VersionedElement) *compdesc.ComponentDescriptor { v.lock.Lock() defer v.lock.Unlock() diff --git a/cmds/ocm/commands/ocmcmds/cli/download/cmd.go b/cmds/ocm/commands/ocmcmds/cli/download/cmd.go index 4193db9ea..a16033773 100644 --- a/cmds/ocm/commands/ocmcmds/cli/download/cmd.go +++ b/cmds/ocm/commands/ocmcmds/cli/download/cmd.go @@ -9,7 +9,6 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" "github.com/spf13/cobra" "github.com/spf13/pflag" - "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/storeoption" clictx "ocm.software/ocm/api/cli" "ocm.software/ocm/api/ocm" @@ -21,6 +20,7 @@ import ( ocmcommon "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr" "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/storeoption" "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" "ocm.software/ocm/cmds/ocm/commands/ocmcmds/resources/common" diff --git a/cmds/ocm/commands/ocmcmds/cmd.go b/cmds/ocm/commands/ocmcmds/cmd.go index 845e2e524..a35b28540 100644 --- a/cmds/ocm/commands/ocmcmds/cmd.go +++ b/cmds/ocm/commands/ocmcmds/cmd.go @@ -15,6 +15,7 @@ import ( "ocm.software/ocm/cmds/ocm/commands/ocmcmds/routingslips" "ocm.software/ocm/cmds/ocm/commands/ocmcmds/sourceconfig" "ocm.software/ocm/cmds/ocm/commands/ocmcmds/sources" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/verified" "ocm.software/ocm/cmds/ocm/commands/ocmcmds/versions" "ocm.software/ocm/cmds/ocm/common/utils" topicocmaccessmethods "ocm.software/ocm/cmds/ocm/topics/ocm/accessmethods" @@ -40,6 +41,7 @@ func NewCommand(ctx clictx.Context) *cobra.Command { cmd.AddCommand(plugins.NewCommand(ctx)) cmd.AddCommand(routingslips.NewCommand(ctx)) cmd.AddCommand(pubsub.NewCommand(ctx)) + cmd.AddCommand(verified.NewCommand(ctx)) cmd.AddCommand(utils.DocuCommandPath(topicocmrefs.New(ctx), "ocm")) cmd.AddCommand(utils.DocuCommandPath(topicocmaccessmethods.New(ctx), "ocm")) diff --git a/cmds/ocm/commands/ocmcmds/common/handlers/verifiedhdlr/typehandler.go b/cmds/ocm/commands/ocmcmds/common/handlers/verifiedhdlr/typehandler.go new file mode 100644 index 000000000..71ee53ffd --- /dev/null +++ b/cmds/ocm/commands/ocmcmds/common/handlers/verifiedhdlr/typehandler.go @@ -0,0 +1,91 @@ +package pluginhdlr + +import ( + "slices" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/general" + "github.com/mandelsoft/vfs/pkg/vfs" + + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/tools/signing" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/utils" +) + +func Elem(e interface{}) *Object { + return e.(*Object) +} + +//////////////////////////////////////////////////////////////////////////////// + +type Object struct { + Signatures []string `json:"signatures,omitempty"` + Element *compdesc.ComponentDescriptor `json:"descriptor"` +} + +func (o *Object) AsManifest() interface{} { + return o +} + +//////////////////////////////////////////////////////////////////////////////// + +type TypeHandler struct { + octx clictx.OCM + store signing.VerifiedStore +} + +func NewTypeHandler(octx clictx.OCM, path string, fss ...vfs.FileSystem) (utils.TypeHandler, error) { + fs := general.OptionalDefaulted(vfsattr.Get(octx.Context()), fss...) + store, err := signing.NewVerifiedStore(path, fs) + if err != nil { + return nil, err + } + return &TypeHandler{ + octx: octx, + store: store, + }, nil +} + +func (h *TypeHandler) Close() error { + return nil +} + +func (h *TypeHandler) All() ([]output.Object, error) { + result := []output.Object{} + + for _, nv := range h.store.Entries() { + e := h.store.GetEntry(nv) + result = append(result, &Object{slices.Clone(e.Signatures), e.Descriptor.Descriptor().Copy()}) + } + return result, nil +} + +func (h *TypeHandler) Get(elemspec utils.ElemSpec) ([]output.Object, error) { + comp, err := ocm.ParseComp(elemspec.String()) + if err != nil { + return nil, err + } + if comp.IsVersion() { + e := h.store.GetEntry(comp.NameVersion()) + if e == nil { + return nil, errors.ErrNotFound(ocm.KIND_COMPONENTVERSION, elemspec.String()) + } + return []output.Object{&Object{slices.Clone(e.Signatures), e.Descriptor.Descriptor().Copy()}}, nil + } + + var objs []output.Object + for _, nv := range h.store.Entries() { + if nv.GetName() == comp.Component { + e := h.store.GetEntry(nv) + objs = append(objs, &Object{slices.Clone(e.Signatures), e.Descriptor.Descriptor().Copy()}) + } + } + if len(objs) == 0 { + return nil, errors.ErrNotFound(ocm.KIND_COMPONENT, elemspec.String()) + } + return objs, nil +} diff --git a/cmds/ocm/commands/ocmcmds/names/names.go b/cmds/ocm/commands/ocmcmds/names/names.go index 7080e38dc..24927051f 100644 --- a/cmds/ocm/commands/ocmcmds/names/names.go +++ b/cmds/ocm/commands/ocmcmds/names/names.go @@ -16,4 +16,5 @@ var ( Action = []string{"action"} RoutingSlips = []string{"routingslips", "routingslip", "rs"} PubSub = []string{"pubsub", "ps"} + Verified = []string{"verified"} ) diff --git a/cmds/ocm/commands/ocmcmds/resources/download/cmd_test.go b/cmds/ocm/commands/ocmcmds/resources/download/cmd_test.go index c8f52b480..5f1a96008 100644 --- a/cmds/ocm/commands/ocmcmds/resources/download/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/resources/download/cmd_test.go @@ -7,21 +7,21 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + . "ocm.software/ocm/cmds/ocm/testhelper" + + "github.com/mandelsoft/vfs/pkg/vfs" + "ocm.software/ocm/api/datacontext" "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" "ocm.software/ocm/api/ocm/extensions/repositories/ctf" "ocm.software/ocm/api/ocm/tools/signing" "ocm.software/ocm/api/tech/signing/handlers/rsa" - "ocm.software/ocm/api/utils/accessobj" - common "ocm.software/ocm/api/utils/misc" - . "ocm.software/ocm/cmds/ocm/testhelper" - - "github.com/mandelsoft/vfs/pkg/vfs" - - metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" "ocm.software/ocm/api/utils/mime" + common "ocm.software/ocm/api/utils/misc" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/verified/cmd.go b/cmds/ocm/commands/ocmcmds/verified/cmd.go new file mode 100644 index 000000000..26fc5c494 --- /dev/null +++ b/cmds/ocm/commands/ocmcmds/verified/cmd.go @@ -0,0 +1,21 @@ +package verified + +import ( + "github.com/spf13/cobra" + + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/verified/get" + "ocm.software/ocm/cmds/ocm/common/utils" +) + +var Names = names.Verified + +// NewCommand creates a new command. +func NewCommand(ctx clictx.Context) *cobra.Command { + cmd := utils.MassageCommand(&cobra.Command{ + Short: "Commands acting on verified component versions", + }, Names...) + cmd.AddCommand(get.NewCommand(ctx, get.Verb)) + return cmd +} diff --git a/cmds/ocm/commands/ocmcmds/verified/get/cmd.go b/cmds/ocm/commands/ocmcmds/verified/get/cmd.go new file mode 100644 index 000000000..53c5a30d7 --- /dev/null +++ b/cmds/ocm/commands/ocmcmds/verified/get/cmd.go @@ -0,0 +1,106 @@ +package get + +import ( + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + clictx "ocm.software/ocm/api/cli" + handler "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/verifiedhdlr" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/storeoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/processing" + "ocm.software/ocm/cmds/ocm/common/utils" +) + +var ( + Names = names.Verified + Verb = verbs.Get +) + +type Command struct { + utils.BaseCommand + + path string + Names []string +} + +// NewCommand creates a new ctf command. +func NewCommand(ctx clictx.Context, names ...string) *cobra.Command { + return utils.SetupCommand( + &Command{ + BaseCommand: utils.NewBaseCommand(ctx, output.OutputOptions(outputs).SortColumns("VERSION", "COMPONENT")), + }, + utils.Names(Names, names...)..., + ) +} + +func (o *Command) ForName(name string) *cobra.Command { + return &cobra.Command{ + Use: "[] { 0 { + var fields []string + for _, f := range o.sort { + split := strings.Split(f, ",") + for _, s := range split { + s = strings.TrimSpace(s) + if s != "" { + if avail.Contains(s) { + fields = append(fields, s) + } else { + return errors.ErrInvalid("sort field", s) + } } } } + o.Sort = fields } - o.Sort = fields return nil } From ba2da6e988955cf3af9d5ce3ab881af0836152b2 Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Fri, 16 Aug 2024 09:44:20 +0200 Subject: [PATCH 6/9] make generate --- docs/reference/ocm_download_cli.md | 12 +++++ docs/reference/ocm_download_resources.md | 12 +++++ docs/reference/ocm_get.md | 1 + docs/reference/ocm_get_verified.md | 44 +++++++++++++++++++ docs/reference/ocm_ocm.md | 1 + docs/reference/ocm_sign_componentversions.md | 10 +++++ .../reference/ocm_verify_componentversions.md | 10 +++++ 7 files changed, 90 insertions(+) create mode 100644 docs/reference/ocm_get_verified.md diff --git a/docs/reference/ocm_download_cli.md b/docs/reference/ocm_download_cli.md index d6ca2db1f..ff71ec89d 100644 --- a/docs/reference/ocm_download_cli.md +++ b/docs/reference/ocm_download_cli.md @@ -20,6 +20,9 @@ cli, ocmcli, ocm-cli -O, --outfile string output file or directory -p, --path lookup executable in PATH --repo string repository name or spec + --use-verified enable verification store + --verified string file used to remember verifications for downloads (default "~/.ocm/verified") + --verify verify downloads ``` ### Description @@ -89,6 +92,15 @@ can be download directly as helm chart archive, even if stored as OCI artifact. This is handled by download handler. Their usage can be enabled with the --download-handlers option. Otherwise the resource as returned by the access method is stored. + +If the verification store is enabled, resources downloaded from +signed or verified component versions are verified against their digests +provided by the component version.(not supported for using downloaders for the +resource download). + +The usage of the verification store is enabled by --use-verified or by +specifying a verification file with --verified. + ### SEE ALSO #### Parents diff --git a/docs/reference/ocm_download_resources.md b/docs/reference/ocm_download_resources.md index 7a931b4fb..368244159 100644 --- a/docs/reference/ocm_download_resources.md +++ b/docs/reference/ocm_download_resources.md @@ -15,6 +15,7 @@ resources, resource, res, r ### Options ```text + --check-verified enable verification store -c, --constraints constraints version constraint -d, --download-handlers use download handler if possible --downloader = artifact downloader ([:[:]]=--check-verified or by +specifying a verification file with --verified. + ### SEE ALSO #### Parents diff --git a/docs/reference/ocm_get.md b/docs/reference/ocm_get.md index f585f6940..5d358ab90 100644 --- a/docs/reference/ocm_get.md +++ b/docs/reference/ocm_get.md @@ -31,4 +31,5 @@ ocm get [] ... * [ocm get resources](ocm_get_resources.md) — get resources of a component version * [ocm get routingslips](ocm_get_routingslips.md) — get routings slips for a component version * [ocm get sources](ocm_get_sources.md) — get sources of a component version +* [ocm get verified](ocm_get_verified.md) — get verfied component versions diff --git a/docs/reference/ocm_get_verified.md b/docs/reference/ocm_get_verified.md new file mode 100644 index 000000000..0ba5e2890 --- /dev/null +++ b/docs/reference/ocm_get_verified.md @@ -0,0 +1,44 @@ +## ocm get verified — Get Verfied Component Versions + +### Synopsis + +```bash +ocm get verified [] {--output the output mode can be selected. +The following modes are supported: + - (default) + - JSON + - json + - wide + - yaml + +### Examples + +```text +$ ocm get verified +$ ocm get verified -f verified.yaml acme.org/component -o yaml +``` + +### SEE ALSO + +#### Parents + +* [ocm get](ocm_get.md) — Get information about artifacts and components +* [ocm](ocm.md) — Open Component Model command line client + diff --git a/docs/reference/ocm_ocm.md b/docs/reference/ocm_ocm.md index 152f07731..2c1f092df 100644 --- a/docs/reference/ocm_ocm.md +++ b/docs/reference/ocm_ocm.md @@ -32,6 +32,7 @@ ocm ocm [] ... * ocm ocm routingslips — Commands working on routing slips * ocm ocm source-configuration — Commands acting on component source specifications * ocm ocm sources — Commands acting on component sources +* ocm ocm verified — Commands acting on verified component versions * ocm ocm versions — Commands acting on component version names diff --git a/docs/reference/ocm_sign_componentversions.md b/docs/reference/ocm_sign_componentversions.md index 646d92f50..89f5cbd39 100644 --- a/docs/reference/ocm_sign_componentversions.md +++ b/docs/reference/ocm_sign_componentversions.md @@ -15,6 +15,7 @@ componentversions, componentversion, cv, components, component, comps, comp, c ### Options ```text + -- enable verification store -S, --algorithm string signature handler (default "RSASSA-PKCS1-V1_5") --ca-cert stringArray additional root certificate authorities (for signing certificates) -c, --constraints constraints version constraint @@ -33,6 +34,7 @@ componentversions, componentversion, cv, components, component, comps, comp, c --tsa use timestamp authority (default server: http://timestamp.digicert.com) --tsa-url string TSA server URL --update update digest in component versions (default true) + --verified string file used to remember verifications for downloads (default "~/.ocm/verified") -V, --verify verify existing digests (default true) ``` @@ -101,6 +103,14 @@ Alternatively a key can be specified as base64 encoded string if the argument start with the prefix ! or as direct string with the prefix =. +If the verification store is enabled, resources downloaded from +signed or verified component versions are verified against their digests +provided by the component version.(not supported for using downloaders for the +resource download). + +The usage of the verification store is enabled by -- or by +specifying a verification file with --verified. + If in signing mode a public key is specified, existing signatures for the given signature name will be verified, instead of recreated. diff --git a/docs/reference/ocm_verify_componentversions.md b/docs/reference/ocm_verify_componentversions.md index dad9e5b01..3ec362add 100644 --- a/docs/reference/ocm_verify_componentversions.md +++ b/docs/reference/ocm_verify_componentversions.md @@ -15,6 +15,7 @@ componentversions, componentversion, cv, components, component, comps, comp, c ### Options ```text + -- enable verification store --ca-cert stringArray additional root certificate authorities (for signing certificates) -c, --constraints constraints version constraint -h, --help help for componentversions @@ -27,6 +28,7 @@ componentversions, componentversion, cv, components, component, comps, comp, c -k, --public-key stringArray public key setting --repo string repository name or spec -s, --signature stringArray signature name + --verified string file used to remember verifications for downloads (default "~/.ocm/verified") -V, --verify verify existing digests ``` @@ -98,6 +100,14 @@ Alternatively a key can be specified as base64 encoded string if the argument start with the prefix ! or as direct string with the prefix =. +If the verification store is enabled, resources downloaded from +signed or verified component versions are verified against their digests +provided by the component version.(not supported for using downloaders for the +resource download). + +The usage of the verification store is enabled by -- or by +specifying a verification file with --verified. + \ If a component lookup for building a reference closure is required the --lookup option can be used to specify a fallback From 69536e33504cbddd35587e0e49292960fefbfb26 Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Wed, 28 Aug 2024 16:27:45 +0200 Subject: [PATCH 7/9] add docu for verified stores --- api/ocm/tools/signing/store.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/api/ocm/tools/signing/store.go b/api/ocm/tools/signing/store.go index 62d4cd37d..adeef6ac2 100644 --- a/api/ocm/tools/signing/store.go +++ b/api/ocm/tools/signing/store.go @@ -18,6 +18,17 @@ import ( "ocm.software/ocm/api/utils/runtime" ) +// VerifiedStore is an interface for some kind of +// memory providing information about verified +// component versions and the digests of the verified +// artifacts. It is used to verify downloaded resource content, +// without requiring to verify the complete component version, again. +// If the component version has already been marked as being verified +// only the digest of the downloaded content has be compared with the +// digest already marked as verified in the context of its component version. +// +// A typical implementation is a file based store, which stored the serialized +// component versions (see NewVerifiedStore). type VerifiedStore interface { Add(cd *compdesc.ComponentDescriptor, signatures ...string) Remove(n common.VersionedElement) @@ -42,10 +53,12 @@ type verifiedStore struct { var _ VerifiedStore = (*verifiedStore)(nil) +// NewLocalVerifiedStore creates a memory based VerifiedStore. func NewLocalVerifiedStore() VerifiedStore { return &verifiedStore{storage: &StorageDescriptor{}} } +// NewVerifiedStore loads or creates a new filesystem based VerifiedStore. func NewVerifiedStore(path string, fss ...vfs.FileSystem) (VerifiedStore, error) { eff, err := utils.ResolvePath(path) if err != nil { From b5b0743d3a2458bb794c73eaae488452e10f4dc9 Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Tue, 3 Sep 2024 13:32:53 +0200 Subject: [PATCH 8/9] fix AddResolver --- api/ocm/compdesc/resolver.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/ocm/compdesc/resolver.go b/api/ocm/compdesc/resolver.go index df7692c92..791533ff9 100644 --- a/api/ocm/compdesc/resolver.go +++ b/api/ocm/compdesc/resolver.go @@ -92,5 +92,8 @@ func (c *CompoundResolver) LookupComponentVersion(name string, version string) ( func (c *CompoundResolver) AddResolver(r ComponentVersionResolver) { c.lock.Lock() defer c.lock.Unlock() - c.resolvers = append(c.resolvers, r) + + if r != nil { + c.resolvers = append(c.resolvers, r) + } } From 90a3aa0f22f89aafa440f18f905692cbcf7ca5b8 Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Tue, 3 Sep 2024 15:17:34 +0200 Subject: [PATCH 9/9] fix typo in cli docu --- cmds/ocm/commands/ocmcmds/verified/get/cmd.go | 2 +- docs/reference/ocm_get.md | 2 +- docs/reference/ocm_get_verified.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmds/ocm/commands/ocmcmds/verified/get/cmd.go b/cmds/ocm/commands/ocmcmds/verified/get/cmd.go index 53c5a30d7..fcaeb36db 100644 --- a/cmds/ocm/commands/ocmcmds/verified/get/cmd.go +++ b/cmds/ocm/commands/ocmcmds/verified/get/cmd.go @@ -41,7 +41,7 @@ func NewCommand(ctx clictx.Context, names ...string) *cobra.Command { func (o *Command) ForName(name string) *cobra.Command { return &cobra.Command{ Use: "[] {] ... * [ocm get resources](ocm_get_resources.md) — get resources of a component version * [ocm get routingslips](ocm_get_routingslips.md) — get routings slips for a component version * [ocm get sources](ocm_get_sources.md) — get sources of a component version -* [ocm get verified](ocm_get_verified.md) — get verfied component versions +* [ocm get verified](ocm_get_verified.md) — get verified component versions diff --git a/docs/reference/ocm_get_verified.md b/docs/reference/ocm_get_verified.md index 0ba5e2890..039eaba30 100644 --- a/docs/reference/ocm_get_verified.md +++ b/docs/reference/ocm_get_verified.md @@ -1,4 +1,4 @@ -## ocm get verified — Get Verfied Component Versions +## ocm get verified — Get Verified Component Versions ### Synopsis