From 4b610ea95fc5db39329106b32f341ad2fbb24c64 Mon Sep 17 00:00:00 2001 From: Dhanus M Lal Date: Tue, 9 Jun 2026 14:53:20 +0530 Subject: [PATCH 1/7] fix(plugin): kill plugin server if init fails If plugin initialization fails, kill the plugin process. Otherwise, the plugin server process runs in the background and is not terminated even when the service exits. Also fix typo in logging (q -> %q) Signed-off-by: Dhanus M Lal --- plugin/goplugin_loader.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/goplugin_loader.go b/plugin/goplugin_loader.go index c0d57799..63eee95e 100644 --- a/plugin/goplugin_loader.go +++ b/plugin/goplugin_loader.go @@ -174,7 +174,8 @@ func DiscoverGoPluginUsing[I IPluggable](o *GoPluginLoader) error { o.logger.Debugw("initializing plugin", "plugin", pluginName, "params", params.Map()) if err := pluginContext.Handle.Init(params); err != nil { - o.logger.Errorf("plugin q: %s", pluginName, err.Error()) + o.logger.Errorf("plugin %q: %s", pluginName, err.Error()) + pluginContext.client.Kill() continue } From 0c7d486d397d84af6b3ae10c9e6c91ae30573595 Mon Sep 17 00:00:00 2001 From: Dhanus M Lal Date: Mon, 24 Aug 2026 10:30:46 +0530 Subject: [PATCH 2/7] feat(plugin/parameters): add merge method Add method to join two parameter objects Signed-off-by: Dhanus M Lal --- plugin/parameters.go | 32 ++++++++++++++++++++------------ plugin/parameters_test.go | 27 +++++++++++++++++++++------ 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/plugin/parameters.go b/plugin/parameters.go index 5ec057e8..d27af719 100644 --- a/plugin/parameters.go +++ b/plugin/parameters.go @@ -14,7 +14,7 @@ import ( ) var ( - ErrNotSet = errors.New("parameter not set") + ErrNotSet = errors.New("parameter not set") ErrInvalid = errors.New("invalid parameter value") ) @@ -28,18 +28,18 @@ var ( // // For example: // -// v := viper.New() -// v.Set("s1.p1", "foo") -// v.Set("s2.p1", 1) -// m1, err := ParametersMapFromViper(v, func(n string) string { return strings.ToUpper(n) }) +// v := viper.New() +// v.Set("s1.p1", "foo") +// v.Set("s2.p1", 1) +// m1, err := ParametersMapFromViper(v, func(n string) string { return strings.ToUpper(n) }) // -// m2 := map[string]*Parameters{ -// "S1": NewParameters().SetString("p1", "foo"), -// "S2": NewParameters().SetInt("p1", 1), -// } +// m2 := map[string]*Parameters{ +// "S1": NewParameters().SetString("p1", "foo"), +// "S2": NewParameters().SetInt("p1", 1), +// } // // m1 and m2 above are equivalent. -func ParametersMapFromViper(v *viper.Viper, keyTrans func(string)string) (map[string]*Parameters, error) { +func ParametersMapFromViper(v *viper.Viper, keyTrans func(string) string) (map[string]*Parameters, error) { ret := make(map[string]*Parameters) if v == nil { return ret, nil @@ -180,7 +180,7 @@ func (o *Parameters) Set(key string, value any) error { case float64: // note: due to limited precision, float(9223372036854775807) // is 9223372036854775808.0, so we're using >= rather than > - // here. + // here. // This means that, in practice, the highest float64 // convertable to int64 is 9223372036854774784.0 // ((2^(63-52))/2), as anything above that would round up to @@ -201,6 +201,15 @@ func (o *Parameters) Set(key string, value any) error { return nil } +// Merge populates the Parameters from another Parameters +// object. Duplicate keys are overwritten +func (o *Parameters) Merge(p *Parameters) error { + if p == nil { + return nil + } + return o.PopulateFromMap(p.values) +} + // PopulateFromMap populates the Parameters from the provided map. This is // equivalent to calling Set for each key and corresponding value in the map. func (o *Parameters) PopulateFromMap(m map[string]any) error { @@ -334,7 +343,6 @@ func (o *Parameters) MustGetInt64(key string) int64 { return val } - // DefaultGetInt64 returns the int64 value corresponding to the specified key. // If the key is not set, defaultValue is returned instead. If the value is not // a int64, returns 0 and the error is set to ErrInvalid. diff --git a/plugin/parameters_test.go b/plugin/parameters_test.go index 2034823c..ffb3c164 100644 --- a/plugin/parameters_test.go +++ b/plugin/parameters_test.go @@ -10,14 +10,13 @@ import ( "github.com/stretchr/testify/assert" ) - func TestParameters_round_trip(t *testing.T) { params := NewParameters(). - SetString("p1", "foo"). - SetInt("p2", 1). - SetInt64("p3", 2). - SetBytes("p4", []byte{0xde, 0xad, 0xbe, 0xef}). - SetBool("p5", true) + SetString("p1", "foo"). + SetInt("p2", 1). + SetInt64("p3", 2). + SetBytes("p4", []byte{0xde, 0xad, 0xbe, 0xef}). + SetBool("p5", true) data, err := params.MarshalJSON() assert.NoError(t, err) @@ -174,3 +173,19 @@ func TestPametersMapFromViper(t *testing.T) { _, err = ParametersMapFromViper(v, nil) assert.ErrorIs(t, err, ErrInvalid) } + +func TestParametersMerge(t *testing.T) { + this := NewParameters().SetInt64("one", 1) + var other0 *Parameters // nil + other1 := NewParameters().SetInt64("two", 2) + + err := this.Merge(other0) + assert.NoError(t, err) + + err = this.Merge(other1) + assert.NoError(t, err) + + v, err := this.GetInt64("two") + assert.NoError(t, err) + assert.Equal(t, v, int64(2)) +} From 26614697a695680a0584fd4b88db03944ca220de Mon Sep 17 00:00:00 2001 From: Dhanus M Lal Date: Tue, 9 Jun 2026 15:52:15 +0530 Subject: [PATCH 3/7] feat(plugin): add Fini method in IPluggable Add Fini method in IPlugable that can be called to free the resources used by the plugin. The plugin loader now calls this method before killing the plugin process. Signed-off-by: Dhanus M Lal --- handler/coservproxy_rpc.go | 9 +++++++++ handler/scheme_rpc.go | 13 +++++++++++-- handler/schemeimplementationwrapper.go | 9 +++++++++ plugin/goplugin_context.go | 3 +++ plugin/ipluggable.go | 4 ++++ plugin/test/ammo.go | 18 ++++++++++++++++-- plugin/test/gascartridge/gascartridge.go | 4 ++++ plugin/test/loader_test.go | 2 +- plugin/test/mook.go | 18 ++++++++++++++++-- plugin/test/powercell/powercell.go | 4 ++++ plugin/test/redshirt/redshirt.go | 4 ++++ plugin/test/trooper/trooper.go | 4 ++++ scheme/amd-kds-coserv/coserv_handler.go | 4 ++++ scheme/nvidia-coserv/coserv_handler.go | 4 ++++ 14 files changed, 93 insertions(+), 7 deletions(-) diff --git a/handler/coservproxy_rpc.go b/handler/coservproxy_rpc.go index 1b0ee469..9e8b979c 100644 --- a/handler/coservproxy_rpc.go +++ b/handler/coservproxy_rpc.go @@ -43,6 +43,10 @@ func (o *CoservProxyRPCServer) Init(args []byte, resp *any) error { return o.Impl.Init(params) } +func (o *CoservProxyRPCServer) Fini(args any, resp *any) error { + return o.Impl.Fini() +} + func (s *CoservProxyRPCServer) GetName(args interface{}, resp *string) error { *resp = s.Impl.GetName() return nil @@ -91,6 +95,11 @@ func (o *CoservProxyRPCClient) Init(params *plugin.Parameters) error { return o.client.Call("Plugin.Init", args, &unused) } +func (o *CoservProxyRPCClient) Fini() error { + var unused any + return o.client.Call("Plugin.Fini", unused, &unused) +} + func (c *CoservProxyRPCClient) GetName() string { var ( resp string diff --git a/handler/scheme_rpc.go b/handler/scheme_rpc.go index 4750a3db..53be4e4b 100644 --- a/handler/scheme_rpc.go +++ b/handler/scheme_rpc.go @@ -40,8 +40,8 @@ type SchemeRPCClient struct { func (o *SchemeRPCClient) Init(params *plugin.Parameters) error { var ( unused any - args []byte - err error + args []byte + err error ) if params != nil { @@ -53,6 +53,11 @@ func (o *SchemeRPCClient) Init(params *plugin.Parameters) error { return o.client.Call("Plugin.Init", args, &unused) } +func (o *SchemeRPCClient) Fini() error { + var unused any + return o.client.Call("Plugin.Fini", &unused, &unused) +} + func (o *SchemeRPCClient) GetName() string { var ( unused any @@ -296,6 +301,10 @@ func (o *SchemeRPCServer) Init(args []byte, resp *any) error { return o.Impl.Init(params) } +func (o *SchemeRPCServer) Fini(args any, resp *any) error { + return o.Impl.Fini() +} + func (o *SchemeRPCServer) GetName(unused any, resp *string) error { *resp = o.Impl.GetName() return nil diff --git a/handler/schemeimplementationwrapper.go b/handler/schemeimplementationwrapper.go index b8944545..97e289e4 100644 --- a/handler/schemeimplementationwrapper.go +++ b/handler/schemeimplementationwrapper.go @@ -58,6 +58,15 @@ func (o *SchemeImplementationWrapper) Init(params *plugin.Parameters) error { return nil } +func (o *SchemeImplementationWrapper) Fini() error { + if pluginImpl, ok := o.Impl.(interface { + Fini() error + }); ok { + return pluginImpl.Fini() + } + return nil +} + func (o *SchemeImplementationWrapper) GetName() string { return PluginNameFromScheme(o.Desc.Name) } diff --git a/plugin/goplugin_context.go b/plugin/goplugin_context.go index f8ff2cb9..af3340b9 100644 --- a/plugin/goplugin_context.go +++ b/plugin/goplugin_context.go @@ -64,6 +64,9 @@ func (o PluginContext[I]) GetHandle() interface{} { } func (o PluginContext[I]) Close() { + if err := o.Handle.Fini(); err != nil { + log.Errorf("failed to Fini plugin %s: %v", o.Name, err) + } if o.client != nil { o.client.Kill() } diff --git a/plugin/ipluggable.go b/plugin/ipluggable.go index 9a313540..f82ccae3 100644 --- a/plugin/ipluggable.go +++ b/plugin/ipluggable.go @@ -26,4 +26,8 @@ type IPluggable interface { // free to define their own categories -- as far as IPluggable goes, // categories are just arbitrary groupings of media types. GetSupportedMediaTypes() map[string][]string + + // Releases the resources used by the plugin. This method should be + // called before closing connection to the plugin. + Fini() error } diff --git a/plugin/test/ammo.go b/plugin/test/ammo.go index f4b37cd0..ac346a4d 100644 --- a/plugin/test/ammo.go +++ b/plugin/test/ammo.go @@ -16,6 +16,7 @@ type IAmmo interface { GetAttestationScheme() string GetSupportedMediaTypes() map[string][]string GetCapacity() int + Fini() error } type AmmoRPCClient struct { @@ -25,8 +26,8 @@ type AmmoRPCClient struct { func (o *AmmoRPCClient) Init(params *plugin.Parameters) error { var ( unused any - args []byte - err error + args []byte + err error ) if params != nil { @@ -103,6 +104,15 @@ func (o *AmmoRPCClient) GetCapacity() int { return resp } +func (o *AmmoRPCClient) Fini() error { + var unused any + err := o.client.Call("Plugin.Fini", &unused, &unused) + if err != nil { + log.Printf("Plugin.Fini RPC call failed: %v", err) + } + return err +} + type AmmoRPCServer struct { Impl IAmmo } @@ -145,6 +155,10 @@ func (o *AmmoRPCServer) GetCapacity(args any, resp *int) error { return nil } +func (o *AmmoRPCServer) Fini(args any, resp *any) error { + return o.Impl.Fini() +} + func GetAmmoClient(c *rpc.Client) any { return &AmmoRPCClient{client: c} } diff --git a/plugin/test/gascartridge/gascartridge.go b/plugin/test/gascartridge/gascartridge.go index 1aa02522..256b37a2 100644 --- a/plugin/test/gascartridge/gascartridge.go +++ b/plugin/test/gascartridge/gascartridge.go @@ -30,6 +30,10 @@ func (o GasCartridge) GetCapacity() int { return 500 } +func (o GasCartridge) Fini() error { + return nil +} + func main() { test.RegisterAmmoImplementation(&GasCartridge{}) plugin.Serve() diff --git a/plugin/test/loader_test.go b/plugin/test/loader_test.go index 0dafa310..28588297 100644 --- a/plugin/test/loader_test.go +++ b/plugin/test/loader_test.go @@ -23,7 +23,7 @@ func TestLoader_discover_and_load(t *testing.T) { pluginParams := map[string]*plugin.Parameters{ "Federation Starship Officer": plugin.NewParameters().SetString("sound", "zap"), - "Galactic Imperial Trooper": plugin.NewParameters().SetString("sound", "pew, pew"), + "Galactic Imperial Trooper": plugin.NewParameters().SetString("sound", "pew, pew"), } ldr, err := plugin.CreateGoPluginLoader(cfg, pluginParams, logger) diff --git a/plugin/test/mook.go b/plugin/test/mook.go index 0a23abe5..70cbb132 100644 --- a/plugin/test/mook.go +++ b/plugin/test/mook.go @@ -16,6 +16,7 @@ type IMook interface { GetAttestationScheme() string GetSupportedMediaTypes() map[string][]string Shoot() string + Fini() error } type MookRPCClient struct { @@ -25,8 +26,8 @@ type MookRPCClient struct { func (o *MookRPCClient) Init(params *plugin.Parameters) error { var ( unused any - args []byte - err error + args []byte + err error ) if params != nil { @@ -103,6 +104,15 @@ func (o *MookRPCClient) Shoot() string { return resp } +func (o *MookRPCClient) Fini() error { + var unused any + err := o.client.Call("Plugin.Fini", &unused, &unused) + if err != nil { + log.Printf("Plugin.Fini RPC call failed: %v", err) + } + return err +} + type MookRPCServer struct { Impl IMook } @@ -145,6 +155,10 @@ func (o *MookRPCServer) Shoot(args any, resp *string) error { return nil } +func (o *MookRPCServer) Fini(args any, resp *any) error { + return o.Impl.Fini() +} + func GetMookClient(c *rpc.Client) any { return &MookRPCClient{client: c} } diff --git a/plugin/test/powercell/powercell.go b/plugin/test/powercell/powercell.go index 6cd1bf0c..474a5412 100644 --- a/plugin/test/powercell/powercell.go +++ b/plugin/test/powercell/powercell.go @@ -30,6 +30,10 @@ func (o PowerCell) GetCapacity() int { return 12000000 } +func (o PowerCell) Fini() error { + return nil +} + func main() { test.RegisterAmmoImplementation(&PowerCell{}) plugin.Serve() diff --git a/plugin/test/redshirt/redshirt.go b/plugin/test/redshirt/redshirt.go index 025c95b7..4933ccdb 100644 --- a/plugin/test/redshirt/redshirt.go +++ b/plugin/test/redshirt/redshirt.go @@ -35,6 +35,10 @@ func (o RedShirt) Shoot() string { return fmt.Sprintf("phaser goes %q", o.sound) } +func (o RedShirt) Fini() error { + return nil +} + func main() { test.RegisterMookImplementation(&RedShirt{}) plugin.Serve() diff --git a/plugin/test/trooper/trooper.go b/plugin/test/trooper/trooper.go index 9b1b6642..4a17576f 100644 --- a/plugin/test/trooper/trooper.go +++ b/plugin/test/trooper/trooper.go @@ -36,6 +36,10 @@ func (o ImperialTrooper) Shoot() string { return fmt.Sprintf("blaster goes %q", o.sound) } +func (o ImperialTrooper) Fini() error { + return nil +} + func main() { test.RegisterMookImplementation(&ImperialTrooper{}) plugin.Serve() diff --git a/scheme/amd-kds-coserv/coserv_handler.go b/scheme/amd-kds-coserv/coserv_handler.go index 4531d3d9..8e94f68c 100644 --- a/scheme/amd-kds-coserv/coserv_handler.go +++ b/scheme/amd-kds-coserv/coserv_handler.go @@ -54,6 +54,10 @@ func (s CoservProxyHandler) Init(*plugin.Parameters) error { return nil } +func (s CoservProxyHandler) Fini() error { + return nil +} + func (s CoservProxyHandler) GetName() string { return "amd-kds-coserv-proxy-handler" } diff --git a/scheme/nvidia-coserv/coserv_handler.go b/scheme/nvidia-coserv/coserv_handler.go index 2f501cf2..f20891b1 100644 --- a/scheme/nvidia-coserv/coserv_handler.go +++ b/scheme/nvidia-coserv/coserv_handler.go @@ -41,6 +41,10 @@ func (s CoservProxyHandler) Init(*plugin.Parameters) error { return nil } +func (s CoservProxyHandler) Fini() error { + return nil +} + func (s CoservProxyHandler) GetName() string { return "nvidia-coserv-proxy-handler" } From c8f9c02c53476c7f7b3d7b3ece56ab16e9244843 Mon Sep 17 00:00:00 2001 From: Dhanus M Lal Date: Mon, 25 May 2026 15:52:34 +0530 Subject: [PATCH 4/7] feat(proto): protobuf definitions for store Definition of protobuf messages used as arguments for endorsement store interface. Signed-off-by: Dhanus M Lal --- proto/Makefile | 1 + proto/endorsement_store.pb.go | 251 +++++++++++++++++++++++++++++ proto/endorsement_store.pb.json.go | 40 +++++ proto/endorsement_store.proto | 20 +++ 4 files changed, 312 insertions(+) create mode 100644 proto/endorsement_store.pb.go create mode 100644 proto/endorsement_store.pb.json.go create mode 100644 proto/endorsement_store.proto diff --git a/proto/Makefile b/proto/Makefile index caf76379..12a7073c 100644 --- a/proto/Makefile +++ b/proto/Makefile @@ -11,6 +11,7 @@ PROTOSRCS += vts.proto PROTOSRCS += status.proto PROTOSRCS += endorsement_query.proto PROTOSRCS += scheme.proto +PROTOSRCS += endorsement_store.proto lint-hook-pre: protogen protolint lint $(PROTOSRCS) diff --git a/proto/endorsement_store.pb.go b/proto/endorsement_store.pb.go new file mode 100644 index 00000000..7dd2df2c --- /dev/null +++ b/proto/endorsement_store.pb.go @@ -0,0 +1,251 @@ +// Copyright 2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.21.12 +// source: endorsement_store.proto + +package proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type GetEndorsementsArgs struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Environment []byte `protobuf:"bytes,1,opt,name=environment,json=environment-map,proto3" json:"environment,omitempty"` + Label string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty"` + MatchExactly bool `protobuf:"varint,3,opt,name=match_exactly,json=match-exactly,proto3" json:"match_exactly,omitempty"` +} + +func (x *GetEndorsementsArgs) Reset() { + *x = GetEndorsementsArgs{} + if protoimpl.UnsafeEnabled { + mi := &file_endorsement_store_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetEndorsementsArgs) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetEndorsementsArgs) ProtoMessage() {} + +func (x *GetEndorsementsArgs) ProtoReflect() protoreflect.Message { + mi := &file_endorsement_store_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetEndorsementsArgs.ProtoReflect.Descriptor instead. +func (*GetEndorsementsArgs) Descriptor() ([]byte, []int) { + return file_endorsement_store_proto_rawDescGZIP(), []int{0} +} + +func (x *GetEndorsementsArgs) GetEnvironment() []byte { + if x != nil { + return x.Environment + } + return nil +} + +func (x *GetEndorsementsArgs) GetLabel() string { + if x != nil { + return x.Label + } + return "" +} + +func (x *GetEndorsementsArgs) GetMatchExactly() bool { + if x != nil { + return x.MatchExactly + } + return false +} + +type SubmitEndorsementsArgs struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Endorsement []byte `protobuf:"bytes,1,opt,name=endorsement,json=endorsements,proto3" json:"endorsement,omitempty"` + Label string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty"` + Activate bool `protobuf:"varint,3,opt,name=activate,proto3" json:"activate,omitempty"` +} + +func (x *SubmitEndorsementsArgs) Reset() { + *x = SubmitEndorsementsArgs{} + if protoimpl.UnsafeEnabled { + mi := &file_endorsement_store_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SubmitEndorsementsArgs) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubmitEndorsementsArgs) ProtoMessage() {} + +func (x *SubmitEndorsementsArgs) ProtoReflect() protoreflect.Message { + mi := &file_endorsement_store_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubmitEndorsementsArgs.ProtoReflect.Descriptor instead. +func (*SubmitEndorsementsArgs) Descriptor() ([]byte, []int) { + return file_endorsement_store_proto_rawDescGZIP(), []int{1} +} + +func (x *SubmitEndorsementsArgs) GetEndorsement() []byte { + if x != nil { + return x.Endorsement + } + return nil +} + +func (x *SubmitEndorsementsArgs) GetLabel() string { + if x != nil { + return x.Label + } + return "" +} + +func (x *SubmitEndorsementsArgs) GetActivate() bool { + if x != nil { + return x.Activate + } + return false +} + +var File_endorsement_store_proto protoreflect.FileDescriptor + +var file_endorsement_store_proto_rawDesc = []byte{ + 0x0a, 0x17, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x73, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x74, + 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x22, 0x77, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x64, 0x6f, 0x72, 0x73, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x41, 0x72, 0x67, 0x73, 0x12, 0x24, 0x0a, 0x0b, 0x65, 0x6e, 0x76, 0x69, 0x72, + 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x65, 0x6e, + 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x2d, 0x6d, 0x61, 0x70, 0x12, 0x14, 0x0a, + 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x65, 0x78, 0x61, + 0x63, 0x74, 0x6c, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x6d, 0x61, 0x74, 0x63, + 0x68, 0x2d, 0x65, 0x78, 0x61, 0x63, 0x74, 0x6c, 0x79, 0x22, 0x6d, 0x0a, 0x16, 0x53, 0x75, 0x62, + 0x6d, 0x69, 0x74, 0x45, 0x6e, 0x64, 0x6f, 0x72, 0x73, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x41, + 0x72, 0x67, 0x73, 0x12, 0x21, 0x0a, 0x0b, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x73, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x73, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1a, 0x0a, 0x08, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x42, 0x24, 0x5a, 0x22, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x65, 0x72, 0x61, 0x69, 0x73, 0x6f, 0x6e, 0x2f, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_endorsement_store_proto_rawDescOnce sync.Once + file_endorsement_store_proto_rawDescData = file_endorsement_store_proto_rawDesc +) + +func file_endorsement_store_proto_rawDescGZIP() []byte { + file_endorsement_store_proto_rawDescOnce.Do(func() { + file_endorsement_store_proto_rawDescData = protoimpl.X.CompressGZIP(file_endorsement_store_proto_rawDescData) + }) + return file_endorsement_store_proto_rawDescData +} + +var file_endorsement_store_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_endorsement_store_proto_goTypes = []interface{}{ + (*GetEndorsementsArgs)(nil), // 0: proto.GetEndorsementsArgs + (*SubmitEndorsementsArgs)(nil), // 1: proto.SubmitEndorsementsArgs +} +var file_endorsement_store_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_endorsement_store_proto_init() } +func file_endorsement_store_proto_init() { + if File_endorsement_store_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_endorsement_store_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetEndorsementsArgs); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_endorsement_store_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SubmitEndorsementsArgs); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_endorsement_store_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_endorsement_store_proto_goTypes, + DependencyIndexes: file_endorsement_store_proto_depIdxs, + MessageInfos: file_endorsement_store_proto_msgTypes, + }.Build() + File_endorsement_store_proto = out.File + file_endorsement_store_proto_rawDesc = nil + file_endorsement_store_proto_goTypes = nil + file_endorsement_store_proto_depIdxs = nil +} diff --git a/proto/endorsement_store.pb.json.go b/proto/endorsement_store.pb.json.go new file mode 100644 index 00000000..786d5ad8 --- /dev/null +++ b/proto/endorsement_store.pb.json.go @@ -0,0 +1,40 @@ +// Code generated by protoc-gen-go-json. DO NOT EDIT. +// source: endorsement_store.proto + +package proto + +import ( + "google.golang.org/protobuf/encoding/protojson" +) + +// MarshalJSON implements json.Marshaler +func (msg *GetEndorsementsArgs) MarshalJSON() ([]byte, error) { + return protojson.MarshalOptions{ + UseEnumNumbers: false, + EmitUnpopulated: false, + UseProtoNames: false, + }.Marshal(msg) +} + +// UnmarshalJSON implements json.Unmarshaler +func (msg *GetEndorsementsArgs) UnmarshalJSON(b []byte) error { + return protojson.UnmarshalOptions{ + DiscardUnknown: false, + }.Unmarshal(b, msg) +} + +// MarshalJSON implements json.Marshaler +func (msg *SubmitEndorsementsArgs) MarshalJSON() ([]byte, error) { + return protojson.MarshalOptions{ + UseEnumNumbers: false, + EmitUnpopulated: false, + UseProtoNames: false, + }.Marshal(msg) +} + +// UnmarshalJSON implements json.Unmarshaler +func (msg *SubmitEndorsementsArgs) UnmarshalJSON(b []byte) error { + return protojson.UnmarshalOptions{ + DiscardUnknown: false, + }.Unmarshal(b, msg) +} diff --git a/proto/endorsement_store.proto b/proto/endorsement_store.proto new file mode 100644 index 00000000..12d92bb0 --- /dev/null +++ b/proto/endorsement_store.proto @@ -0,0 +1,20 @@ +// Copyright 2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; + +package proto; + +option go_package = "github.com/veraison/services/proto"; + +message GetEndorsementsArgs { + bytes environment = 1 [json_name = "environment-map"]; + string label = 2 [json_name = "label"]; + bool match_exactly = 3 [json_name = "match-exactly"]; +} + +message SubmitEndorsementsArgs { + bytes endorsement = 1 [json_name = "endorsements"]; + string label = 2 [json_name = "label"]; + bool activate = 3 [json_name = "activate"]; +} From 1e58c4aa65a48f5dc05ecafc1eafe72b365ee1e0 Mon Sep 17 00:00:00 2001 From: Dhanus M Lal Date: Mon, 25 May 2026 15:57:46 +0530 Subject: [PATCH 5/7] feat(handler): definition of IEndorsementStore The store plugin interface definition and implementation of the RPC layer (for go-plugin client and server). Endorsement store interface contains methods for 1. Fetching key and value triples using environment map 2. Fetching CoSERV result by passing a CoSERV query as input 3. Adding CoRIM bytes to the store Signed-off-by: Dhanus M Lal --- handler/README.md | 43 ++++- handler/endorsementstore_rpc.go | 267 ++++++++++++++++++++++++++++++++ handler/error.go | 8 + handler/error_test.go | 10 ++ handler/iendorsementstore.go | 42 +++++ handler/plugin.go | 7 + 6 files changed, 371 insertions(+), 6 deletions(-) create mode 100644 handler/endorsementstore_rpc.go create mode 100644 handler/iendorsementstore.go diff --git a/handler/README.md b/handler/README.md index 058d23e8..9d85042d 100644 --- a/handler/README.md +++ b/handler/README.md @@ -1,8 +1,39 @@ -This package defines [`IEvidenceHandler`](ievidencehandler.go), -[`IEndorsementHandler`](iendorsementhandler.go), -[`IStoreHandler`](istorehandler.go) and [`ICoservHandler`](icoservhandler.go) -[pluggable](../plugin/README.md) interfaces and associated RPC channels. -These are used to add new attestation scheme to Veraison services. -Additionally, the package defines a [couple of wrappers](plugin.go) around +This package defines [`ISchemeHandler`](ischemehandler.go) and +[`IEndorsementStore`](iendorsementstore.go) interfaces and associated RPC +channels. These are used to add new attestation scheme and store backends +to Veraison services. Additionally, the package defines a +[couple of wrappers](plugin.go) around `plugin.RegisterImplementation` for registering implementations of these four interfaces. + + +## Notes on Endorsement Store interface + +The interface has two classes of methods: + +### Get endorsements + +1. `GetKeyTriples` and `GetValueTriples`: query for comid key and value +triples using comid environment map as input. These methods also take a label +as input, which is used to identify the attestation scheme. These methods +must return `ErrUnsupported` if the store backend does not support the method. +This could be because of either the store backend not implementing the method +or not supporting the attestation scheme. If the store backend supports the +method and the scheme, but no matches could be found, these methods must return +`ErrNotFound`. + +2. `ExecuteCoservQuery`: obtain CoSERV results as output using the base64url +encoded CoSERV query as input. This method also takes a profile as input, which +is used to identify the CoSERV profile. This method must return `ErrUnsupported` +if the store backend does not support the method or if the CoSERV profile is +not supported by the store backend. If the store backend supports the method and +the CoSERV profile, but no match could be found for the requested query, this +method must return `ErrNotFound`. This method should never return a CoSERV +containing empty results (empty AKQ or RVQ). + + +### Submit endorsements + +The method `AddCorimBytes` takes the CoRIM bytes as input along with the +scheme. The implementation must return `ErrUnsupported` if it does not support +the operation or the attestation scheme. diff --git a/handler/endorsementstore_rpc.go b/handler/endorsementstore_rpc.go new file mode 100644 index 00000000..07c7be98 --- /dev/null +++ b/handler/endorsementstore_rpc.go @@ -0,0 +1,267 @@ +// Copyright 2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package handler + +import ( + "encoding/json" + "net/rpc" + + "github.com/fxamacker/cbor/v2" + "github.com/veraison/corim/comid" + "github.com/veraison/corim/coserv" + "github.com/veraison/services/log" + "github.com/veraison/services/plugin" + "github.com/veraison/services/proto" + "go.uber.org/zap" +) + +var EndorsementStoreRPC = &plugin.RPCChannel[IEndorsementStorePlugin]{ + GetClient: getStoreClient, + GetServer: getStoreServer, +} + +func getStoreServer(i IEndorsementStorePlugin) any { + return &StoreRPCServer{Impl: i} +} + +func getStoreClient(c *rpc.Client) any { + return &StoreRPCClient{ + client: c, + logger: log.Named("endorsementstore-rpc"), + } +} + +type StoreRPCServer struct { + Impl IEndorsementStorePlugin +} + +func (s *StoreRPCServer) Init(args []byte, resp *any) error { + var ( + params *plugin.Parameters + err error + ) + if args == nil { + params = plugin.NewParameters() + } else { + if params, err = plugin.ParametersFromJSON(args); err != nil { + return err + } + } + + return s.Impl.Init(params) +} + +func (s *StoreRPCServer) Fini(args any, resp *any) error { + return s.Impl.Fini() +} + +func (s *StoreRPCServer) GetName(args any, resp *string) error { + *resp = s.Impl.GetName() + return nil +} + +func (s *StoreRPCServer) GetAttestationScheme(args any, resp *string) error { + *resp = s.Impl.GetAttestationScheme() + return nil +} + +func (s *StoreRPCServer) GetSupportedMediaTypes(args any, resp *[]byte) error { + var err error + mts := s.Impl.GetSupportedMediaTypes() + + *resp, err = json.Marshal(mts) + return err +} + +func (s *StoreRPCServer) GetValueTriples(params *proto.GetEndorsementsArgs, resp *[]byte) error { + var env comid.Environment + if err := (&env).FromCBOR(params.Environment); err != nil { + return err + } + refVals, err := s.Impl.GetValueTriples(&env, params.Label, params.MatchExactly) + if err != nil { + return err + } + *resp, err = cbor.Marshal(refVals) + return err +} + +func (s *StoreRPCServer) GetKeyTriples(params *proto.GetEndorsementsArgs, resp *[]byte) error { + var env comid.Environment + if err := (&env).FromCBOR(params.Environment); err != nil { + return err + } + keys, err := s.Impl.GetKeyTriples(&env, params.Label, params.MatchExactly) + if err != nil { + return err + } + *resp, err = cbor.Marshal(keys) + return err +} + +func (s *StoreRPCServer) ExecuteCoservQuery(params *proto.EndorsementQueryIn, resp *[]byte) error { + cos, err := s.Impl.ExecuteCoservQuery(params.MediaType, params.Query) + if err != nil { + return err + } + *resp, err = cos.ToCBOR() + if err != nil { + return err + } + return nil +} + +func (s *StoreRPCServer) AddCorimBytes(params *proto.SubmitEndorsementsArgs, resp *any) error { + return s.Impl.AddCorimBytes(params.Endorsement, params.Label, params.Activate) +} + +type StoreRPCClient struct { + client *rpc.Client + logger *zap.SugaredLogger +} + +func (c *StoreRPCClient) Init(params *plugin.Parameters) error { + var ( + unused any + args []byte + err error + ) + + if params != nil { + if args, err = params.MarshalJSON(); err != nil { + return err + } + } + + return c.client.Call("Plugin.Init", args, &unused) +} + +func (c *StoreRPCClient) Fini() error { + var unused any + return c.client.Call("Plugin.Fini", &unused, &unused) +} + +func (c *StoreRPCClient) GetName() string { + var ( + resp string + unused any + ) + + err := c.client.Call("Plugin.GetName", &unused, &resp) + if err != nil { + log.Errorf("Plugin.GetName RPC call failed: %v", err) + return "" + } + + return resp +} + +func (c *StoreRPCClient) GetAttestationScheme() string { + var ( + resp string + unused any + ) + + err := c.client.Call("Plugin.GetAttestationScheme", &unused, &resp) + if err != nil { + log.Errorf("Plugin.GetAttestationScheme RPC call failed: %v", err) + return "" + } + + return resp +} + +func (c *StoreRPCClient) GetSupportedMediaTypes() map[string][]string { + var ( + resp []byte + unused any + ) + + err := c.client.Call("Plugin.GetSupportedMediaTypes", &unused, &resp) + if err != nil { + log.Errorf("Plugin.GetSupportedMediaTypes RPC call failed: %v", err) + return nil + } + + var ret map[string][]string + if err := json.Unmarshal(resp, &ret); err != nil { + log.Error(err) + } + + return ret +} + +func (c *StoreRPCClient) GetValueTriples(env *comid.Environment, label string, exact bool) ([]*comid.ValueTriple, error) { + c.logger.Debugw("value triples lookup", "environment", env) + envCbor, err := cbor.Marshal(env) + if err != nil { + return nil, err + } + args := proto.GetEndorsementsArgs{ + Environment: envCbor, + Label: label, + MatchExactly: exact, + } + var rawResp []byte + if err := c.client.Call("Plugin.GetValueTriples", &args, &rawResp); err != nil { + return nil, ParseError(err) + } + var ret []*comid.ValueTriple + if err := cbor.Unmarshal(rawResp, &ret); err != nil { + return nil, err + } + return ret, nil +} + +func (c *StoreRPCClient) GetKeyTriples(env *comid.Environment, label string, exact bool) ([]*comid.KeyTriple, error) { + c.logger.Debugw("key triples lookup", "environment", env) + envCbor, err := cbor.Marshal(env) + if err != nil { + return nil, err + } + args := proto.GetEndorsementsArgs{ + Environment: envCbor, + Label: label, + MatchExactly: exact, + } + var rawResp []byte + if err := c.client.Call("Plugin.GetKeyTriples", &args, &rawResp); err != nil { + return nil, ParseError(err) + } + var ret []*comid.KeyTriple + if err := cbor.Unmarshal(rawResp, &ret); err != nil { + return nil, err + } + return ret, nil +} + +func (c *StoreRPCClient) ExecuteCoservQuery(mediaType, query string) (*coserv.Coserv, error) { + c.logger.Debugw("coserv request", "media-type", mediaType, "query", query) + args := proto.EndorsementQueryIn{ + MediaType: mediaType, + Query: query, + } + + var rawResp []byte + if err := c.client.Call("Plugin.ExecuteCoservQuery", &args, &rawResp); err != nil { + return nil, ParseError(err) + } + var ret coserv.Coserv + if err := ret.FromCBOR(rawResp); err != nil { + return nil, err + } + return &ret, nil +} + +func (c *StoreRPCClient) AddCorimBytes(data []byte, label string, activate bool) error { + args := proto.SubmitEndorsementsArgs{ + Endorsement: data, + Label: label, + Activate: activate, + } + var unused any + if err := c.client.Call("Plugin.AddCorimBytes", &args, &unused); err != nil { + return ParseError(err) + } + return nil +} diff --git a/handler/error.go b/handler/error.go index c520c517..80936b4b 100644 --- a/handler/error.go +++ b/handler/error.go @@ -193,6 +193,14 @@ func ParseError(err error) error { return BadEvidenceError{msg[14:]} } + if msg == ErrNotFound.Error() { + return ErrNotFound + } + + if msg == ErrUnsupported.Error() { + return ErrUnsupported + } + var bee BadEvidenceError var decErr error if decErr = json.Unmarshal([]byte(msg), &bee); decErr == nil { diff --git a/handler/error_test.go b/handler/error_test.go index b2a177c0..e1243850 100644 --- a/handler/error_test.go +++ b/handler/error_test.go @@ -98,3 +98,13 @@ func Test_BadEvidenceError_unmarshalable(t *testing.T) { assert.True(t, ok) assert.Contains(t, parsed.Detail, "0x") } + +func Test_StoreErrorParse(t *testing.T) { + enf := errors.New(ErrNotFound.Error()) + eusp := errors.New(ErrUnsupported.Error()) + eother := errors.New("Other error") + + assert.ErrorIs(t, ParseError(enf), ErrNotFound) + assert.ErrorIs(t, ParseError(eusp), ErrUnsupported) + assert.Equal(t, ParseError(eother).Error(), eother.Error()) +} diff --git a/handler/iendorsementstore.go b/handler/iendorsementstore.go new file mode 100644 index 00000000..94e0ed56 --- /dev/null +++ b/handler/iendorsementstore.go @@ -0,0 +1,42 @@ +// Copyright 2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package handler + +import ( + "errors" + + "github.com/veraison/corim/comid" + "github.com/veraison/corim/coserv" + "github.com/veraison/services/plugin" +) + +var ( + // When the store does not support the operation. + // Example 1: store supportes scheme A and C, but + // endorsements for scheme B is requested. + // + // Example 2: store does not support CoSERV but the + // ExecuteCoservQuery is called. + ErrUnsupported = errors.New("store does not support the operation") + + // Not found in store. This means the store supports the operation + // and the attestation scheme, but no matching values were found + // in the store. + ErrNotFound = errors.New("not found in store") +) + +type IEndorsementStorePlugin interface { + plugin.IPluggable + + IEndorsementStore +} + +type IEndorsementStore interface { + GetKeyTriples(env *comid.Environment, scheme string, exact bool) ([]*comid.KeyTriple, error) + + GetValueTriples(env *comid.Environment, scheme string, exact bool) ([]*comid.ValueTriple, error) + + ExecuteCoservQuery(profile, query string) (*coserv.Coserv, error) + + AddCorimBytes(data []byte, scheme string, activate bool) error +} diff --git a/handler/plugin.go b/handler/plugin.go index c7ffc2a3..d584e5c5 100644 --- a/handler/plugin.go +++ b/handler/plugin.go @@ -28,3 +28,10 @@ func RegisterSchemeImplementation(desc SchemeDescriptor, i ISchemeImplementation RegisterSchemeHandler(wrapper) } + +func RegisterEndorsementStore(i IEndorsementStorePlugin) { + err := plugin.RegisterImplementation("endorsement-store", i, EndorsementStoreRPC) + if err != nil { + panic(err) + } +} From d5295318e942819f3bb86dc87f9ae66745e619bc Mon Sep 17 00:00:00 2001 From: Dhanus M Lal Date: Mon, 14 Sep 2026 14:11:27 +0530 Subject: [PATCH 6/7] fix(plugin): set plugin class when creating manager CreateGoPluginManager now takes pluginClass as an additional input, which specifies the subsection under plugin that should be used by the manager to load plugins from. Signed-off-by: Dhanus M Lal --- management/policy.go | 2 +- plugin/README.md | 11 ++++++++++- plugin/goplugin_manager.go | 17 +++++++++++++++-- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/management/policy.go b/management/policy.go index a46c2fbd..a54dcb69 100644 --- a/management/policy.go +++ b/management/policy.go @@ -48,7 +48,7 @@ func CreatePolicyManagerFromConfig(v *viper.Viper, name string) (*PolicyManager, if config.SchemeLoader == "plugins" { // nolint:gocritic pluginManager, err = plugin.CreateGoPluginManager( - subs["plugin"], pluginConfig, log.Named("plugin"), + subs["plugin"], "go-plugin", pluginConfig, log.Named("plugin"), "scheme-handler", handler.SchemeHandlerRPC) if err != nil { log.Fatalf("plugin manager initialization failed: %v", err) diff --git a/plugin/README.md b/plugin/README.md index 96a469f2..41d2668b 100644 --- a/plugin/README.md +++ b/plugin/README.md @@ -143,8 +143,17 @@ func main() { log.Fatal(err) } + // Extract plugin configuration + pluginConfig, err := plugin.ParametersMapFromViper(subs["plugin-type"], nil) + if err != nil { + log.Fatal(err) + } + pluginManager, err := plugin.CreateGoPluginManager( - subs["plugin"], log.Named("plugin"), + subs["plugin"], + "go-plugin-myplugin", // subs["plugin"]["go-plugin-myplugin"] must not be empty + pluginConfig, + log.Named("plugin"), // plugins must register themselves with type "my-plugin" -- see // above. "my-plugins", myplugin.MyPluginRPC) diff --git a/plugin/goplugin_manager.go b/plugin/goplugin_manager.go index fc92ecb3..107e93e0 100644 --- a/plugin/goplugin_manager.go +++ b/plugin/goplugin_manager.go @@ -26,20 +26,33 @@ func NewGoPluginManager[I IPluggable]( return &GoPluginManager[I]{loader: loader, logger: logger} } +// CreateGoPluginManager create a new GoPluginManager for the provided plugin +// RPCChannel ch. In addition to the RPCChannel, it takes the the following +// additional inputs to find and load the plugins: +// +// - v - plugin configuration (section plugin in the config) +// - pluginClass - the class of plugins for which to create the manager. +// The parameters for loading this class of plugins will be part of +// 'pluginClass' section of the plugin configuration +// - pluginParams - parameters to be passed to this class of plugins +// - logger - logger that will be used by the plugin manager +// - name - the plugin implementation name that was registered +// - ch - the plugin RPC channel func CreateGoPluginManager[I IPluggable]( v *viper.Viper, + pluginClass string, pluginParams map[string]*Parameters, logger *zap.SugaredLogger, name string, ch *RPCChannel[I], ) (*GoPluginManager[I], error) { - subs, err := config.GetSubs(v, "go-plugin") + subs, err := config.GetSubs(v, pluginClass) if err != nil { return nil, err } - loader, err := CreateGoPluginLoader(subs["go-plugin"].AllSettings(), pluginParams, logger) + loader, err := CreateGoPluginLoader(subs[pluginClass].AllSettings(), pluginParams, logger) if err != nil { return nil, err } From 28f370e9e14ba890fed9b3d5db5c7221d67f19f2 Mon Sep 17 00:00:00 2001 From: Dhanus M Lal Date: Wed, 2 Sep 2026 10:49:48 +0530 Subject: [PATCH 7/7] feat!: endorsement-store implementation for VTS VTS uses a store composed of multiple store plugins. For each store operation (except addition), the operation is iterated over the component stores until a match is found. The store plugins to be used by vts are passed as a list in the vts configuration in the active-stores section. The store plugins may take initialization parameters, these are passed to them via the plugin parameters. Some configurations are common for all plugins. Such parameters are broadcasted to all the active store plugins. Examples of such config parameters include the CoSERV configuration (default expiry time of the results and a fallback authority for adding the service's authority in the quads). This commit converts the existing corim-store into a store plugin. The coserv proxy plugins have also been converted into store plugins, with only the ExecuteCoservQuery method implemented. The following store plugins are now available * corim-store * amd-kds-coserv-proxy-handler * nvidia-coserv-proxy-handler The native and docker deployments are updtated to use all these stores in this order in the active-stores list. BREAKING CHANGE: Existing configurations won't work anymore because the store section of the config (which had corim-store configuration) is now part of the corim-store plugin parameter. For the native and docker deployments, the directory structure of plugin binaries have also changed. In the plugins section, go-plugins subsection containing the plugin loading configuration has been renamed to go-plugin-schemes (now for scheme plugins) and a new section go-plugin-stores has been added for store plugins. Some of the CoSERV configurations (signer config and max-expiry) are also now part of the endorsement-service config, instead of coserv service configuration. Signed-off-by: Dhanus M Lal --- .gitignore | 1 + Makefile | 6 +- builtin/builtin_loader.go | 21 +- builtin/builtin_manager.go | 3 +- builtin/{schemes.go => plugins.go} | 27 +- deployments/docker/src/builder-dispatcher | 6 +- deployments/docker/src/config.yaml.template | 27 +- deployments/native/README.md | 7 +- .../native/config/services.yaml.template | 41 ++- deployments/native/deployment.cfg | 3 + deployments/native/deployment.sh | 22 +- handler/coservproxy_rpc.go | 165 --------- handler/icoservproxyhandler.go | 17 - handler/plugin.go | 7 - management/cmd/management-service/README.md | 4 +- management/cmd/management-service/config.yaml | 2 +- management/policy.go | 4 +- scheme/Makefile | 3 - store-plugin/Makefile | 11 + store-plugin/amd-kds-coserv/Makefile | 15 + .../amd-kds-coserv/README.md | 0 .../amd-kds-coserv/coserv_handler.go | 55 ++- .../amd-kds-coserv/plugin/Makefile | 4 +- .../amd-kds-coserv}/plugin/main.go | 4 +- .../amd-kds-coserv/scheme.go | 3 +- .../corim-store}/Makefile | 4 +- .../corim-store/corimstore_enstore.go | 148 ++++++++ store-plugin/corim-store/plugin/Makefile | 11 + store-plugin/corim-store/plugin/main.go | 14 + .../corim-store}/store.go | 38 +- .../nvidia-coserv}/Makefile | 4 +- .../nvidia-coserv/README.md | 0 .../nvidia-coserv/coserv_handler.go | 55 ++- .../nvidia-coserv/plugin/Makefile | 4 +- .../nvidia-coserv}/plugin/main.go | 4 +- .../nvidia-coserv/scheme.go | 3 +- vts/Makefile | 1 + vts/cmd/vts-service/config-docker.yaml | 26 +- vts/cmd/vts-service/config.yaml | 26 +- vts/cmd/vts-service/main.go | 118 +++--- vts/coserv/context.go | 13 +- vts/coserv/store_config.go | 69 ++++ vts/coserv/store_config_test.go | 92 +++++ vts/endorsementstore/Makefile | 13 + vts/endorsementstore/Readme.md | 49 +++ vts/endorsementstore/config.go | 73 ++++ vts/endorsementstore/config_test.go | 129 +++++++ .../mocks/iendorsementstore.go | 248 +++++++++++++ vts/endorsementstore/store.go | 171 +++++++++ vts/endorsementstore/store_test.go | 339 ++++++++++++++++++ vts/trustedservices/trustedservices_grpc.go | 156 ++++---- 51 files changed, 1831 insertions(+), 435 deletions(-) rename builtin/{schemes.go => plugins.go} (69%) delete mode 100644 handler/coservproxy_rpc.go delete mode 100644 handler/icoservproxyhandler.go create mode 100644 store-plugin/Makefile create mode 100644 store-plugin/amd-kds-coserv/Makefile rename {scheme => store-plugin}/amd-kds-coserv/README.md (100%) rename {scheme => store-plugin}/amd-kds-coserv/coserv_handler.go (74%) rename {scheme => store-plugin}/amd-kds-coserv/plugin/Makefile (62%) rename {scheme/nvidia-coserv => store-plugin/amd-kds-coserv}/plugin/main.go (64%) rename {scheme => store-plugin}/amd-kds-coserv/scheme.go (60%) rename {scheme/nvidia-coserv => store-plugin/corim-store}/Makefile (66%) create mode 100644 store-plugin/corim-store/corimstore_enstore.go create mode 100644 store-plugin/corim-store/plugin/Makefile create mode 100644 store-plugin/corim-store/plugin/main.go rename {vts/store => store-plugin/corim-store}/store.go (57%) rename {scheme/amd-kds-coserv => store-plugin/nvidia-coserv}/Makefile (65%) rename {scheme => store-plugin}/nvidia-coserv/README.md (100%) rename {scheme => store-plugin}/nvidia-coserv/coserv_handler.go (83%) rename {scheme => store-plugin}/nvidia-coserv/plugin/Makefile (63%) rename {scheme/amd-kds-coserv => store-plugin/nvidia-coserv}/plugin/main.go (64%) rename {scheme => store-plugin}/nvidia-coserv/scheme.go (60%) create mode 100644 vts/coserv/store_config.go create mode 100644 vts/coserv/store_config_test.go create mode 100644 vts/endorsementstore/Makefile create mode 100644 vts/endorsementstore/Readme.md create mode 100644 vts/endorsementstore/config.go create mode 100644 vts/endorsementstore/config_test.go create mode 100644 vts/endorsementstore/mocks/iendorsementstore.go create mode 100644 vts/endorsementstore/store.go create mode 100644 vts/endorsementstore/store_test.go diff --git a/.gitignore b/.gitignore index 10bce5a3..ce90322e 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ policy/cmd/polcli/polcli protogen provisioning/cmd/provisioning-service/provisioning-service scheme/bin +store-plugin/bin plugin/test/bin scheme/enacttrust/test/tokens/*.token scheme/tpm-enacttrust/test/cmd/gen-token/gen-token diff --git a/Makefile b/Makefile index 59e9362c..66b13130 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ SUBDIR += builtin SUBDIR += auth SUBDIR += config SUBDIR += coserv +SUBDIR += store-plugin SUBDIR += handler SUBDIR += kvstore SUBDIR += log @@ -47,9 +48,10 @@ IGNORE_COVERAGE += github.com/veraison/services/vts/trustedservices IGNORE_COVERAGE += github.com/veraison/services/vtsclient IGNORE_COVERAGE += github.com/veraison/services/coserv/api IGNORE_COVERAGE += github.com/veraison/services/coserv/cmd/coserv-service -IGNORE_COVERAGE += github.com/veraison/services/scheme/amd-kds-coserv -IGNORE_COVERAGE += github.com/veraison/services/scheme/nvidia-coserv +IGNORE_COVERAGE += github.com/veraison/services/store-plugin/amd-kds-coserv +IGNORE_COVERAGE += github.com/veraison/services/store-plugin/nvidia-coserv IGNORE_COVERAGE += github.com/veraison/services/scheme/nvidia +IGNORE_COVERAGE += github.com/veraison/services/store-plugin/corim-store IGNORE_COVERAGE += github.com/veraison/services/scheme/parsec-cca IGNORE_COVERAGE += github.com/veraison/services/scheme/parsec-tpm IGNORE_COVERAGE += github.com/veraison/services/scheme/psa-iot diff --git a/builtin/builtin_loader.go b/builtin/builtin_loader.go index c81e2336..746a782d 100644 --- a/builtin/builtin_loader.go +++ b/builtin/builtin_loader.go @@ -10,11 +10,12 @@ import ( "go.uber.org/zap" ) -var defaultBuiltinLoader *BuiltinLoader +var defaultBuiltinSchemeLoader *BuiltinLoader type BuiltinLoader struct { logger *zap.SugaredLogger + class PluginClass loadedByName map[string]plugin.IPluggable loadedByMediaType map[string]plugin.IPluggable @@ -23,16 +24,20 @@ type BuiltinLoader struct { registeredPluginTypes map[string]string } -func NewBuiltinLoader(logger *zap.SugaredLogger) *BuiltinLoader { - return &BuiltinLoader{logger: logger} +func NewBuiltinLoader(logger *zap.SugaredLogger, class PluginClass) *BuiltinLoader { + return &BuiltinLoader{ + logger: logger, + class: class, + } } func CreateBuiltinLoader( cfg map[string]any, + class PluginClass, pluginParams map[string]*plugin.Parameters, logger *zap.SugaredLogger, ) (*BuiltinLoader, error) { - loader := NewBuiltinLoader(logger) + loader := NewBuiltinLoader(logger, class) err := loader.Init(cfg, pluginParams) return loader, err } @@ -70,11 +75,11 @@ func (o *BuiltinLoader) GetRegisteredMediaTypesByCategory(category string) []str } func DiscoverBuiltin[I plugin.IPluggable]() error { - return DiscoverBuiltinUsing[I](defaultBuiltinLoader) + return DiscoverBuiltinUsing[I](defaultBuiltinSchemeLoader) } func DiscoverBuiltinUsing[I plugin.IPluggable](loader *BuiltinLoader) error { - for _, p := range plugins { + for _, p := range plugins[loader.class] { _, ok := p.(I) if !ok { continue @@ -117,7 +122,7 @@ func DiscoverBuiltinUsing[I plugin.IPluggable](loader *BuiltinLoader) error { } func GetBuiltinHandleByMediaType[I plugin.IPluggable](mediaType string) (I, error) { - return GetBuiltinHandleByMediaTypeUsing[I](defaultBuiltinLoader, mediaType) + return GetBuiltinHandleByMediaTypeUsing[I](defaultBuiltinSchemeLoader, mediaType) } func GetBuiltinHandleByMediaTypeUsing[I plugin.IPluggable]( @@ -196,5 +201,5 @@ func GetBuiltinHandleByAttestationSchemeUsing[I plugin.IPluggable]( } func init() { - defaultBuiltinLoader = NewBuiltinLoader(log.Named("builtin")) + defaultBuiltinSchemeLoader = NewBuiltinLoader(log.Named("builtin"), SchemePlugin) } diff --git a/builtin/builtin_manager.go b/builtin/builtin_manager.go index 5c42cf6f..1fa1ab95 100644 --- a/builtin/builtin_manager.go +++ b/builtin/builtin_manager.go @@ -23,6 +23,7 @@ func NewBuiltinManager[I plugin.IPluggable]( func CreateBuiltinManager[I plugin.IPluggable]( v *viper.Viper, + class PluginClass, pluginParams map[string]*plugin.Parameters, logger *zap.SugaredLogger, name string, @@ -32,7 +33,7 @@ func CreateBuiltinManager[I plugin.IPluggable]( return nil, err } - loader, err := CreateBuiltinLoader(subs["builtin"].AllSettings(), pluginParams, logger) + loader, err := CreateBuiltinLoader(subs["builtin"].AllSettings(), class, pluginParams, logger) if err != nil { return nil, err } diff --git a/builtin/schemes.go b/builtin/plugins.go similarity index 69% rename from builtin/schemes.go rename to builtin/plugins.go index 5f8e8674..e197c1a2 100644 --- a/builtin/schemes.go +++ b/builtin/plugins.go @@ -6,18 +6,31 @@ import ( "github.com/veraison/services/handler" "github.com/veraison/services/plugin" - scheme9 "github.com/veraison/services/scheme/amd-kds-coserv" scheme3 "github.com/veraison/services/scheme/arm-cca" scheme10 "github.com/veraison/services/scheme/da-spdm" - scheme8 "github.com/veraison/services/scheme/nvidia-coserv" scheme1 "github.com/veraison/services/scheme/parsec-cca" scheme5 "github.com/veraison/services/scheme/parsec-tpm" scheme6 "github.com/veraison/services/scheme/psa-iot" scheme7 "github.com/veraison/services/scheme/sevsnp" scheme4 "github.com/veraison/services/scheme/tpm-enacttrust" + store3 "github.com/veraison/services/store-plugin/amd-kds-coserv" + store1 "github.com/veraison/services/store-plugin/corim-store" + store2 "github.com/veraison/services/store-plugin/nvidia-coserv" ) -var plugins = []plugin.IPluggable{ +type PluginClass uint8 + +const ( + SchemePlugin PluginClass = iota + StorePlugin +) + +var plugins = map[PluginClass][]plugin.IPluggable{ + SchemePlugin: schemePlugins, + StorePlugin: storePlugins, +} + +var schemePlugins = []plugin.IPluggable{ handler.MustNewSchemeImplementationWrapper(scheme1.Descriptor, scheme1.NewImplementation()), handler.MustNewSchemeImplementationWrapper(scheme3.Descriptor, scheme3.NewImplementation()), handler.MustNewSchemeImplementationWrapper(scheme4.Descriptor, scheme4.NewImplementation()), @@ -25,6 +38,10 @@ var plugins = []plugin.IPluggable{ handler.MustNewSchemeImplementationWrapper(scheme6.Descriptor, scheme6.NewImplementation()), handler.MustNewSchemeImplementationWrapper(scheme7.Descriptor, scheme7.NewImplementation()), handler.MustNewSchemeImplementationWrapper(scheme10.Descriptor, scheme10.NewImplementation()), - &scheme8.CoservProxyHandler{}, - &scheme9.CoservProxyHandler{}, +} + +var storePlugins = []plugin.IPluggable{ + &store2.CoservProxyHandler{}, + &store3.CoservProxyHandler{}, + store1.NewStore(), } diff --git a/deployments/docker/src/builder-dispatcher b/deployments/docker/src/builder-dispatcher index 2d05b067..82af8b73 100755 --- a/deployments/docker/src/builder-dispatcher +++ b/deployments/docker/src/builder-dispatcher @@ -25,11 +25,12 @@ function deploy() { local gobin=$(go env GOPATH)/bin echo "creating deployment directory structure under $DEPLOY_DIR" - mkdir -p $DEPLOY_DIR/plugins + mkdir -p $DEPLOY_DIR/plugins/schemes mkdir -p $DEPLOY_DIR/stores mkdir -p $DEPLOY_DIR/logs mkdir -p $DEPLOY_DIR/utils mkdir -p $DEPLOY_DIR/certs + mkdir -p $DEPLOY_DIR/plugins/stores echo "deploying services" cp $BUILD_DIR/provisioning/cmd/provisioning-service/provisioning-service $DEPLOY_DIR/ @@ -37,7 +38,8 @@ function deploy() { cp $BUILD_DIR/vts/cmd/vts-service/vts-service $DEPLOY_DIR/ cp $BUILD_DIR/management/cmd/management-service/management-service $DEPLOY_DIR/ cp $BUILD_DIR/coserv/cmd/coserv-service/coserv-service $DEPLOY_DIR/ - cp $BUILD_DIR/scheme/bin/* $DEPLOY_DIR/plugins/ + cp $BUILD_DIR/scheme/bin/* $DEPLOY_DIR/plugins/schemes/ + cp $BUILD_DIR/store-plugin/bin/* $DEPLOY_DIR/plugins/stores/ cp $BUILD_DIR/deployments/docker/src/skey.jwk $DEPLOY_DIR/ cp $BUILD_DIR/deployments/docker/src/coserv-signer.jwk $DEPLOY_DIR/ cp $BUILD_DIR/deployments/docker/src/service-entrypoint $DEPLOY_DIR/ diff --git a/deployments/docker/src/config.yaml.template b/deployments/docker/src/config.yaml.template index ffa30f73..92be250c 100644 --- a/deployments/docker/src/config.yaml.template +++ b/deployments/docker/src/config.yaml.template @@ -26,10 +26,6 @@ coserv: protocol: https cert: /opt/veraison/coserv.crt cert-key: /opt/veraison/coserv.key - max-expiry: 5 mins - signer: - alg: ES256 - key: /opt/veraison/coserv-signer.jwk vts: server-addr: vts-service:${VTS_PORT} tls: true @@ -42,11 +38,10 @@ ear-signer: key: skey.jwk plugin: backend: go-plugin - go-plugin: - dir: ./plugins/ -store: - dbms: sqlite - dsn: stores/vts/store.sql + go-plugin-schemes: + dir: ./plugins/schemes/ + go-plugin-stores: + dir: ./plugins/stores/ po-store: backend: sql sql: @@ -55,6 +50,20 @@ po-store: datasource: stores/vts/po-store.sql po-agent: backend: opa +endorsement-store: + coserv: + max-expiry: 5 mins + signer: + alg: ES256 + key: /opt/veraison/coserv-signer.jwk + active-plugins: + - corim-store + - nvidia-coserv-proxy-handler + - amd-kds-coserv-proxy-handler + plugin-parameters: + corim-store: + dbms: sqlite + dsn: /opt/veraison/stores/vts/store.sql auth: backend: keycloak host: keycloak-service diff --git a/deployments/native/README.md b/deployments/native/README.md index e41b12d7..cce3cc81 100644 --- a/deployments/native/README.md +++ b/deployments/native/README.md @@ -368,6 +368,8 @@ structure ├── env ├── logs ├── plugins +│   ├── schemes +│   └── stores ├── signing ├── stores └── systemd (or launchd) @@ -401,9 +403,10 @@ This directory contains service logs. #### `plugins` -This directly contains attestation scheme plugins. +This directly contains attestation scheme plugins (in the `schemes` sub directory) +and endorsement store plugins (in the `stores` sub directory). -(note: if the deployment was crated with `-s` option, the plugins will in fact +(note: if the deployment was created with `-s` option, the plugins will in fact be symlinks to their source locations.) #### `signing` diff --git a/deployments/native/config/services.yaml.template b/deployments/native/config/services.yaml.template index f032fe4b..85a559a7 100644 --- a/deployments/native/config/services.yaml.template +++ b/deployments/native/config/services.yaml.template @@ -26,10 +26,6 @@ coserv: protocol: https cert: ${VERAISON_CERTS_DIR}/coserv.crt cert-key: ${VERAISON_CERTS_DIR}/coserv.key - max-expiry: 5 mins - signer: - alg: ES256 - key: ${VERAISON_SIGNING_DIR}/coserv-signer.jwk vts: server-addr: ${VTS_HOST}:${VTS_PORT} tls: true @@ -42,8 +38,10 @@ ear-signer: key: ${VERAISON_SIGNING_DIR}/skey.jwk plugin: backend: go-plugin - go-plugin: - dir: ${VERAISON_PLUGINS_DIR}/ + go-plugin-schemes: + dir: ${VERAISON_SCHEME_PLUGINS_DIR}/ + go-plugin-stores: + dir: ${VERAISON_STORE_PLUGINS_DIR}/ ############################################################################## # The store entries below configure the stores for endorsements, trust # anchors, and policies. They are configured to use sqlite3 by default. sqlite3 @@ -58,17 +56,30 @@ plugin: # on localhost on its default port, the user is "veraison" with password # "password", and the relevant database is "veraison". ############################################################################## -store: - trace-sql: false +endorsement-store: + coserv: + max-expiry: 5 mins + signer: + alg: ES256 + key: ${VERAISON_SIGNING_DIR}/coserv-signer.jwk + active-plugins: + - corim-store + - nvidia-coserv-proxy-handler + - amd-kds-coserv-proxy-handler - dbms: sqlite3 - dsn: ${VERAISON_STORES_DIR}/store.sql + # configurations for the corim-store plugin + plugin-parameters: + corim-store: + trace-sql: false - #dbms: pgx - #dsn: postgres://veraison:password@localhost:5432/veraison - - #dbms: mysql - #dsn: veraison:password@tcp(localhost:3306)/veraison + dbms: sqlite3 + dsn: ${VERAISON_STORES_DIR}/store.sql + + #dbms: pgx + #dsn: postgres://veraison:password@localhost:5432/veraison + + #dbms: mysql + #dsn: veraison:password@tcp(localhost:3306)/veraison po-store: backend: sql sql: diff --git a/deployments/native/deployment.cfg b/deployments/native/deployment.cfg index eaf2a916..eba36c2e 100644 --- a/deployments/native/deployment.cfg +++ b/deployments/native/deployment.cfg @@ -53,4 +53,7 @@ VERAISON_SYSTEMD_USER_DIR=$(echo ${VERAISON_ROOT}/${VERAISON_SYSTEMD_USER_SUBDIR VERAISON_TMUX_DIR=$(echo ${VERAISON_ROOT}/${VERAISON_TMUX_SUBDIR:-} | tr -s '/') VERAISON_LICENSE_DIR=$(echo ${VERAISON_ROOT}/${VERAISON_LICENSE_SUBDIR:-} | tr -s '/') +VERAISON_SCHEME_PLUGINS_DIR=$(echo ${VERAISON_PLUGINS_DIR}/schemes | tr -s '/') +VERAISON_STORE_PLUGINS_DIR=$(echo ${VERAISON_PLUGINS_DIR}/stores | tr -s '/') + # vim: set ft=bash: diff --git a/deployments/native/deployment.sh b/deployments/native/deployment.sh index 1c202bb6..0ade63aa 100755 --- a/deployments/native/deployment.sh +++ b/deployments/native/deployment.sh @@ -27,7 +27,8 @@ SRC_LAUNCHD_DIR=${_THIS_DIR}/launchd SRC_CERTS_DIR=${VERAISON_CERTS:-${EXAMPLE_DIR}/certs} DEPLOYMENT_BIN_DIR=$(echo ${DEPLOYMENT_DEST}/${VERAISON_BIN_DIR} | tr -s '/') -DEPLOYMENT_PLUGINS_DIR=$(echo ${DEPLOYMENT_DEST}/${VERAISON_PLUGINS_DIR} | tr -s '/') +DEPLOYMENT_SCHEME_PLUGINS_DIR=$(echo ${DEPLOYMENT_DEST}/${VERAISON_SCHEME_PLUGINS_DIR} | tr -s '/') +DEPLOYMENT_STORE_PLUGINS_DIR=$(echo ${DEPLOYMENT_DEST}/${VERAISON_STORE_PLUGINS_DIR} | tr -s '/') DEPLOYMENT_CERTS_DIR=$(echo ${DEPLOYMENT_DEST}/${VERAISON_CERTS_DIR} | tr -s '/') DEPLOYMENT_LOGS_DIR=$(echo ${DEPLOYMENT_DEST}/${VERAISON_LOGS_DIR} | tr -s '/') DEPLOYMENT_STORES_DIR=$(echo ${DEPLOYMENT_DEST}/${VERAISON_STORES_DIR} | tr -s '/') @@ -433,7 +434,8 @@ function help() { function _init_deployment_dir() { mkdir -p ${DEPLOYMENT_BIN_DIR} - mkdir -p ${DEPLOYMENT_PLUGINS_DIR} + mkdir -p ${DEPLOYMENT_SCHEME_PLUGINS_DIR} + mkdir -p ${DEPLOYMENT_STORE_PLUGINS_DIR} mkdir -p ${DEPLOYMENT_CERTS_DIR} mkdir -p ${DEPLOYMENT_LOGS_DIR} mkdir -p ${DEPLOYMENT_CONFIG_DIR} @@ -547,7 +549,13 @@ function _symlink_bins() { while IFS= read -r -d '' path; do path=$(realpath "$path") chmod +x "$path" - ln -s $_f "$path" "${DEPLOYMENT_PLUGINS_DIR}/$(basename $path)" + ln -s $_f "$path" "${DEPLOYMENT_SCHEME_PLUGINS_DIR}/$(basename $path)" + done + + find "${ROOT_DIR}/store-plugin/bin/" -name '*.plugin' -print0 | grep -z -v handler | + while IFS= read -r -d '' path; do + chmod +x "$path" + ln -s $_f "$path" "${DEPLOYMENT_STORE_PLUGINS_DIR}/$(basename $path)" done } @@ -561,8 +569,14 @@ function _deploy_bins() { find "${ROOT_DIR}/scheme/bin/" -name '*.plugin' -print0 | grep -z -v handler | while IFS= read -r -d '' path; do - $_INSTALL -m 0755 "$path" "${DEPLOYMENT_PLUGINS_DIR}/$(basename $path)" + $_INSTALL -m 0755 "$path" "${DEPLOYMENT_SCHEME_PLUGINS_DIR}/$(basename $path)" done + + find "${ROOT_DIR}/endorsementstore/bin/" -name '*.plugin' -print0 | grep -z -v handler | + while IFS= read -r -d '' path; do + $_INSTALL -m 0755 "$path" "${DEPLOYMENT_STORE_PLUGINS_DIR}/$(basename $path)" + done + } function _deploy_frontend { diff --git a/handler/coservproxy_rpc.go b/handler/coservproxy_rpc.go deleted file mode 100644 index 9e8b979c..00000000 --- a/handler/coservproxy_rpc.go +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright 2025-2026 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 -package handler - -import ( - "encoding/json" - "fmt" - "net/rpc" - - "github.com/veraison/services/log" - "github.com/veraison/services/plugin" -) - -var CoservProxyHandlerRPC = &plugin.RPCChannel[ICoservProxyHandler]{ - GetClient: getCoservClient, - GetServer: getCoservServer, -} - -func getCoservClient(c *rpc.Client) interface{} { - return &CoservProxyRPCClient{client: c} -} - -func getCoservServer(i ICoservProxyHandler) interface{} { - return &CoservProxyRPCServer{Impl: i} -} - -type CoservProxyRPCServer struct { - Impl ICoservProxyHandler -} - -func (o *CoservProxyRPCServer) Init(args []byte, resp *any) error { - var params *plugin.Parameters - var err error - - if args == nil { - params = plugin.NewParameters() - } else { - if params, err = plugin.ParametersFromJSON(args); err != nil { - return err - } - } - - return o.Impl.Init(params) -} - -func (o *CoservProxyRPCServer) Fini(args any, resp *any) error { - return o.Impl.Fini() -} - -func (s *CoservProxyRPCServer) GetName(args interface{}, resp *string) error { - *resp = s.Impl.GetName() - return nil -} - -func (s *CoservProxyRPCServer) GetAttestationScheme(args interface{}, resp *string) error { - *resp = s.Impl.GetAttestationScheme() - return nil -} - -func (s *CoservProxyRPCServer) GetSupportedMediaTypes(args interface{}, resp *[]byte) error { - var err error - mts := s.Impl.GetSupportedMediaTypes() - - *resp, err = json.Marshal(mts) - return err -} - -type GetEndorsementArgs struct { - TenantID string - Query string -} - -func (s *CoservProxyRPCServer) GetEndorsements(args GetEndorsementArgs, resp *[]byte) (err error) { - *resp, err = s.Impl.GetEndorsements(args.TenantID, args.Query) - return err -} - -type CoservProxyRPCClient struct { - client *rpc.Client -} - -func (o *CoservProxyRPCClient) Init(params *plugin.Parameters) error { - var ( - unused any - args []byte - err error - ) - - if params != nil { - if args, err = params.MarshalJSON(); err != nil { - return err - } - } - - return o.client.Call("Plugin.Init", args, &unused) -} - -func (o *CoservProxyRPCClient) Fini() error { - var unused any - return o.client.Call("Plugin.Fini", unused, &unused) -} - -func (c *CoservProxyRPCClient) GetName() string { - var ( - resp string - unused interface{} - ) - - err := c.client.Call("Plugin.GetName", &unused, &resp) - if err != nil { - log.Errorf("Plugin.GetName RPC call failed: %v", err) - return "" - } - - return resp -} - -func (c *CoservProxyRPCClient) GetAttestationScheme() string { - var ( - resp string - unused interface{} - ) - - err := c.client.Call("Plugin.GetAttestationScheme", &unused, &resp) - if err != nil { - log.Errorf("Plugin.GetAttestationScheme RPC call failed: %v", err) - return "" - } - - return resp -} - -func (c *CoservProxyRPCClient) GetSupportedMediaTypes() map[string][]string { - var ( - resp []byte - unused any - ) - - err := c.client.Call("Plugin.GetSupportedMediaTypes", &unused, &resp) - if err != nil { - log.Errorf("Plugin.GetSupportedMediaTypes RPC call failed: %v", err) - return nil - } - - var ret map[string][]string - if err := json.Unmarshal(resp, &ret); err != nil { - log.Error(err) - } - - return ret -} - -func (c *CoservProxyRPCClient) GetEndorsements( - tenantID string, - query string, -) (resp []byte, err error) { - args := GetEndorsementArgs{TenantID: tenantID, Query: query} - - err = c.client.Call("Plugin.GetEndorsements", args, &resp) - if err != nil { - return nil, fmt.Errorf("Plugin.GetEndorsements RPC call failed: %w", ParseError(err)) - } - - return resp, nil -} diff --git a/handler/icoservproxyhandler.go b/handler/icoservproxyhandler.go deleted file mode 100644 index ee631bcc..00000000 --- a/handler/icoservproxyhandler.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2025 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 -package handler - -import ( - "github.com/veraison/services/plugin" -) - -// ICoservProxyHandler defines the interface for CoSERV translation plugins -type ICoservProxyHandler interface { - plugin.IPluggable - - // GetEndorsements adds the "result set" to the input "query" CoSERV. - // The input query CoSERV is base64url-encoded. - // In case of a failure an error is returned and the CoSERV is nil. - GetEndorsements(tenantID string, query string) ([]byte, error) -} diff --git a/handler/plugin.go b/handler/plugin.go index d584e5c5..605e7701 100644 --- a/handler/plugin.go +++ b/handler/plugin.go @@ -6,13 +6,6 @@ import ( "github.com/veraison/services/plugin" ) -func RegisterCoservProxyHandler(i ICoservProxyHandler) { - err := plugin.RegisterImplementation("coserv-proxy-handler", i, CoservProxyHandlerRPC) - if err != nil { - panic(err) - } -} - func RegisterSchemeHandler(i ISchemeHandler) { err := plugin.RegisterImplementation("scheme-handler", i, SchemeHandlerRPC) if err != nil { diff --git a/management/cmd/management-service/README.md b/management/cmd/management-service/README.md index c1d700b7..dfb56a8e 100644 --- a/management/cmd/management-service/README.md +++ b/management/cmd/management-service/README.md @@ -59,6 +59,6 @@ po-agent: backend: opa plugin: backend: go-plugin - go-plugin: - folder: ../../plugins/bin/ + go-plugin-schemes: + dir: ../../plugins/bin/ ``` diff --git a/management/cmd/management-service/config.yaml b/management/cmd/management-service/config.yaml index 237c4cd2..963d7bf3 100644 --- a/management/cmd/management-service/config.yaml +++ b/management/cmd/management-service/config.yaml @@ -1,6 +1,6 @@ plugin: backend: go-plugin - go-plugin: + go-plugin-schemes: dir: ../../../scheme/bin/ po-store: backend: sql diff --git a/management/policy.go b/management/policy.go index a54dcb69..2fcf895b 100644 --- a/management/policy.go +++ b/management/policy.go @@ -48,14 +48,14 @@ func CreatePolicyManagerFromConfig(v *viper.Viper, name string) (*PolicyManager, if config.SchemeLoader == "plugins" { // nolint:gocritic pluginManager, err = plugin.CreateGoPluginManager( - subs["plugin"], "go-plugin", pluginConfig, log.Named("plugin"), + subs["plugin"], "go-plugin-schemes", pluginConfig, log.Named("plugin"), "scheme-handler", handler.SchemeHandlerRPC) if err != nil { log.Fatalf("plugin manager initialization failed: %v", err) } } else if config.SchemeLoader == "builtin" { pluginManager, err = builtin.CreateBuiltinManager[handler.ISchemeHandler]( - subs["plugin"], pluginConfig, log.Named("builtin"), "scheme-handler") + subs["plugin"], builtin.SchemePlugin, pluginConfig, log.Named("builtin"), "scheme-handler") if err != nil { log.Fatalf("scheme manager initialization failed: %v", err) } diff --git a/scheme/Makefile b/scheme/Makefile index 07154058..651491b2 100644 --- a/scheme/Makefile +++ b/scheme/Makefile @@ -12,9 +12,6 @@ SUBDIR += psa-iot SUBDIR += sevsnp SUBDIR += tpm-enacttrust -SUBDIR += amd-kds-coserv -SUBDIR += nvidia-coserv - clean: ; $(RM) -rf ./bin include ../mk/subdir.mk diff --git a/store-plugin/Makefile b/store-plugin/Makefile new file mode 100644 index 00000000..11c57462 --- /dev/null +++ b/store-plugin/Makefile @@ -0,0 +1,11 @@ +# Copyright 2026 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 + + +SUBDIR += amd-kds-coserv +SUBDIR += corim-store +SUBDIR += nvidia-coserv + +clean: ; $(RM) -rf ./bin + +include ../mk/subdir.mk diff --git a/store-plugin/amd-kds-coserv/Makefile b/store-plugin/amd-kds-coserv/Makefile new file mode 100644 index 00000000..20e7e7c1 --- /dev/null +++ b/store-plugin/amd-kds-coserv/Makefile @@ -0,0 +1,15 @@ +# Copyright 2025-2026 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 + +.DEFAULT_GOAL := test + +GOPKG := github.com/veraison/services/store-plugin/amd-kds-coserv +SRCS := $(wildcard *.go) + +SUBDIR += plugin + +include ../../mk/common.mk +include ../../mk/lint.mk +include ../../mk/pkg.mk +include ../../mk/subdir.mk +include ../../mk/test.mk diff --git a/scheme/amd-kds-coserv/README.md b/store-plugin/amd-kds-coserv/README.md similarity index 100% rename from scheme/amd-kds-coserv/README.md rename to store-plugin/amd-kds-coserv/README.md diff --git a/scheme/amd-kds-coserv/coserv_handler.go b/store-plugin/amd-kds-coserv/coserv_handler.go similarity index 74% rename from scheme/amd-kds-coserv/coserv_handler.go rename to store-plugin/amd-kds-coserv/coserv_handler.go index 8e94f68c..2a329e09 100644 --- a/scheme/amd-kds-coserv/coserv_handler.go +++ b/store-plugin/amd-kds-coserv/coserv_handler.go @@ -9,18 +9,20 @@ import ( "fmt" "io" "net/http" + "slices" "time" "github.com/veraison/corim/comid" "github.com/veraison/corim/coserv" + "github.com/veraison/services/handler" + "github.com/veraison/services/log" "github.com/veraison/services/plugin" + vtscoserv "github.com/veraison/services/vts/coserv" ) -type CoservProxyHandler struct{} - -var ( - dummyAuthority = []byte{0xab, 0xcd, 0xef} -) +type CoservProxyHandler struct { + *vtscoserv.StoreConfig +} func constructVcekUrl(instance *coserv.StatefulInstance) string { // TODO(paulhowardarm) - deduce the product name and TCB parameters from the @@ -50,7 +52,14 @@ func getVcekForInstance(instance *coserv.StatefulInstance) ([]byte, error) { return certBytes, nil } -func (s CoservProxyHandler) Init(*plugin.Parameters) error { +func (s *CoservProxyHandler) Init(params *plugin.Parameters) error { + var cfg vtscoserv.StoreConfig + if err := (&cfg).FromParams(params); err != nil { + log.Named(SchemeName).Warn("CoSERV configuration not passed") + s.StoreConfig = nil + } else { + s.StoreConfig = &cfg + } return nil } @@ -77,14 +86,10 @@ func (s CoservProxyHandler) addTrustAnchorForInstance(i *coserv.StatefulInstance return err } - // TODO(paulhowardarm) - This authority is a dummy value. + // TODO(paulhowardarm) // We need some kind of cert here, representing this plug-in's authority to re-package from NVIDIA CoRIM // We probably also need an NVIDIA cert in the chain - authority, err := comid.NewCryptoKeyTaggedBytes(dummyAuthority) - - if err != nil { - return fmt.Errorf("failed to make authority tagged bytes: %v", err) - } + authority := s.Authority block := &pem.Block{ Type: "CERTIFICATE", @@ -113,7 +118,15 @@ func (s CoservProxyHandler) addTrustAnchorForInstance(i *coserv.StatefulInstance return nil } -func (s CoservProxyHandler) GetEndorsements(tenantID string, query string) ([]byte, error) { +func (s CoservProxyHandler) ExecuteCoservQuery(profile, query string) (*coserv.Coserv, error) { + if s.StoreConfig == nil { + panic("received CoSERV request when CoSERV API is disabled") + } + + if !slices.Contains(SupportedCoservProfiles, profile) { + return nil, handler.ErrUnsupported + } + var q coserv.Coserv if err := q.FromBase64Url(query); err != nil { return nil, err @@ -150,14 +163,24 @@ func (s CoservProxyHandler) GetEndorsements(tenantID string, query string) ([]by } } - // Set expiry on the results - fairly arbitrary expiry time of 1 hour - coservResult.SetExpiry(time.Now().Add(time.Hour)) + coservResult.SetExpiry(time.Now().Add(s.MaxExpiry)) // Add all results into the top-level CoSERV object err := q.AddResults(coservResult) if err != nil { return nil, err } + return &q, nil +} + +func (o CoservProxyHandler) GetKeyTriples(env *comid.Environment, scheme string, exact bool) ([]*comid.KeyTriple, error) { + return nil, handler.ErrUnsupported +} + +func (o CoservProxyHandler) GetValueTriples(env *comid.Environment, scheme string, exact bool) ([]*comid.ValueTriple, error) { + return nil, handler.ErrUnsupported +} - return q.ToCBOR() +func (b CoservProxyHandler) AddCorimBytes(data []byte, scheme string, activate bool) error { + return handler.ErrUnsupported } diff --git a/scheme/amd-kds-coserv/plugin/Makefile b/store-plugin/amd-kds-coserv/plugin/Makefile similarity index 62% rename from scheme/amd-kds-coserv/plugin/Makefile rename to store-plugin/amd-kds-coserv/plugin/Makefile index ea0015f6..defcd1d9 100644 --- a/scheme/amd-kds-coserv/plugin/Makefile +++ b/store-plugin/amd-kds-coserv/plugin/Makefile @@ -1,8 +1,8 @@ -# Copyright 2025 Contributors to the Veraison project. +# Copyright 2025-2026 Contributors to the Veraison project. # SPDX-License-Identifier: Apache-2.0 PLUGIN := ../../bin/coserv-amd-kds.plugin -GOPKG := github.com/veraison/services/scheme/amd-kds-coserv +GOPKG := github.com/veraison/services/store-plugin/amd-kds-coserv SRCS := main.go include ../../../mk/common.mk diff --git a/scheme/nvidia-coserv/plugin/main.go b/store-plugin/amd-kds-coserv/plugin/main.go similarity index 64% rename from scheme/nvidia-coserv/plugin/main.go rename to store-plugin/amd-kds-coserv/plugin/main.go index b5ee6c78..37ad6db5 100644 --- a/scheme/nvidia-coserv/plugin/main.go +++ b/store-plugin/amd-kds-coserv/plugin/main.go @@ -4,12 +4,12 @@ package main import ( + scheme "github.com/veraison/services/store-plugin/amd-kds-coserv" "github.com/veraison/services/handler" "github.com/veraison/services/plugin" - scheme "github.com/veraison/services/scheme/nvidia-coserv" ) func main() { - handler.RegisterCoservProxyHandler(&scheme.CoservProxyHandler{}) + handler.RegisterEndorsementStore(&scheme.CoservProxyHandler{}) plugin.Serve() } diff --git a/scheme/amd-kds-coserv/scheme.go b/store-plugin/amd-kds-coserv/scheme.go similarity index 60% rename from scheme/amd-kds-coserv/scheme.go rename to store-plugin/amd-kds-coserv/scheme.go index 26967aaf..040f00ae 100644 --- a/scheme/amd-kds-coserv/scheme.go +++ b/store-plugin/amd-kds-coserv/scheme.go @@ -1,4 +1,4 @@ -// Copyright 2025 Contributors to the Veraison project. +// Copyright 2025-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package amdkdscoserv @@ -9,4 +9,5 @@ var ( CoservMediaTypes = []string{ `application/coserv+cbor; profile="tag:github.com/veraison,2023:amd_kds_coserv_proxy#1.0.0"`, } + SupportedCoservProfiles = []string{"tag:github.com/veraison,2023:amd_kds_coserv_proxy#1.0.0"} ) diff --git a/scheme/nvidia-coserv/Makefile b/store-plugin/corim-store/Makefile similarity index 66% rename from scheme/nvidia-coserv/Makefile rename to store-plugin/corim-store/Makefile index 49f41e73..9bd6c83f 100644 --- a/scheme/nvidia-coserv/Makefile +++ b/store-plugin/corim-store/Makefile @@ -1,9 +1,9 @@ -# Copyright 2025 Contributors to the Veraison project. +# Copyright 2026 Contributors to the Veraison project. # SPDX-License-Identifier: Apache-2.0 .DEFAULT_GOAL := test -GOPKG := github.com/veraison/services/scheme/nvidia-coserv +GOPKG := github.com/veraison/services/store-plugin/corim-store SRCS := $(wildcard *.go) SUBDIR += plugin diff --git a/store-plugin/corim-store/corimstore_enstore.go b/store-plugin/corim-store/corimstore_enstore.go new file mode 100644 index 00000000..5fd483fb --- /dev/null +++ b/store-plugin/corim-store/corimstore_enstore.go @@ -0,0 +1,148 @@ +// Copyright 2025-2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package corim_store + +import ( + "context" + "errors" + "strings" + + corimstore "github.com/veraison/corim-store/pkg/store" + "github.com/veraison/corim/comid" + "github.com/veraison/corim/coserv" + handler "github.com/veraison/services/handler" + "github.com/veraison/services/log" + "github.com/veraison/services/plugin" + vtscoserv "github.com/veraison/services/vts/coserv" + "go.uber.org/zap" +) + +const ( + PluginName = "corim-store" +) + +// implement the IEndorsementStore interface for corimstore.Store +type Store struct { + Store *corimstore.Store + logger *zap.SugaredLogger + CoservCfg *vtscoserv.StoreConfig +} + +func NewStore() *Store { + logger := log.Named(PluginName) + return &Store{nil, logger, nil} +} + +func (s *Store) GetKeyTriples(env *comid.Environment, label string, exact bool) ([]*comid.KeyTriple, error) { + res, err := s.Store.GetActiveKeyTriples(env, label, exact) + if errors.Is(err, corimstore.ErrNoMatch) { + return nil, handler.ErrNotFound + } + return res, err +} + +func (s *Store) GetValueTriples(env *comid.Environment, label string, exact bool) ([]*comid.ValueTriple, error) { + res, err := s.Store.GetActiveValueTriples(env, label, exact) + if errors.Is(err, corimstore.ErrNoMatch) { + return nil, handler.ErrNotFound + } + return res, err +} + +func (s *Store) ExecuteCoservQuery(profile, query string) (*coserv.Coserv, error) { + // If reading CoSERV config failed during initialization, + // CoSERV interface would be disabled. + if s.CoservCfg == nil { + panic("received CoSERV request when CoSERV API is disabled") + } + s.logger.Infof("got coserv query: %v", query) + coservService := corimstore.NewCoSERVService(s.Store, s.CoservCfg.Authority, s.CoservCfg.MaxExpiry) + var q coserv.Coserv + if err := q.FromBase64Url(query); err != nil { + s.logger.Errorf("could not decode string to coserv: %v", err) + return nil, err + } + if err := coservService.UpdateCoSERV(&q); err != nil { + s.logger.Errorf("could not update coserv: %v", err) + return nil, err + } + if q.Results == nil { + return nil, errors.New("internal error: bad CoSERV result: result-set is nil") + } + // return ErrNotFound instead of empty results + if q.Results.AKQ == nil && q.Results.RVQ == nil { + return nil, handler.ErrNotFound + } + s.logger.Debugf("got coserv response: %v", q) + return &q, nil +} + +func (s *Store) AddCorimBytes(data []byte, label string, activate bool) error { + s.logger.Debugf("adding CoRIM") + return s.Store.AddBytes(data, label, activate) +} + +func (s *Store) Fini() error { + s.logger.Info("closing corimstore") + if s.Store == nil { + panic("attempted to close an uninitialized store") + } + if err := s.Store.Close(); err != nil { + s.logger.Errorf("Failed to close corim-store: %v", err) + return err + } + return nil +} + +func (s *Store) Init(params *plugin.Parameters) error { + s.logger.Debug("initializing default store") + if params == nil { + return errors.New("parameters are required for corimstore") + } + cfg, err := ConfigFromParameters(params, s.logger) + if err != nil { + s.logger.Errorf("Failed to load corim-store parameters: %v", err) + return err + } + s.CoservCfg = cfg.CoservConfig() + + s.logger.Debugf("connecting to %s store %s", cfg.DBMS, cfg.DSN) + + store, err := corimstore.Open(context.Background(), cfg.StoreConfig()) + if err != nil { + return err + } + + // The store must be initialized before it may be used. In general, we + // rely on the store being pointed to by DSN to be initialized prior + // to starting the VTS. For in-memory store this can never be the case, so we + // initialize it here. + if strings.Contains(cfg.DSN, ":memory:") { + if err := store.Init(); err != nil { + return err + } + } + + s.Store = store + return nil +} + +func (s *Store) GetName() string { + return PluginName +} + +func (s *Store) GetAttestationScheme() string { + // FIXME(dhanus): This store will in principle work for any schemes that + // support CoRIM endorsements. So returning a single scheme name does + // not make sense. The plugin interface must be updated to support plugins + // that are associated with multiple schemes. + return "" +} + +func (s *Store) GetSupportedMediaTypes() map[string][]string { + // FIXME(dhanus): This method does not make much sense for store plugin, + // but something similar is required to let VTS know the CoSERV profiles + // that are supported. The plugin interface must be updated to somehow + // incorporate this. + return nil +} diff --git a/store-plugin/corim-store/plugin/Makefile b/store-plugin/corim-store/plugin/Makefile new file mode 100644 index 00000000..50e959fe --- /dev/null +++ b/store-plugin/corim-store/plugin/Makefile @@ -0,0 +1,11 @@ +# Copyright 2026 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 + +PLUGIN := ../../bin/corimstore.plugin +GOPKG := github.com/veraison/services/store-plugin/corim-store +SRCS := main.go + +include ../../../mk/common.mk +include ../../../mk/plugin.mk +include ../../../mk/lint.mk +include ../../../mk/test.mk diff --git a/store-plugin/corim-store/plugin/main.go b/store-plugin/corim-store/plugin/main.go new file mode 100644 index 00000000..c309009a --- /dev/null +++ b/store-plugin/corim-store/plugin/main.go @@ -0,0 +1,14 @@ +// Copyright 2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package main + +import ( + "github.com/veraison/services/store-plugin/corim-store" + "github.com/veraison/services/handler" + "github.com/veraison/services/plugin" +) + +func main() { + handler.RegisterEndorsementStore(corim_store.NewStore()) + plugin.Serve() +} diff --git a/vts/store/store.go b/store-plugin/corim-store/store.go similarity index 57% rename from vts/store/store.go rename to store-plugin/corim-store/store.go index 7006cf60..59e22284 100644 --- a/vts/store/store.go +++ b/store-plugin/corim-store/store.go @@ -1,6 +1,6 @@ // Copyright 2025-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 -package store +package corim_store import ( "context" @@ -9,13 +9,39 @@ import ( "github.com/spf13/viper" corimstore "github.com/veraison/corim-store/pkg/store" "github.com/veraison/services/config" + "github.com/veraison/services/plugin" + vtscoserv "github.com/veraison/services/vts/coserv" "go.uber.org/zap" ) type Config struct { - DBMS string `mapstructure:"dbms"` - DSN string `mapstructure:"dsn"` - TraceSQL bool `mapstructure:"trace-sql" config:"zerodefault"` + DBMS string `mapstructure:"dbms"` + DSN string `mapstructure:"dsn"` + TraceSQL bool `mapstructure:"trace-sql" config:"zerodefault"` + CoservCfg *vtscoserv.StoreConfig `config:"zerodefault"` +} + +func ConfigFromParameters(params *plugin.Parameters, logger *zap.SugaredLogger) (*Config, error) { + var ( + cfg Config + coservCfg vtscoserv.StoreConfig + ) + + logger.Debug("creating corim-store config from plugin parameters") + loader := config.NewNonExclusiveLoader(&cfg) + if err := loader.LoadFromMap(params.Map()); err != nil { + return nil, err + } + + // CoSERV configuration not being present is not an error + if err := (&coservCfg).FromParams(params); err != nil { + logger.Warnf("error converting parameters to coserv config: %v", err) + cfg.CoservCfg = nil + } else { + cfg.CoservCfg = &coservCfg + } + + return &cfg, nil } func (o *Config) StoreConfig() *corimstore.Config { @@ -35,6 +61,10 @@ func (o *Config) StoreConfig() *corimstore.Config { return ret } +func (o *Config) CoservConfig() *vtscoserv.StoreConfig { + return o.CoservCfg +} + func New(v *viper.Viper, logger *zap.SugaredLogger) (*corimstore.Store, error) { var cfg Config diff --git a/scheme/amd-kds-coserv/Makefile b/store-plugin/nvidia-coserv/Makefile similarity index 65% rename from scheme/amd-kds-coserv/Makefile rename to store-plugin/nvidia-coserv/Makefile index 77b1b57f..e90d32b0 100644 --- a/scheme/amd-kds-coserv/Makefile +++ b/store-plugin/nvidia-coserv/Makefile @@ -1,9 +1,9 @@ -# Copyright 2025 Contributors to the Veraison project. +# Copyright 2025-2026 Contributors to the Veraison project. # SPDX-License-Identifier: Apache-2.0 .DEFAULT_GOAL := test -GOPKG := github.com/veraison/services/scheme/amd-kds-coserv +GOPKG := github.com/veraison/services/store-plugin/nvidia-coserv SRCS := $(wildcard *.go) SUBDIR += plugin diff --git a/scheme/nvidia-coserv/README.md b/store-plugin/nvidia-coserv/README.md similarity index 100% rename from scheme/nvidia-coserv/README.md rename to store-plugin/nvidia-coserv/README.md diff --git a/scheme/nvidia-coserv/coserv_handler.go b/store-plugin/nvidia-coserv/coserv_handler.go similarity index 83% rename from scheme/nvidia-coserv/coserv_handler.go rename to store-plugin/nvidia-coserv/coserv_handler.go index f20891b1..03bdc860 100644 --- a/scheme/nvidia-coserv/coserv_handler.go +++ b/store-plugin/nvidia-coserv/coserv_handler.go @@ -10,13 +10,17 @@ import ( "fmt" "io" "net/http" + "slices" "time" "github.com/veraison/cmw" "github.com/veraison/corim/comid" "github.com/veraison/corim/corim" "github.com/veraison/corim/coserv" + "github.com/veraison/services/handler" + "github.com/veraison/services/log" "github.com/veraison/services/plugin" + vtscoserv "github.com/veraison/services/vts/coserv" ) // ----- JSON Helper Types ----- @@ -31,13 +35,18 @@ type RimServiceResponse struct { RequestId string `json:"request_id"` } -type CoservProxyHandler struct{} - -var ( - dummyAuthority = []byte{0xab, 0xcd, 0xef} -) +type CoservProxyHandler struct { + *vtscoserv.StoreConfig +} -func (s CoservProxyHandler) Init(*plugin.Parameters) error { +func (s *CoservProxyHandler) Init(params *plugin.Parameters) error { + var cfg vtscoserv.StoreConfig + if err := (&cfg).FromParams(params); err != nil { + log.Named(SchemeName).Warn("CoSERV configuration not passed") + s.StoreConfig = nil + } else { + s.StoreConfig = &cfg + } return nil } @@ -165,14 +174,11 @@ func (s CoservProxyHandler) addReferenceValuesForClass(query *coserv.Query, c *c // We'll just look at reference value triples in the CoMID for _, triple := range c.Triples.ReferenceValues.Values { + authority := s.Authority // Turn each triple into a quad - // TODO(paulhowardarm) - This authority is a dummy value. + // TODO(paulhowardarm) // We need some kind of cert here, representing this plug-in's authority to re-package from NVIDIA CoRIM // We probably also need an NVIDIA cert in the chain - authority, err := comid.NewCryptoKeyTaggedBytes(dummyAuthority) - if err != nil { - return fmt.Errorf("failed to make authority tagged bytes: %w", err) - } rvQuad := coserv.RefValQuad{ Authorities: comid.NewCryptoKeys().Add(authority), @@ -188,7 +194,15 @@ func (s CoservProxyHandler) addReferenceValuesForClass(query *coserv.Query, c *c return nil } -func (s CoservProxyHandler) GetEndorsements(tenantID string, query string) ([]byte, error) { +func (s CoservProxyHandler) ExecuteCoservQuery(profile, query string) (*coserv.Coserv, error) { + if s.StoreConfig == nil { + panic("received CoSERV request when CoSERV API is disabled") + } + + if !slices.Contains(SupportedCoservProfiles, profile) { + return nil, handler.ErrUnsupported + } + var q coserv.Coserv if err := q.FromBase64Url(query); err != nil { return nil, err @@ -225,8 +239,7 @@ func (s CoservProxyHandler) GetEndorsements(tenantID string, query string) ([]by } } - // Set expiry on the results - fairly arbitrary expiry time of 1 hour - coservResult.SetExpiry(time.Now().Add(time.Hour)) + coservResult.SetExpiry(time.Now().Add(s.MaxExpiry)) // Add all results into the top-level CoSERV object err := q.AddResults(coservResult) @@ -234,5 +247,17 @@ func (s CoservProxyHandler) GetEndorsements(tenantID string, query string) ([]by return nil, err } - return q.ToCBOR() + return &q, nil +} + +func (o CoservProxyHandler) GetKeyTriples(env *comid.Environment, scheme string, exact bool) ([]*comid.KeyTriple, error) { + return nil, handler.ErrUnsupported +} + +func (o CoservProxyHandler) GetValueTriples(env *comid.Environment, scheme string, exact bool) ([]*comid.ValueTriple, error) { + return nil, handler.ErrUnsupported +} + +func (b CoservProxyHandler) AddCorimBytes(data []byte, scheme string, activate bool) error { + return handler.ErrUnsupported } diff --git a/scheme/nvidia-coserv/plugin/Makefile b/store-plugin/nvidia-coserv/plugin/Makefile similarity index 63% rename from scheme/nvidia-coserv/plugin/Makefile rename to store-plugin/nvidia-coserv/plugin/Makefile index 41edc8c8..8200e1aa 100644 --- a/scheme/nvidia-coserv/plugin/Makefile +++ b/store-plugin/nvidia-coserv/plugin/Makefile @@ -1,8 +1,8 @@ -# Copyright 2025 Contributors to the Veraison project. +# Copyright 2025-2026 Contributors to the Veraison project. # SPDX-License-Identifier: Apache-2.0 PLUGIN := ../../bin/coserv-nvidia.plugin -GOPKG := github.com/veraison/services/scheme/nvidia-coserv +GOPKG := github.com/veraison/services/store-plugin/nvidia-coserv SRCS := main.go include ../../../mk/common.mk diff --git a/scheme/amd-kds-coserv/plugin/main.go b/store-plugin/nvidia-coserv/plugin/main.go similarity index 64% rename from scheme/amd-kds-coserv/plugin/main.go rename to store-plugin/nvidia-coserv/plugin/main.go index 5f0bd720..26afb981 100644 --- a/scheme/amd-kds-coserv/plugin/main.go +++ b/store-plugin/nvidia-coserv/plugin/main.go @@ -4,12 +4,12 @@ package main import ( + scheme "github.com/veraison/services/store-plugin/nvidia-coserv" "github.com/veraison/services/handler" "github.com/veraison/services/plugin" - scheme "github.com/veraison/services/scheme/amd-kds-coserv" ) func main() { - handler.RegisterCoservProxyHandler(&scheme.CoservProxyHandler{}) + handler.RegisterEndorsementStore(&scheme.CoservProxyHandler{}) plugin.Serve() } diff --git a/scheme/nvidia-coserv/scheme.go b/store-plugin/nvidia-coserv/scheme.go similarity index 60% rename from scheme/nvidia-coserv/scheme.go rename to store-plugin/nvidia-coserv/scheme.go index cbc5a7da..68c6d2b9 100644 --- a/scheme/nvidia-coserv/scheme.go +++ b/store-plugin/nvidia-coserv/scheme.go @@ -1,4 +1,4 @@ -// Copyright 2025 Contributors to the Veraison project. +// Copyright 2025-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package nvidiacoserv @@ -9,4 +9,5 @@ var ( CoservMediaTypes = []string{ `application/coserv+cbor; profile="tag:github.com/veraison,2023:nvidia_coserv_proxy#1.0.0"`, } + SupportedCoservProfiles = []string{"tag:github.com/veraison,2023:nvidia_coserv_proxy#1.0.0"} ) diff --git a/vts/Makefile b/vts/Makefile index 3727b75f..b0f50ad4 100644 --- a/vts/Makefile +++ b/vts/Makefile @@ -4,6 +4,7 @@ SUBDIR += trustedservices SUBDIR += policymanager SUBDIR += cmd/vts-service +SUBDIR += endorsementstore # Create directories for packaging (TODO: May be a better way to do this) install: diff --git a/vts/cmd/vts-service/config-docker.yaml b/vts/cmd/vts-service/config-docker.yaml index 0e976972..7d873cc7 100644 --- a/vts/cmd/vts-service/config-docker.yaml +++ b/vts/cmd/vts-service/config-docker.yaml @@ -3,12 +3,10 @@ plugin: #backend: builtin backend: go-plugin - go-plugin: + go-plugin-schemes: dir: ../../../scheme/bin/ -store: - dbms: sqlite3 - dsn: file:/veraison/stores/vts/store.sql - trace-sql: true + go-plugin-stores: + dir: ../../../store-plugin/bin/ po-store: backend: sql sql: @@ -26,10 +24,18 @@ vts: ear-signer: alg: ES256 key: ./skey.jwk -coserv: - max-expiry: 5 mins - signer: - alg: ES256 - key: ./skey.jwk +endorsement-store: + coserv: + signer: + alg: ES256 + key: ./skey.jwk + max-expiry: 5 mins + active-plugins: + - corim-store + plugin-parameters: + corim-store: + dbms: sqlite3 + dsn: file::memory:?cache=shared + trace-sql: false logging: level: debug diff --git a/vts/cmd/vts-service/config.yaml b/vts/cmd/vts-service/config.yaml index dd9d2ca1..80cbf7b0 100644 --- a/vts/cmd/vts-service/config.yaml +++ b/vts/cmd/vts-service/config.yaml @@ -3,17 +3,15 @@ # automatically if run via ./run-vts script. plugin: backend: go-plugin - go-plugin: + go-plugin-schemes: dir: ../../../scheme/bin/ + go-plugin-stores: + dir: ../../../store-plugin/bin/ po-store: backend: sql sql: driver: sqlite3 datasource: /tmp/po-store.sql -store: - dbms: sqlite3 - dsn: file::memory:?cache=shared - trace-sql: false po-agent: backend: opa vts: @@ -28,11 +26,19 @@ vts: ear-signer: alg: ES256 key: ./skey.jwk -coserv: - max-expiry: 5 mins - signer: - alg: ES256 - key: ./skey.jwk +endorsement-store: + coserv: + signer: + alg: ES256 + key: ./skey.jwk + max-expiry: 5 mins + active-plugins: + - corim-store + plugin-parameters: + corim-store: + dbms: sqlite3 + dsn: file::memory:?cache=shared + trace-sql: false logging: level: debug # Scheme configuration Example diff --git a/vts/cmd/vts-service/main.go b/vts/cmd/vts-service/main.go index 3d1ce5e4..d46f41eb 100644 --- a/vts/cmd/vts-service/main.go +++ b/vts/cmd/vts-service/main.go @@ -18,8 +18,8 @@ import ( "github.com/veraison/services/policy" "github.com/veraison/services/vts/coserv" "github.com/veraison/services/vts/earsigner" + "github.com/veraison/services/vts/endorsementstore" "github.com/veraison/services/vts/policymanager" - "github.com/veraison/services/vts/store" "github.com/veraison/services/vts/trustedservices" ) @@ -31,8 +31,8 @@ func main() { log.Fatalf("could not read config: %v", err) } - subs, err := config.GetSubs(v, "store", "po-store", - "*po-agent", "plugin", "*vts", "ear-signer", "*coserv", "*logging", "*scheme") + subs, err := config.GetSubs(v, "po-store", "endorsement-store", + "*po-agent", "plugin", "*vts", "ear-signer", "*logging", "*scheme") if err != nil { log.Fatal(err) } @@ -42,12 +42,32 @@ func main() { log.Fatalf("could not configure logging: %v", err) } - log.Info("initializing stores") - enStore, err := store.New(subs["store"], log.Named("store")) + log.Info("processing endorsement store configuration") + endorsementStoreCfg := subs["endorsement-store"] + activeStorePlugins := endorsementStoreCfg.GetStringSlice("active-plugins") + if len(activeStorePlugins) == 0 { + log.Fatalf("could not find active plugins list in endorsement store config") + } + var ( + coservContext *coserv.Context + coservCfg *coserv.StoreConfig + ) + if cfg := endorsementStoreCfg.Sub("coserv"); cfg != nil { + coservContext, err = coserv.NewCoservContextFromViper(cfg) + if err != nil { + log.Fatalf("CoSERV config initialization failed: %v", err) + } + coservCfg = &coservContext.StoreConfig + } else { + log.Info("No CoSERV config supplied") + } + endorsementStorePluginConfig, err := endorsementstore.CreateStoreParams( + endorsementStoreCfg, coservCfg) if err != nil { - log.Fatalf("endorsement store initialization failed: %v", err) + log.Fatalf("failed to load endorsement store parameter: %v", err) } + log.Info("initializing policy store") poStore, err := policy.NewStore(subs["po-store"], log.Named("po-store")) if err != nil { log.Fatalf("policy store initialization failed: %v", err) @@ -61,65 +81,81 @@ func main() { log.Info("loading attestation schemes") var schemePluginManager plugin.IManager[handler.ISchemeHandler] - var coservProxyPluginManager plugin.IManager[handler.ICoservProxyHandler] + var endorsementStoreManager plugin.IManager[handler.IEndorsementStorePlugin] log.Debug("loading scheme configuration") - pluginConfig, err := plugin.ParametersMapFromViper(subs["scheme"], handler.PluginNameFromScheme) + schemePluginConfig, err := plugin.ParametersMapFromViper(subs["scheme"], handler.PluginNameFromScheme) if err != nil { log.Fatalf("could not load scheme config: %v", err) } - log.Debug("loading scheme plugins") - psubs, err := config.GetSubs(subs["plugin"], "*go-plugin", "*builtin") + log.Debug("loading scheme and endorsement store plugins") + psubs, err := config.GetSubs(subs["plugin"], "*go-plugin-schemes", "*builtin", "go-plugin-stores") if err != nil { log.Fatalf("could not get subs: %v", err) } switch config.SchemeLoader { case "plugins": - loader, err := plugin.CreateGoPluginLoader( - psubs["go-plugin"].AllSettings(), - pluginConfig, + schemeLoader, err := plugin.CreateGoPluginLoader( + psubs["go-plugin-schemes"].AllSettings(), + schemePluginConfig, log.Named("plugin")) if err != nil { log.Fatalf("could not create plugin loader: %v", err) } + storeLoader, err := plugin.CreateGoPluginLoader( + psubs["go-plugin-stores"].AllSettings(), + endorsementStorePluginConfig, + log.Named("store-loader")) + if err != nil { + log.Fatalf("could not create store plugin loader: %v", err) + } schemePluginManager, err = plugin.CreateGoPluginManagerWithLoader( - loader, + schemeLoader, "scheme-handler", log.Named("plugin"), handler.SchemeHandlerRPC) if err != nil { log.Fatalf("could not create store PluginManagerWithLoader: %v", err) } - coservProxyPluginManager, err = plugin.CreateGoPluginManagerWithLoader( - loader, - "coserv-proxy-handler", - log.Named("plugin"), - handler.CoservProxyHandlerRPC) + endorsementStoreManager, err = plugin.CreateGoPluginManagerWithLoader( + storeLoader, + "endorsement-store", + log.Named("store-plugin-manager"), + handler.EndorsementStoreRPC) if err != nil { - log.Fatalf("could not create coserv PluginManagerWithLoader: %v", err) + log.Fatalf("could not create store PluginManagerWithLoader: %v", err) } case "builtin": - loader, err := builtin.CreateBuiltinLoader( + schemeLoader, err := builtin.CreateBuiltinLoader( + psubs["builtin"].AllSettings(), + builtin.SchemePlugin, + schemePluginConfig, + log.Named("builtin-scheme")) + if err != nil { + log.Fatalf("could not create builtin scheme loader: %v", err) + } + storeLoader, err := builtin.CreateBuiltinLoader( psubs["builtin"].AllSettings(), - pluginConfig, - log.Named("builtin")) + builtin.StorePlugin, + endorsementStorePluginConfig, + log.Named("builtin-store")) if err != nil { - log.Fatalf("could not create builtin loader: %v", err) + log.Fatalf("could not create builtin store loader: %v", err) } schemePluginManager, err = builtin.CreateBuiltinManagerWithLoader[handler.ISchemeHandler]( - loader, log.Named("builtin"), + schemeLoader, log.Named("builtin"), "scheme-handler") if err != nil { log.Fatalf("could not create store BuiltinManagerWithLoader: %v", err) } - coservProxyPluginManager, err = builtin.CreateBuiltinManagerWithLoader[handler.ICoservProxyHandler]( - loader, log.Named("builtin"), - "coserv-handler") + endorsementStoreManager, err = builtin.CreateBuiltinManagerWithLoader[handler.IEndorsementStorePlugin]( + storeLoader, log.Named("builtin"), + "endorsement-store") if err != nil { - log.Fatalf("could not create coserv BuiltinManagerWithLoader: %v", err) + log.Fatalf("could not create endorsement-store BuiltinManagerWithLoader: %v", err) } default: log.Panicw("invalid SchemeLoader value", "SchemeLoader", config.SchemeLoader) @@ -135,36 +171,20 @@ func main() { log.Info("\t", mt) } - log.Info("CoSERV Proxy media types:") - for _, mt := range coservProxyPluginManager.GetRegisteredMediaTypes() { - log.Info("\t", mt) - } - log.Info("loading EAR signer") earSigner, err := earsigner.New(subs["ear-signer"], afero.NewOsFs()) if err != nil { log.Fatalf("EAR signer initialization failed: %v", err) } - var coservContext *coserv.Context - if subs["coserv"].IsSet("signer") { - coservContext, err = coserv.NewCoservContextFromViper(subs["coserv"]) - if err != nil { - log.Fatal("CoSERV config initialization: %v", err) - } - - // CoSERV media types. - log.Info("TODO CoSERV profile types:") - } - log.Info("initializing service") // from this point onwards taStore, enStore, evPluginManager, // endPluginManager, storePluginManager, coservProxyPluginManager, // policyManager and earSigner are owned by vts - vts := trustedservices.NewGRPC(enStore, - schemePluginManager, coservProxyPluginManager, - policyManager, earSigner, coservContext, log.Named("vts")) - + vts := trustedservices.NewGRPC( + schemePluginManager, endorsementStoreManager, + policyManager, earSigner, coservContext, activeStorePlugins, + log.Named("vts")) if err = vts.Init(subs["vts"]); err != nil { log.Fatalf("VTS initialisation failed: %v", err) } diff --git a/vts/coserv/context.go b/vts/coserv/context.go index 1d574c3e..5313d264 100644 --- a/vts/coserv/context.go +++ b/vts/coserv/context.go @@ -11,16 +11,14 @@ import ( "github.com/spf13/afero" "github.com/spf13/viper" - "github.com/veraison/corim/comid" "github.com/veraison/services/config" ) var FallbackMaxExpiry = 5 * time.Minute type Context struct { - Signer ISigner - FallbackAuthority *comid.CryptoKey - MaxExpiry time.Duration + StoreConfig + Signer ISigner } func NewCoservContextFromViper(v *viper.Viper) (*Context, error) { @@ -44,10 +42,11 @@ func NewCoservContextFromViper(v *viper.Viper) (*Context, error) { return nil, fmt.Errorf("fallback authority: %w", err) } + storeCfg := CreateStoreConfig(authority, expiry) + return &Context{ - Signer: signer, - MaxExpiry: expiry, - FallbackAuthority: authority, + StoreConfig: storeCfg, + Signer: signer, }, nil } diff --git a/vts/coserv/store_config.go b/vts/coserv/store_config.go new file mode 100644 index 00000000..f38d74a2 --- /dev/null +++ b/vts/coserv/store_config.go @@ -0,0 +1,69 @@ +// Copyright 2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package coserv + +import ( + "time" + + "github.com/veraison/corim/comid" + "github.com/veraison/services/plugin" +) + +// StoreConfig contains the CoSERV related config parameters +// that the endorsement store uses. +type StoreConfig struct { + Authority *comid.CryptoKey + MaxExpiry time.Duration +} + +func CreateStoreConfig(auth *comid.CryptoKey, exp time.Duration) StoreConfig { + return StoreConfig{ + auth, + exp, + } +} + +// private constants used for serializing and deserializing +// StoreConfig +var ( + fallbackAuthorityKey = "coserv-fallback-authority" + maxExpiryKey = "coserv-max-expiry" +) + +// Deserialize plugin.Parameters into StoreConfig +func (o *StoreConfig) FromParams(params *plugin.Parameters) error { + var key comid.CryptoKey + + auth, err := params.GetBytes(fallbackAuthorityKey) + if err != nil { + return err + } + + exp, err := params.GetInt64(maxExpiryKey) + if err != nil { + return err + } + + if err := (&key).UnmarshalCBOR(auth); err != nil { + return err + } + + o.Authority = &key + o.MaxExpiry = time.Duration(exp) + return nil +} + +// Serialize StoreConfig to plugin.Parameters +func (o *StoreConfig) ToParams() (*plugin.Parameters, error) { + auth, err := o.Authority.MarshalCBOR() + if err != nil { + return nil, err + } + exp := o.MaxExpiry.Nanoseconds() + + params := plugin.NewParameters(). + SetBytes(fallbackAuthorityKey, auth). + SetInt64(maxExpiryKey, exp) + + return params, nil +} diff --git a/vts/coserv/store_config_test.go b/vts/coserv/store_config_test.go new file mode 100644 index 00000000..0b6b6e85 --- /dev/null +++ b/vts/coserv/store_config_test.go @@ -0,0 +1,92 @@ +// Copyright 2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package coserv + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/veraison/corim/comid" + "github.com/veraison/services/plugin" +) + +func TestConfigStoreConfigFrom(t *testing.T) { + params1 := plugin.NewParameters() + params1.SetBytes(fallbackAuthorityKey, cryptoKeyCBOR()) + params1.SetInt64(maxExpiryKey, expiryInt()) + + params2 := plugin.NewParameters() + + params3 := plugin.NewParameters() + params3.SetBytes(fallbackAuthorityKey, cryptoKeyCBOR()) + + params4 := plugin.NewParameters() + params4.SetBytes(fallbackAuthorityKey, []byte{0xa2}) // invalid CBOR + params4.SetInt64(maxExpiryKey, expiryInt()) + + storePar1 := new(StoreConfig) + err := storePar1.FromParams(params1) + assert.NoError(t, err) + assert.Equal(t, storePar1.MaxExpiry, expiry()) + assert.Equal(t, *cryptoKey(), *storePar1.Authority) + + storePar2 := new(StoreConfig) + err = storePar2.FromParams(params2) + assert.Error(t, err) + + storePar3 := new(StoreConfig) + err = storePar3.FromParams(params3) + assert.Error(t, err) + + storePar4 := new(StoreConfig) + err = storePar4.FromParams(params4) + assert.Error(t, err) +} + +func TestConfigStoreConfigTo(t *testing.T) { + cfg := StoreConfig{ + cryptoKey(), + expiry(), + } + + params, err := (&cfg).ToParams() + + assert.NoError(t, err) + + exp, err := params.GetInt64(maxExpiryKey) + assert.NoError(t, err) + assert.Equal(t, exp, expiryInt()) + + auth, err := params.GetBytes(fallbackAuthorityKey) + assert.NoError(t, err) + assert.Equal(t, auth, cryptoKeyCBOR()) +} + +func cryptoKey() *comid.CryptoKey { + k, err := comid.NewCryptoKeyTaggedBytes([]byte{0, 0, 0, 0}) + if err != nil { + panic(err) + } + return k +} + +func cryptoKeyCBOR() []byte { + bytes, err := cryptoKey().MarshalCBOR() + if err != nil { + panic(err) + } + return bytes +} + +func expiryInt() int64 { + return expiry().Nanoseconds() +} + +func expiry() time.Duration { + d, err := time.ParseDuration("300s") + if err != nil { + panic(err) + } + return d +} diff --git a/vts/endorsementstore/Makefile b/vts/endorsementstore/Makefile new file mode 100644 index 00000000..b7926a1a --- /dev/null +++ b/vts/endorsementstore/Makefile @@ -0,0 +1,13 @@ +# Copyright 2026 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 + +INTERFACES := ../../handler/iendorsementstore.go + +MOCKPKG := mocks + +lint-hook-pre: _mocks + +include ../../mk/common.mk +include ../../mk/pkg.mk +include ../../mk/lint.mk +include ../../mk/test.mk diff --git a/vts/endorsementstore/Readme.md b/vts/endorsementstore/Readme.md new file mode 100644 index 00000000..5790a478 --- /dev/null +++ b/vts/endorsementstore/Readme.md @@ -0,0 +1,49 @@ +# VTS Endorsement Store + +The endorsement store used by VTS consists of a list of store plugins. The +implementation iterates over the list for each store operation, until it +succeeds. + +## Configuration +The endorsement store configuration is passed in the `endorsement-store` stanza +of the configuration yaml file. + +Example: +```yaml +endorsement-store: + coserv: + signer: + alg: ES256 + key: ./skey.jwk + max-expiry: 5 mins + active-plugins: + - corim-store + plugin-parameters: + corim-store: + dbms: sqlite3 + dsn: file::memory:?cache=shared + trace-sql: false +``` + +* `coserv`: contains the CoSERV configuration parameters of the store. These +parameters are passed along with the plugin parameters to each of the plugins +during plugin initialization. + +* `active-plugins`: contains the list of store plugins that will be used to +construct the endorsement-store. The plugins will be iterated over in the +order they are given in the config. + +* `plugin-parameters`: contains parameters for store plugins. + +### Notes + +* If the `active-stores` list contains a plugin name which is not loadable, +the service will fail to start. + +* If the `active-stores` section is empty or not present, the service will +fail to start. + +* **Important**: For submitting endorsements, only the first store in the list +is used, instead of iterating over the list of active stores. If a read-only +store is used as the first store in the list, endorsement provisioning will not +work. diff --git a/vts/endorsementstore/config.go b/vts/endorsementstore/config.go new file mode 100644 index 00000000..a34a1e2b --- /dev/null +++ b/vts/endorsementstore/config.go @@ -0,0 +1,73 @@ +// Copyright 2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package endorsementstore + +import ( + "fmt" + + "github.com/spf13/viper" + "github.com/veraison/services/plugin" + vtscoserv "github.com/veraison/services/vts/coserv" +) + +// Creates the plugin parameters map by combining the plugin specific +// parameters with the common CoSERV configuration +func CreateStoreParams( + cfg *viper.Viper, + coservCfg *vtscoserv.StoreConfig, +) (map[string]*plugin.Parameters, error) { + var ( + err error + coservParams *plugin.Parameters + pluginParams map[string]*plugin.Parameters + ) + if cfg == nil { + // caller should ensure that the config is non-nil + panic("empty endorsement store configuration") + } + if cfg.Sub("coserv") != nil && coservCfg == nil { + // plugins can assume that CoSERV API is disabled if CoSERV + // configuration is not passed to them + panic("invalid endorsement-store configuration for CoSERV") + } + + activePlugins := cfg.GetStringSlice("active-plugins") + if len(activePlugins) == 0 { + return nil, fmt.Errorf("failed to read active store plugins list") + } + + if p := cfg.Sub("plugin-parameters"); p != nil { + pluginParams, err = plugin.ParametersMapFromViper(p, nil) + if err != nil { + return nil, fmt.Errorf("failed to read store plugin parameters: %w", err) + } + } + + if coservCfg == nil { + // no CoSERV parameters to broadcast + return pluginParams, nil + } + + coservParams, err = coservCfg.ToParams() + if err != nil { + return nil, fmt.Errorf("invalid CoSERV parameters: %w", err) + } + + if pluginParams == nil { + pluginParams = make(map[string]*plugin.Parameters, len(activePlugins)) + } + + // broadcast coserv config to all plugins + for _, s := range activePlugins { + // create parameter maps for plugins that do not have + // config parameters in their plugin configuration + if _, ok := pluginParams[s]; !ok { + pluginParams[s] = plugin.NewParameters() + } + if err := pluginParams[s].Merge(coservParams); err != nil { + return nil, fmt.Errorf("failed to create store config: %w", err) + } + } + + return pluginParams, nil +} diff --git a/vts/endorsementstore/config_test.go b/vts/endorsementstore/config_test.go new file mode 100644 index 00000000..a9aca774 --- /dev/null +++ b/vts/endorsementstore/config_test.go @@ -0,0 +1,129 @@ +// Copyright 2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package endorsementstore + +import ( + "maps" + "slices" + "testing" + "time" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "github.com/veraison/corim/comid" + "github.com/veraison/services/plugin" + "github.com/veraison/services/vts/coserv" +) + +func TestCreateStoreParams(t *testing.T) { + cfg1 := viper.New() + cfg1.Set("coserv", coservSub()) + cfg1.Set("active-plugins", activePluginsSub()) + cfg1.Set("plugin-parameters", pluginParamsSub()) + + cfg2 := viper.New() + cfg2.Set("active-plugins", activePluginsSub()) + cfg2.Set("plugin-parameters", pluginParamsSub()) + + cfg3 := viper.New() + cfg3.Set("active-plugins", activePluginsSub()) + + cfg4 := viper.New() + cfg4.Set("coserv", coservSub()) + cfg4.Set("plugin-parameters", pluginParamsSub()) + + cfg5 := viper.New() + cfg5.Set("active-plugins", coservSub()) + + cfg6 := viper.New() + cfg6.Set("active-plugins", activePluginsSub()) + cfg6.Set("plugin-parameters", coservSub()) + + cfg7 := viper.New() + cfg7.Set("active-plugins", activePluginsSub()) + cfg7.Set("coserv", coservSub()) + + coservCfg := &coserv.StoreConfig{ + Authority: cryptoKey(), + MaxExpiry: expiry(), + } + + assert.Panics(t, func() { CreateStoreParams(cfg1, nil) }) //nolint:errcheck + assert.Panics(t, func() { CreateStoreParams(nil, coservCfg) }) //nolint:errcheck + + var ( + pm map[string]*plugin.Parameters + err error + ) + pm, err = CreateStoreParams(cfg1, coservCfg) + + assert.NoError(t, err) + assert.ElementsMatch(t, slices.Collect(maps.Keys(pm)), storeList()) + assert.Equal(t, pm["pl1"].MustGetString("pl1-cf1"), "val1") + + for _, cfg := range pm { + var storeCfg coserv.StoreConfig + assert.NoError(t, (&storeCfg).FromParams(cfg)) + assert.Equal(t, storeCfg.Authority, cryptoKey()) + assert.Equal(t, storeCfg.MaxExpiry, expiry()) + } + + _, err = CreateStoreParams(cfg2, coservCfg) + assert.NoError(t, err) + + _, err = CreateStoreParams(cfg3, coservCfg) + assert.NoError(t, err) + + _, err = CreateStoreParams(cfg5, coservCfg) + assert.Error(t, err) + + _, err = CreateStoreParams(cfg6, coservCfg) + assert.Error(t, err) + + _, err = CreateStoreParams(cfg7, coservCfg) + assert.NoError(t, err) +} + +func coservSub() map[string]any { + m := map[string]any{ + "signer": map[string]any{ + "alg": "xx", + "key": "yy", + }, + "max-expiry": "1m", + } + return m +} + +func activePluginsSub() []any { + return []any{"pl1", "pl2"} +} + +func pluginParamsSub() map[string]any { + return map[string]any{ + "pl1": map[string]any{ + "pl1-cf1": "val1", + "pl1-cf2": "val2", + }, + } +} + +func cryptoKey() *comid.CryptoKey { + k, err := comid.NewCryptoKeyTaggedBytes([]byte{0, 0, 0, 0}) + if err != nil { + panic(err) + } + return k +} + +func expiry() time.Duration { + d, err := time.ParseDuration("300s") + if err != nil { + panic(err) + } + return d +} + +func storeList() []string { + return []string{"pl1", "pl2"} +} diff --git a/vts/endorsementstore/mocks/iendorsementstore.go b/vts/endorsementstore/mocks/iendorsementstore.go new file mode 100644 index 00000000..7108af42 --- /dev/null +++ b/vts/endorsementstore/mocks/iendorsementstore.go @@ -0,0 +1,248 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ../../handler/iendorsementstore.go + +// Package mocks is a generated GoMock package. +package mocks + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + comid "github.com/veraison/corim/comid" + coserv "github.com/veraison/corim/coserv" + plugin "github.com/veraison/services/plugin" +) + +// MockIEndorsementStorePlugin is a mock of IEndorsementStorePlugin interface. +type MockIEndorsementStorePlugin struct { + ctrl *gomock.Controller + recorder *MockIEndorsementStorePluginMockRecorder +} + +// MockIEndorsementStorePluginMockRecorder is the mock recorder for MockIEndorsementStorePlugin. +type MockIEndorsementStorePluginMockRecorder struct { + mock *MockIEndorsementStorePlugin +} + +// NewMockIEndorsementStorePlugin creates a new mock instance. +func NewMockIEndorsementStorePlugin(ctrl *gomock.Controller) *MockIEndorsementStorePlugin { + mock := &MockIEndorsementStorePlugin{ctrl: ctrl} + mock.recorder = &MockIEndorsementStorePluginMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockIEndorsementStorePlugin) EXPECT() *MockIEndorsementStorePluginMockRecorder { + return m.recorder +} + +// AddCorimBytes mocks base method. +func (m *MockIEndorsementStorePlugin) AddCorimBytes(data []byte, scheme string, activate bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddCorimBytes", data, scheme, activate) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddCorimBytes indicates an expected call of AddCorimBytes. +func (mr *MockIEndorsementStorePluginMockRecorder) AddCorimBytes(data, scheme, activate interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddCorimBytes", reflect.TypeOf((*MockIEndorsementStorePlugin)(nil).AddCorimBytes), data, scheme, activate) +} + +// ExecuteCoservQuery mocks base method. +func (m *MockIEndorsementStorePlugin) ExecuteCoservQuery(profile, query string) (*coserv.Coserv, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExecuteCoservQuery", profile, query) + ret0, _ := ret[0].(*coserv.Coserv) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ExecuteCoservQuery indicates an expected call of ExecuteCoservQuery. +func (mr *MockIEndorsementStorePluginMockRecorder) ExecuteCoservQuery(profile, query interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecuteCoservQuery", reflect.TypeOf((*MockIEndorsementStorePlugin)(nil).ExecuteCoservQuery), profile, query) +} + +// Fini mocks base method. +func (m *MockIEndorsementStorePlugin) Fini() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Fini") + ret0, _ := ret[0].(error) + return ret0 +} + +// Fini indicates an expected call of Fini. +func (mr *MockIEndorsementStorePluginMockRecorder) Fini() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fini", reflect.TypeOf((*MockIEndorsementStorePlugin)(nil).Fini)) +} + +// GetAttestationScheme mocks base method. +func (m *MockIEndorsementStorePlugin) GetAttestationScheme() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAttestationScheme") + ret0, _ := ret[0].(string) + return ret0 +} + +// GetAttestationScheme indicates an expected call of GetAttestationScheme. +func (mr *MockIEndorsementStorePluginMockRecorder) GetAttestationScheme() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAttestationScheme", reflect.TypeOf((*MockIEndorsementStorePlugin)(nil).GetAttestationScheme)) +} + +// GetKeyTriples mocks base method. +func (m *MockIEndorsementStorePlugin) GetKeyTriples(env *comid.Environment, scheme string, exact bool) ([]*comid.KeyTriple, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetKeyTriples", env, scheme, exact) + ret0, _ := ret[0].([]*comid.KeyTriple) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetKeyTriples indicates an expected call of GetKeyTriples. +func (mr *MockIEndorsementStorePluginMockRecorder) GetKeyTriples(env, scheme, exact interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetKeyTriples", reflect.TypeOf((*MockIEndorsementStorePlugin)(nil).GetKeyTriples), env, scheme, exact) +} + +// GetName mocks base method. +func (m *MockIEndorsementStorePlugin) GetName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetName") + ret0, _ := ret[0].(string) + return ret0 +} + +// GetName indicates an expected call of GetName. +func (mr *MockIEndorsementStorePluginMockRecorder) GetName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetName", reflect.TypeOf((*MockIEndorsementStorePlugin)(nil).GetName)) +} + +// GetSupportedMediaTypes mocks base method. +func (m *MockIEndorsementStorePlugin) GetSupportedMediaTypes() map[string][]string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSupportedMediaTypes") + ret0, _ := ret[0].(map[string][]string) + return ret0 +} + +// GetSupportedMediaTypes indicates an expected call of GetSupportedMediaTypes. +func (mr *MockIEndorsementStorePluginMockRecorder) GetSupportedMediaTypes() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSupportedMediaTypes", reflect.TypeOf((*MockIEndorsementStorePlugin)(nil).GetSupportedMediaTypes)) +} + +// GetValueTriples mocks base method. +func (m *MockIEndorsementStorePlugin) GetValueTriples(env *comid.Environment, scheme string, exact bool) ([]*comid.ValueTriple, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetValueTriples", env, scheme, exact) + ret0, _ := ret[0].([]*comid.ValueTriple) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetValueTriples indicates an expected call of GetValueTriples. +func (mr *MockIEndorsementStorePluginMockRecorder) GetValueTriples(env, scheme, exact interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValueTriples", reflect.TypeOf((*MockIEndorsementStorePlugin)(nil).GetValueTriples), env, scheme, exact) +} + +// Init mocks base method. +func (m *MockIEndorsementStorePlugin) Init(params *plugin.Parameters) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Init", params) + ret0, _ := ret[0].(error) + return ret0 +} + +// Init indicates an expected call of Init. +func (mr *MockIEndorsementStorePluginMockRecorder) Init(params interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockIEndorsementStorePlugin)(nil).Init), params) +} + +// MockIEndorsementStore is a mock of IEndorsementStore interface. +type MockIEndorsementStore struct { + ctrl *gomock.Controller + recorder *MockIEndorsementStoreMockRecorder +} + +// MockIEndorsementStoreMockRecorder is the mock recorder for MockIEndorsementStore. +type MockIEndorsementStoreMockRecorder struct { + mock *MockIEndorsementStore +} + +// NewMockIEndorsementStore creates a new mock instance. +func NewMockIEndorsementStore(ctrl *gomock.Controller) *MockIEndorsementStore { + mock := &MockIEndorsementStore{ctrl: ctrl} + mock.recorder = &MockIEndorsementStoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockIEndorsementStore) EXPECT() *MockIEndorsementStoreMockRecorder { + return m.recorder +} + +// AddCorimBytes mocks base method. +func (m *MockIEndorsementStore) AddCorimBytes(data []byte, scheme string, activate bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddCorimBytes", data, scheme, activate) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddCorimBytes indicates an expected call of AddCorimBytes. +func (mr *MockIEndorsementStoreMockRecorder) AddCorimBytes(data, scheme, activate interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddCorimBytes", reflect.TypeOf((*MockIEndorsementStore)(nil).AddCorimBytes), data, scheme, activate) +} + +// ExecuteCoservQuery mocks base method. +func (m *MockIEndorsementStore) ExecuteCoservQuery(profile, query string) (*coserv.Coserv, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExecuteCoservQuery", profile, query) + ret0, _ := ret[0].(*coserv.Coserv) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ExecuteCoservQuery indicates an expected call of ExecuteCoservQuery. +func (mr *MockIEndorsementStoreMockRecorder) ExecuteCoservQuery(profile, query interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecuteCoservQuery", reflect.TypeOf((*MockIEndorsementStore)(nil).ExecuteCoservQuery), profile, query) +} + +// GetKeyTriples mocks base method. +func (m *MockIEndorsementStore) GetKeyTriples(env *comid.Environment, scheme string, exact bool) ([]*comid.KeyTriple, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetKeyTriples", env, scheme, exact) + ret0, _ := ret[0].([]*comid.KeyTriple) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetKeyTriples indicates an expected call of GetKeyTriples. +func (mr *MockIEndorsementStoreMockRecorder) GetKeyTriples(env, scheme, exact interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetKeyTriples", reflect.TypeOf((*MockIEndorsementStore)(nil).GetKeyTriples), env, scheme, exact) +} + +// GetValueTriples mocks base method. +func (m *MockIEndorsementStore) GetValueTriples(env *comid.Environment, scheme string, exact bool) ([]*comid.ValueTriple, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetValueTriples", env, scheme, exact) + ret0, _ := ret[0].([]*comid.ValueTriple) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetValueTriples indicates an expected call of GetValueTriples. +func (mr *MockIEndorsementStoreMockRecorder) GetValueTriples(env, scheme, exact interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValueTriples", reflect.TypeOf((*MockIEndorsementStore)(nil).GetValueTriples), env, scheme, exact) +} diff --git a/vts/endorsementstore/store.go b/vts/endorsementstore/store.go new file mode 100644 index 00000000..b1c3b02c --- /dev/null +++ b/vts/endorsementstore/store.go @@ -0,0 +1,171 @@ +// Copyright 2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package endorsementstore + +import ( + "errors" + + "github.com/veraison/corim/comid" + "github.com/veraison/corim/coserv" + "github.com/veraison/services/handler" + "github.com/veraison/services/plugin" + "go.uber.org/zap" +) + +type StoreManager = plugin.IManager[handler.IEndorsementStorePlugin] + +var ErrNoStores = errors.New("no store plugins active") + +type VtsEndorsementStore struct { + logger *zap.SugaredLogger + stores []handler.IEndorsementStorePlugin +} + +func (s *VtsEndorsementStore) addStore(store handler.IEndorsementStorePlugin) { + if s.stores == nil { + s.stores = []handler.IEndorsementStorePlugin{store} + } else { + s.stores = append(s.stores, store) + } +} + +func (s *VtsEndorsementStore) storeList() ([]handler.IEndorsementStorePlugin, error) { + if len(s.stores) == 0 { + return nil, ErrNoStores + } + return s.stores, nil +} + +func CreateEndorsementStore(pluginNames []string, manager StoreManager, logger *zap.SugaredLogger) (handler.IEndorsementStore, error) { + if len(pluginNames) == 0 { + err := errors.New("no names in `active-stores` list") + return nil, err + } + store := VtsEndorsementStore{ + logger: logger, + } + for _, pl := range pluginNames { + st, err := manager.LookupByName(pl) + if err != nil { + logger.Errorf("failed to load store `%s': %v", pl, err) + return nil, err + } + store.addStore(st) + } + return &store, nil +} + +func (s *VtsEndorsementStore) GetKeyTriples(env *comid.Environment, scheme string, exact bool) ([]*comid.KeyTriple, error) { + s.logger.Debugw("searching key triples for", "env", env) + errs := make(map[error]bool, 2) // keep track of kind of errors encountered + stores, err := s.storeList() + if err != nil { + s.logger.Errorf("failed to fetch store list: %v", err) + return nil, err + } + for _, store := range stores { + name := store.GetName() + t, err := store.GetKeyTriples(env, scheme, exact) + if err != nil { + if logOrErr(err, s.logger, name, &errs) != nil { + return nil, err + } + } else { + s.logger.Infof("found key triples in store `%s'", name) + return t, nil + } + } + return nil, computeStoreErr(errs) +} + +func (s *VtsEndorsementStore) GetValueTriples(env *comid.Environment, scheme string, exact bool) ([]*comid.ValueTriple, error) { + s.logger.Debugw("searching value triples for", "env", env) + errs := make(map[error]bool, 2) // keep track of kind of errors encountered + stores, err := s.storeList() + if err != nil { + s.logger.Errorf("failed to fetch store list: %v", err) + return nil, err + } + for _, store := range stores { + name := store.GetName() + t, err := store.GetValueTriples(env, scheme, exact) + if err != nil { + if logOrErr(err, s.logger, name, &errs) != nil { + return nil, err + } + } else { + s.logger.Infof("found value triples in store `%s'", name) + return t, nil + } + } + return nil, computeStoreErr(errs) +} + +func (s *VtsEndorsementStore) ExecuteCoservQuery(profile, query string) (*coserv.Coserv, error) { + s.logger.Debugw("coserv query", "profile", profile, "query", query) + errs := make(map[error]bool, 2) // keep track of kind of errors encountered + stores, err := s.storeList() + if err != nil { + s.logger.Errorf("failed to fetch store list: %v", err) + return nil, err + } + for _, store := range stores { + name := store.GetName() + res, err := store.ExecuteCoservQuery(profile, query) + if err != nil { + if logOrErr(err, s.logger, name, &errs) != nil { + return nil, err + } + } else { + s.logger.Infof("CoSERV results found in store `%s'", name) + return res, nil + } + } + return nil, computeStoreErr(errs) +} + +func (s *VtsEndorsementStore) AddCorimBytes(data []byte, scheme string, activate bool) error { + // only submit endorsements to the first store in list + stores, err := s.storeList() + if err != nil { + s.logger.Errorf("failed to fetch store list: %v", err) + return err + } + store := stores[0] // stores is guaranteed to contain at least one entry + name := store.GetName() + if err := store.AddCorimBytes(data, scheme, activate); err != nil { + s.logger.Errorf("failed to add endorsements to store `%s': %v", name, err) + return err + } + return nil +} + +func logOrErr(err error, logger *zap.SugaredLogger, name string, errs *map[error]bool) error { + if err != nil { + // errors.Is can be used because the plugin rpc client parses the error + // before returning. + if errors.Is(err, handler.ErrNotFound) { + (*errs)[handler.ErrNotFound] = true + logger.Debugf("not found in store `%s'", name) + return nil + } + if errors.Is(err, handler.ErrUnsupported) { + (*errs)[handler.ErrUnsupported] = true + logger.Debugf("store `%s' does not support operation", name) + return nil + } + logger.Errorf("Failed to fetch from store `%s': %v", name, err) + return err + } // else + return nil +} + +func computeStoreErr(errs map[error]bool) error { + // if ErrNotFound is encountered at least once, it means there + // is at least one store that supports the operation. + if _, ok := errs[handler.ErrNotFound]; ok { + return handler.ErrNotFound + } + // got ErrUnsupported from all stores + return handler.ErrUnsupported +} diff --git a/vts/endorsementstore/store_test.go b/vts/endorsementstore/store_test.go new file mode 100644 index 00000000..05e2c354 --- /dev/null +++ b/vts/endorsementstore/store_test.go @@ -0,0 +1,339 @@ +// Copyright 2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package endorsementstore + +import ( + "fmt" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/veraison/corim/comid" + "github.com/veraison/corim/coserv" + "github.com/veraison/services/handler" + "github.com/veraison/services/log" + mockstore "github.com/veraison/services/vts/endorsementstore/mocks" +) + +var ( + errUnsupp = fmt.Errorf("error: %w", handler.ErrUnsupported) + errNotFound = fmt.Errorf("error: %w", handler.ErrNotFound) + errUnexpected = fmt.Errorf("unexpected error") +) + +func TestGetKeyTriples(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + storeNotFound := notFoundStore(t, ctrl) + storeUnsupported := unsupportedStore(t, ctrl) + storeUnexpected := unexpStore(t, ctrl) + storeValid := validStore(t, ctrl) + + store0 := VtsEndorsementStore{ + log.Named("store0"), + []handler.IEndorsementStorePlugin{}, + } + + store1 := VtsEndorsementStore{ + log.Named("store1"), + nil, + } + store1.addStore(storeNotFound) + store1.addStore(storeUnsupported) + store1.addStore(storeNotFound) + + store2 := VtsEndorsementStore{ + log.Named("store2"), + []handler.IEndorsementStorePlugin{}, + } + store2.addStore(storeUnsupported) + store2.addStore(storeUnsupported) + + store3 := VtsEndorsementStore{ + log.Named("store3"), + []handler.IEndorsementStorePlugin{}, + } + store3.addStore(storeNotFound) + store3.addStore(storeUnsupported) + store3.addStore(storeUnexpected) + + store4 := VtsEndorsementStore{ + log.Named("store4"), + []handler.IEndorsementStorePlugin{}, + } + store4.addStore(storeNotFound) + store4.addStore(storeUnsupported) + store4.addStore(storeValid) + + _, e0 := store0.GetKeyTriples(nil, "", false) + _, e1 := store1.GetKeyTriples(nil, "", false) + _, e2 := store2.GetKeyTriples(nil, "", false) + _, e3 := store3.GetKeyTriples(nil, "", false) + _, e4 := store4.GetKeyTriples(nil, "", false) + + assert.ErrorIs(t, e0, ErrNoStores) + assert.ErrorIs(t, e1, handler.ErrNotFound, "expected ENOTFOUND") + assert.ErrorIs(t, e2, handler.ErrUnsupported, "expected EUNSUPPORTED") + assert.ErrorContains(t, e3, errUnexpected.Error()) + assert.NoError(t, e4) +} + +func TestGetValueTriples(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + storeNotFound := notFoundStore(t, ctrl) + storeUnsupported := unsupportedStore(t, ctrl) + storeUnexpected := unexpStore(t, ctrl) + storeValid := validStore(t, ctrl) + + store0 := VtsEndorsementStore{ + log.Named("store0"), + []handler.IEndorsementStorePlugin{}, + } + + store1 := VtsEndorsementStore{ + log.Named("store1"), + []handler.IEndorsementStorePlugin{}, + } + store1.addStore(storeNotFound) + store1.addStore(storeUnsupported) + store1.addStore(storeNotFound) + + store2 := VtsEndorsementStore{ + log.Named("store2"), + []handler.IEndorsementStorePlugin{}, + } + store2.addStore(storeUnsupported) + store2.addStore(storeUnsupported) + + store3 := VtsEndorsementStore{ + log.Named("store3"), + []handler.IEndorsementStorePlugin{}, + } + store3.addStore(storeNotFound) + store3.addStore(storeUnsupported) + store3.addStore(storeUnexpected) + + store4 := VtsEndorsementStore{ + log.Named("store4"), + []handler.IEndorsementStorePlugin{}, + } + store4.addStore(storeNotFound) + store4.addStore(storeUnsupported) + store4.addStore(storeValid) + + _, e0 := store0.GetValueTriples(nil, "", false) + _, e1 := store1.GetValueTriples(nil, "", false) + _, e2 := store2.GetValueTriples(nil, "", false) + _, e3 := store3.GetValueTriples(nil, "", false) + _, e4 := store4.GetValueTriples(nil, "", false) + + assert.ErrorIs(t, e0, ErrNoStores) + assert.ErrorIs(t, e1, handler.ErrNotFound, "expected ENOTFOUND") + assert.ErrorIs(t, e2, handler.ErrUnsupported, "expected EUNSUPPORTED") + assert.ErrorContains(t, e3, errUnexpected.Error()) + assert.NoError(t, e4) +} + +func TestExecuteCoservQuery(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + storeNotFound := notFoundStore(t, ctrl) + storeUnsupported := unsupportedStore(t, ctrl) + storeUnexpected := unexpStore(t, ctrl) + storeValid := validStore(t, ctrl) + + store0 := VtsEndorsementStore{ + log.Named("store0"), + []handler.IEndorsementStorePlugin{}, + } + + store1 := VtsEndorsementStore{ + log.Named("store1"), + []handler.IEndorsementStorePlugin{}, + } + store1.addStore(storeNotFound) + store1.addStore(storeUnsupported) + store1.addStore(storeNotFound) + + store2 := VtsEndorsementStore{ + log.Named("store2"), + []handler.IEndorsementStorePlugin{}, + } + store2.addStore(storeUnsupported) + store2.addStore(storeUnsupported) + + store3 := VtsEndorsementStore{ + log.Named("store3"), + []handler.IEndorsementStorePlugin{}, + } + store3.addStore(storeNotFound) + store3.addStore(storeUnsupported) + store3.addStore(storeUnexpected) + + store4 := VtsEndorsementStore{ + log.Named("store4"), + []handler.IEndorsementStorePlugin{}, + } + store4.addStore(storeNotFound) + store4.addStore(storeUnsupported) + store4.addStore(storeValid) + + _, e0 := store0.ExecuteCoservQuery("", "") + _, e1 := store1.ExecuteCoservQuery("", "") + _, e2 := store2.ExecuteCoservQuery("", "") + _, e3 := store3.ExecuteCoservQuery("", "") + _, e4 := store4.ExecuteCoservQuery("", "") + + assert.ErrorIs(t, e0, ErrNoStores) + assert.ErrorIs(t, e1, handler.ErrNotFound, "expected ENOTFOUND") + assert.ErrorIs(t, e2, handler.ErrUnsupported, "expected EUNSUPPORTED") + assert.ErrorContains(t, e3, errUnexpected.Error()) + assert.NoError(t, e4) +} + +func TestAddCorimBytes(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + storeUnexpected := unexpStore(t, ctrl) + storeValid := validStore(t, ctrl) + + store0 := VtsEndorsementStore{ + log.Named("store0"), + []handler.IEndorsementStorePlugin{}, + } + + store1 := VtsEndorsementStore{ + log.Named("store1"), + []handler.IEndorsementStorePlugin{}, + } + store1.addStore(storeUnexpected) + store1.addStore(storeValid) + + store2 := VtsEndorsementStore{ + log.Named("store2"), + []handler.IEndorsementStorePlugin{}, + } + store2.addStore(storeValid) + store2.addStore(storeUnexpected) + + e0 := store0.AddCorimBytes(make([]byte, 0), "", false) + e1 := store1.AddCorimBytes(make([]byte, 0), "", false) + e2 := store2.AddCorimBytes(make([]byte, 0), "", false) + + assert.ErrorIs(t, e0, ErrNoStores) + assert.Error(t, e1) + assert.NoError(t, e2) +} + +func TestComputeStoreErr(t *testing.T) { + m := make(map[error]bool, 2) + m[handler.ErrUnsupported] = true + assert.ErrorIs(t, computeStoreErr(m), handler.ErrUnsupported) + + m[handler.ErrNotFound] = true + assert.ErrorIs(t, computeStoreErr(m), handler.ErrNotFound) +} + +func TestLogOrErr(t *testing.T) { + var res error + + res = logOrErr(handler.ErrNotFound, log.Named("temp"), "", &map[error]bool{}) + assert.NoError(t, res) + + res = logOrErr(handler.ErrUnsupported, log.Named("temp"), "", &map[error]bool{}) + assert.NoError(t, res) + + res = logOrErr(errUnexpected, log.Named("temp"), "", &map[error]bool{}) + assert.ErrorIs(t, res, errUnexpected) +} + +func TestStoreList(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + store0 := VtsEndorsementStore{ + log.Named("store0"), + []handler.IEndorsementStorePlugin{}, + } + + store1 := VtsEndorsementStore{ + log.Named("store0"), + []handler.IEndorsementStorePlugin{}, + } + store1.addStore(validStore(t, ctrl)) + + _, e0 := store0.storeList() + _, e1 := store1.storeList() + + assert.ErrorIs(t, e0, ErrNoStores) + assert.NoError(t, e1) +} + +func notFoundStore(t *testing.T, ctrl *gomock.Controller) *mockstore.MockIEndorsementStorePlugin { + return errStore(t, ctrl, errNotFound) +} + +func unsupportedStore(t *testing.T, ctrl *gomock.Controller) *mockstore.MockIEndorsementStorePlugin { + return errStore(t, ctrl, errUnsupp) +} + +func unexpStore(t *testing.T, ctrl *gomock.Controller) *mockstore.MockIEndorsementStorePlugin { + return errStore(t, ctrl, errUnexpected) +} + +func errStore(t *testing.T, ctrl *gomock.Controller, err error) *mockstore.MockIEndorsementStorePlugin { + storeUnexpected := mockstore.NewMockIEndorsementStorePlugin(ctrl) + storeUnexpected.EXPECT(). + GetKeyTriples(gomock.Any(), gomock.Any(), gomock.Any()). + AnyTimes(). + Return(nil, err) + storeUnexpected.EXPECT(). + GetValueTriples(gomock.Any(), gomock.Any(), gomock.Any()). + AnyTimes(). + Return(nil, err) + storeUnexpected.EXPECT(). + ExecuteCoservQuery(gomock.Any(), gomock.Any()). + AnyTimes(). + Return(nil, err) + storeUnexpected.EXPECT(). + AddCorimBytes(gomock.Any(), gomock.Any(), gomock.Any()). + AnyTimes(). + Return(err) + storeUnexpected.EXPECT(). + GetName(). + AnyTimes(). + Return("error-store") + + return storeUnexpected +} + +func validStore(t *testing.T, ctrl *gomock.Controller) *mockstore.MockIEndorsementStorePlugin { + storeValid := mockstore.NewMockIEndorsementStorePlugin(ctrl) + storeValid.EXPECT(). + GetKeyTriples(gomock.Any(), gomock.Any(), gomock.Any()). + AnyTimes(). + Return([]*comid.KeyTriple{&comid.KeyTriple{}}, nil) + storeValid.EXPECT(). + GetValueTriples(gomock.Any(), gomock.Any(), gomock.Any()). + AnyTimes(). + Return([]*comid.ValueTriple{&comid.ValueTriple{}}, nil) + storeValid.EXPECT(). + ExecuteCoservQuery(gomock.Any(), gomock.Any()). + AnyTimes(). + Return(&coserv.Coserv{}, nil) + storeValid.EXPECT(). + AddCorimBytes(gomock.Any(), gomock.Any(), gomock.Any()). + AnyTimes(). + Return(nil) + storeValid.EXPECT(). + GetName(). + AnyTimes(). + Return("valid-store") + + return storeValid +} diff --git a/vts/trustedservices/trustedservices_grpc.go b/vts/trustedservices/trustedservices_grpc.go index 96b7c4fe..040338f0 100644 --- a/vts/trustedservices/trustedservices_grpc.go +++ b/vts/trustedservices/trustedservices_grpc.go @@ -22,7 +22,6 @@ import ( "google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/structpb" - corimstore "github.com/veraison/corim-store/pkg/store" "github.com/veraison/corim/comid" "github.com/veraison/corim/corim" "github.com/veraison/corim/coserv" @@ -34,6 +33,7 @@ import ( "github.com/veraison/services/vts/appraisal" vtscoserv "github.com/veraison/services/vts/coserv" "github.com/veraison/services/vts/earsigner" + "github.com/veraison/services/vts/endorsementstore" "github.com/veraison/services/vts/policymanager" ) @@ -68,13 +68,14 @@ func NewGRPCConfig() *GRPCConfig { type GRPC struct { ServerAddress string - Store *corimstore.Store - SchemePluginManager plugin.IManager[handlermod.ISchemeHandler] - CoservProxyPluginManager plugin.IManager[handlermod.ICoservProxyHandler] - PolicyManager *policymanager.PolicyManager - EarSigner earsigner.IEarSigner - CoservContext *vtscoserv.Context - rootCerts *x509.CertPool + SchemePluginManager plugin.IManager[handlermod.ISchemeHandler] + StoreManager plugin.IManager[handlermod.IEndorsementStorePlugin] + PolicyManager *policymanager.PolicyManager + EarSigner earsigner.IEarSigner + CoservContext *vtscoserv.Context + rootCerts *x509.CertPool + endorsementStore handlermod.IEndorsementStore + activeStorePlugins []string Server *grpc.Server Socket net.Listener @@ -85,22 +86,22 @@ type GRPC struct { } func NewGRPC( - store *corimstore.Store, schemePluginManager plugin.IManager[handlermod.ISchemeHandler], - coservProxyPluginManager plugin.IManager[handlermod.ICoservProxyHandler], + storeManager plugin.IManager[handlermod.IEndorsementStorePlugin], policyManager *policymanager.PolicyManager, earSigner earsigner.IEarSigner, coservConfig *vtscoserv.Context, + activeStorePlugins []string, logger *zap.SugaredLogger, ) ITrustedServices { return &GRPC{ - Store: store, - SchemePluginManager: schemePluginManager, - CoservProxyPluginManager: coservProxyPluginManager, - PolicyManager: policyManager, - EarSigner: earSigner, - CoservContext: coservConfig, - logger: logger, + SchemePluginManager: schemePluginManager, + StoreManager: storeManager, + PolicyManager: policyManager, + EarSigner: earSigner, + CoservContext: coservConfig, + activeStorePlugins: activeStorePlugins, + logger: logger, } } @@ -128,6 +129,11 @@ func (o *GRPC) Init( return err } + if err := o.initStore(); err != nil { + o.logger.Errorf("failed to initialize active stores: %v", err) + return err + } + if cfg.ListenAddress != "" { o.ServerAddress = cfg.ListenAddress } else { @@ -167,6 +173,15 @@ func (o *GRPC) Init( return nil } +func (o *GRPC) initStore() error { + store, err := endorsementstore.CreateEndorsementStore(o.activeStorePlugins, o.StoreManager, o.logger) + if err != nil { + return err + } + o.endorsementStore = store + return nil +} + func (o *GRPC) Close() error { if o.Server != nil { o.Server.GracefulStop() @@ -176,12 +191,8 @@ func (o *GRPC) Close() error { o.logger.Errorf("scheme plugin manager shutdown failed: %v", err) } - if err := o.CoservProxyPluginManager.Close(); err != nil { - o.logger.Errorf("coserv plugin manager shutdown failed: %v", err) - } - - if err := o.Store.Close(); err != nil { - o.logger.Errorf("store closure failed: %v", err) + if err := o.StoreManager.Close(); err != nil { + o.logger.Errorf("store plugin manager shutdown failed: %v", err) } if err := o.EarSigner.Close(); err != nil { @@ -271,9 +282,8 @@ func (o *GRPC) SubmitEndorsements( } else if !resp.IsValid { return submitEndorsementErrorResponse(resp.Error()), nil } - label := fmt.Sprintf("%s/%s", DummyTenantID, handlerPlugin.GetAttestationScheme()) - if err := o.Store.AddBytes(req.Data, label, true); err != nil { + if err := o.endorsementStore.AddCorimBytes(req.Data, label, true); err != nil { return submitEndorsementErrorResponse(err), nil } @@ -407,14 +417,21 @@ func (o *GRPC) GetAttestation( // we are forced to do inexact matching here for now, and leave // it to the attestation schemes to resolve this. matchExactly := false - trustAnchors, err := o.getKeyTriples(appraisal.TrustAnchorIDs, appraisal.StoreLabel(), matchExactly) + var trustAnchors []*comid.KeyTriple + trustAnchors, err = o.getKeyTriples(appraisal.TrustAnchorIDs, appraisal.StoreLabel(), matchExactly) + if err != nil { - if errors.Is(err, corimstore.ErrNoMatch) { - err = handlermod.BadEvidence("no trust anchor for %s", appraisal.DescribeTrustAnchorIDs()) - appraisal.SetAllClaims(ear.CryptoValidationFailedClaim) - appraisal.AddPolicyClaim("problem", "no trust anchor for evidence") + if errors.Is(err, handlermod.ErrNotFound) { + // finalize will be called in the next block + o.logger.Warn("no trust anchor in store") + } else { + return o.finalize(appraisal, err) } - + } + if trustAnchors == nil { + err = handlermod.BadEvidence("no trust anchor for %s", appraisal.DescribeTrustAnchorIDs()) + appraisal.SetAllClaims(ear.CryptoValidationFailedClaim) + appraisal.AddPolicyClaim("problem", "no trust anchor for evidence") return o.finalize(appraisal, err) } @@ -436,10 +453,15 @@ func (o *GRPC) GetAttestation( "software-id", appraisal.ReferenceValueIDs, "trust-anchor-id", appraisal.TrustAnchorIDs) + var endorsements []*comid.ValueTriple o.logger.Debug("obtaining endorsements...") - endorsements, err := o.getValueTriples(appraisal.ReferenceValueIDs, appraisal.StoreLabel(), true) + endorsements, err = o.getValueTriples(appraisal.ReferenceValueIDs, appraisal.StoreLabel(), true) if err != nil { - return o.finalize(appraisal, err) + if errors.Is(err, handlermod.ErrNotFound) { + o.logger.Warn("no reference values in store") + } else { + return o.finalize(appraisal, err) + } } o.logger.Debug("validating evidence...") @@ -484,17 +506,22 @@ func (o *GRPC) getKeyTriples( label string, exact bool, ) ([]*comid.KeyTriple, error) { + var keyTriples []*comid.KeyTriple //nolint for _, taID := range trustAnchorIDs { - triples, err := o.Store.GetActiveKeyTriples(taID, label, exact) + triples, err := o.endorsementStore.GetKeyTriples(taID, label, exact) if err != nil { + o.logger.Warnw("could not find in store", "taID", taID, "error", err) return nil, err } - keyTriples = append(keyTriples, triples...) } + if len(keyTriples) == 0 { + return nil, handlermod.ErrNotFound + } + return keyTriples, nil } @@ -503,17 +530,22 @@ func (o *GRPC) getValueTriples( label string, exact bool, ) ([]*comid.ValueTriple, error) { + var valueTriples []*comid.ValueTriple //nolint for _, valID := range referenceValueIDs { - triples, err := o.Store.GetActiveValueTriples(valID, label, exact) - if err != nil && !errors.Is(err, corimstore.ErrNoMatch) { + triples, err := o.endorsementStore.GetValueTriples(valID, label, exact) + if err != nil && !errors.Is(err, handlermod.ErrNotFound) { + o.logger.Warnw("could not find in store", "valID", valID, "error", err) return nil, err } - valueTriples = append(valueTriples, triples...) } + if len(valueTriples) == 0 { + return nil, handlermod.ErrNotFound + } + return valueTriples, nil } @@ -558,8 +590,11 @@ func (c *GRPC) GetSupportedCoservMediaTypes(context.Context, *emptypb.Empty) (*p "application/rim+cbor", ) + // FIXME(dhanus): Implement a way of obtaining the supported + // CoSERV profiles from the store plugins. The MediaType associated + // with store plugins are temporarily used until then. coservProxyDerived := c.assembleCoservMediaTypes( - c.CoservProxyPluginManager.GetRegisteredMediaTypes(), + c.StoreManager.GetRegisteredMediaTypes(), "application/coserv+cbor", ) @@ -632,29 +667,29 @@ func getEndorsementsError(err error) *proto.EndorsementQueryOut { } } -func (o *GRPC) getEndorsementsFromStores(queryIn *proto.EndorsementQueryIn) ([]byte, error) { +func (o *GRPC) getEndorsementsFromStores( + queryIn *proto.EndorsementQueryIn, +) ([]byte, error) { var query coserv.Coserv if err := query.FromBase64Url(queryIn.Query); err != nil { return nil, err } - coservService := corimstore.NewCoSERVService( - o.Store, - o.CoservContext.FallbackAuthority, - o.CoservContext.MaxExpiry, - ) - if err := coservService.UpdateCoSERV(&query); err != nil { + _, mtParams, err := mime.ParseMediaType(queryIn.MediaType) + if err != nil { + o.logger.Warnf("Bad request: could not parse media type: %v", err) return nil, err } + profile := mtParams["profile"] - return query.ToCBOR() -} + resp, err := o.endorsementStore.ExecuteCoservQuery(profile, queryIn.Query) + if err != nil { + o.logger.Infof("could not find coserv result in store: %v", err) + } else { + return resp.ToCBOR() + } -func (o *GRPC) getEndorsementsFromProxy( - handlerPlugin handlermod.ICoservProxyHandler, - query *proto.EndorsementQueryIn, -) ([]byte, error) { - return handlerPlugin.GetEndorsements(DummyTenantID, query.Query) + return nil, handlermod.ErrNotFound } func (o *GRPC) GetEndorsements( @@ -664,20 +699,11 @@ func (o *GRPC) GetEndorsements( o.logger.Debugw("GetEndorsements", "media-type", query.MediaType) var ( - err error - out []byte - handlerPlugin handlermod.ICoservProxyHandler + err error + out []byte ) - // First, check to see if we have a CoSERV proxy plugin that can handle this query - handlerPlugin, err = o.CoservProxyPluginManager.LookupByMediaType(query.MediaType) - if err == nil { - // No error means we have a proxy plugin, so delegate to that. - out, err = o.getEndorsementsFromProxy(handlerPlugin, query) - } else { - // There was no proxy plugin, so assume we can obtain from own stores - out, err = o.getEndorsementsFromStores(query) - } + out, err = o.getEndorsementsFromStores(query) if err != nil { return getEndorsementsError(err), nil