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/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/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/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/generic.go b/api/ocm/compdesc/generic.go
new file mode 100644
index 000000000..e9a37171b
--- /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)), DefaultJSONCodec)
+}
+
+func (g *GenericComponentDescriptor) UnmarshalJSON(bytes []byte) error {
+ cd, err := Decode(bytes, DefaultJSONCodec)
+ if err != nil {
+ return err
+ }
+ *g = *((*GenericComponentDescriptor)(cd))
+ return nil
+}
+
+func (g *GenericComponentDescriptor) Descriptor() *ComponentDescriptor {
+ return (*ComponentDescriptor)(g)
+}
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..791533ff9
--- /dev/null
+++ b/api/ocm/compdesc/resolver.go
@@ -0,0 +1,99 @@
+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()
+
+ if r != nil {
+ 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/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
new file mode 100644
index 000000000..8636166a8
--- /dev/null
+++ b/api/ocm/tools/signing/digest.go
@@ -0,0 +1,103 @@
+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.
+// 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, 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"))
+ }
+
+ 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())
+ })
+})
diff --git a/api/ocm/tools/signing/handle.go b/api/ocm/tools/signing/handle.go
index 172f18a22..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
@@ -392,7 +395,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)
}
@@ -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..b8dc351ae 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))
})
})
+
+ Context("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..adeef6ac2
--- /dev/null
+++ b/api/ocm/tools/signing/store.go
@@ -0,0 +1,224 @@
+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"
+ "golang.org/x/exp/maps"
+
+ "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"
+)
+
+// 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)
+ 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
+}
+
+type verifiedStore struct {
+ lock sync.Mutex
+ storage *StorageDescriptor
+ fs vfs.FileSystem
+ file string
+}
+
+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 {
+ 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) 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()
+
+ 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) 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()
+
+ 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..df2ff1d6b
--- /dev/null
+++ b/api/ocm/tools/signing/store_test.go
@@ -0,0 +1,91 @@
+package signing_test
+
+import (
+ "os"
+
+ . "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"
+ "ocm.software/ocm/api/ocm/tools/signing"
+ common "ocm.software/ocm/api/utils/misc"
+ "ocm.software/ocm/api/utils/runtime"
+)
+
+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.DefaultComponent(&compdesc.ComponentDescriptor{
+ Metadata: compdesc.Metadata{
+ ConfiguredVersion: v3alpha1.SchemaVersion,
+ },
+ ComponentSpec: compdesc.ComponentSpec{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: COMPONENTA,
+ Version: VERSION,
+ Provider: metav1.Provider{
+ Name: "acme.org",
+ },
+ },
+ },
+ })
+ cd2 := compdesc.DefaultComponent(&compdesc.ComponentDescriptor{
+ Metadata: compdesc.Metadata{
+ ConfiguredVersion: v2.SchemaVersion,
+ },
+ ComponentSpec: compdesc.ComponentSpec{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: COMPONENTB,
+ Version: VERSION,
+ Provider: metav1.Provider{
+ Name: "acme.org",
+ },
+ },
+ },
+ })
+
+ 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()))
+
+ exp := Must(runtime.DefaultYAMLEncoding.Marshal(desc))
+ Expect(data).To(YAMLEqual(exp))
+
+ 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/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/cli/download/cmd.go b/cmds/ocm/commands/ocmcmds/cli/download/cmd.go
index 7f4b757c6..a16033773 100644
--- a/cmds/ocm/commands/ocmcmds/cli/download/cmd.go
+++ b/cmds/ocm/commands/ocmcmds/cli/download/cmd.go
@@ -20,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"
@@ -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/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/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/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/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/common/options/signoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go
index 6e27d1d0d..14723d097 100644
--- a/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go
+++ b/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go
@@ -19,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"
)
@@ -58,10 +59,15 @@ type Option struct {
Hash hashoption.Option
Keyless bool
+
+ 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)
@@ -116,7 +122,7 @@ func (o *Option) Configure(ctx clictx.Context) error {
return err
}
- return nil
+ return o.Verified.Configure(ctx)
}
func (o *Option) Usage() string {
@@ -130,7 +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
=
.
-`
+` + o.Verified.Usage()
if o.SignMode {
s += `
@@ -194,4 +200,6 @@ func (o *Option) ApplySigningOption(opts *ocmsign.Options) {
}
opts.Update = o.Update
opts.Keyless = o.Keyless
+
+ 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/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/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
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..5f1a96008 100644
--- a/cmds/ocm/commands/ocmcmds/resources/download/cmd_test.go
+++ b/cmds/ocm/commands/ocmcmds/resources/download/cmd_test.go
@@ -2,6 +2,7 @@ package download_test
import (
"bytes"
+ "os"
. "github.com/mandelsoft/goutils/testutils"
. "github.com/onsi/ginkgo/v2"
@@ -10,9 +11,17 @@ import (
"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/accessio"
+ "ocm.software/ocm/api/utils/accessobj"
"ocm.software/ocm/api/utils/mime"
+ common "ocm.software/ocm/api/utils/misc"
)
const (
@@ -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 {
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..fcaeb36db
--- /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
}
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..28a479765 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 verified component versions
diff --git a/docs/reference/ocm_get_verified.md b/docs/reference/ocm_get_verified.md
new file mode 100644
index 000000000..039eaba30
--- /dev/null
+++ b/docs/reference/ocm_get_verified.md
@@ -0,0 +1,44 @@
+## ocm get verified — Get Verified 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