Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions PLUGIN_VERSIONING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Plugin Versioning Implementation (Issue #120)

## Summary

This PR implements versioning support for Veraison plugins and scheme implementations, addressing **Issue #120**.

**Issue Requirements:**
- ✅ Add versioning to pluggable interfaces
- ✅ Log versions during plugin discovery
- ✅ Expose versions via meta query interface (ServiceState API)
- ✅ Allow clients to understand level of support for particular formats

## Implementation Approach

**Scheme-Level Versioning:** All pluggables associated with a scheme (evidence handler, endorsement handler, store handler) share the same version, consistent with how they share the scheme name.

## Key Changes

### 1. Core Plugin Interface
- Added `GetVersion() string` to `IPluggable` interface
- All plugins must now return semantic version (e.g., "1.0.0")

### 2. Version Exposure Methods

**Manager Interface (`plugin/imanager.go`):**
```go
GetPluginVersion(name string) (string, error)
GetSchemeVersion(scheme string) (string, error)
```

**ServiceState API (proto/state.proto):**
```protobuf
map<string, string> scheme_versions = 4;
```

### 3. Logging
Plugins now log version at INFO level during discovery:
```
loaded plugin | name=psa-evidence-handler scheme=PSA_IOT version=1.0.0 path=...
```

### 4. All Schemes Updated

| Scheme | Version |
|--------|---------|
| PSA_IOT | 1.0.0 |
| ARM_CCA | 1.0.0 |
| PARSEC_CCA | 1.0.0 |
| PARSEC_TPM | 1.0.0 |
| RIOT | 1.0.0 |
| TPM_ENACTTRUST | 1.0.0 |

## Files Modified (39 total)

**Core Framework:**
- `plugin/ipluggable.go` - Added GetVersion() method
- `plugin/goplugin_context.go` - Version tracking
- `plugin/goplugin_loader.go` - Version logging
- `plugin/goplugin_manager.go` - Version query methods
- `plugin/imanager.go` - Manager interface extension
- `builtin/builtin_loader.go` - Version logging
- `builtin/builtin_manager.go` - Version query methods

**RPC Support:**
- `handler/evidence_rpc.go`
- `handler/endorsement_rpc.go`
- `handler/store_rpc.go`

**All 6 Scheme Implementations:**
- Added `SchemeVersion` constants
- Implemented `GetVersion()` in all handlers

**Test Infrastructure:**
- Updated test plugins and interfaces

## Usage Examples

### Query via Manager
```go
version, err := pluginManager.GetSchemeVersion("PSA_IOT")
// Returns: "1.0.0"
```

### Query via API
```
GET /verification/v1/state
```
Response includes:
```json
{
"scheme_versions": {
"PSA_IOT": "1.0.0",
"ARM_CCA": "1.0.0",
...
}
}
```

## Build Status
✅ All packages compile successfully
✅ No errors or warnings

## Resolves
Closes #120
6 changes: 5 additions & 1 deletion builtin/builtin_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,11 @@ func DiscoverBuiltinUsing[I plugin.IPluggable](loader *BuiltinLoader) error {
loader.logger.Panicw("duplicate plugin name", "name", name)
}

loader.logger.Debugw("found plugin", "name", name)
loader.logger.Infow("loaded builtin plugin",
"name", name,
"scheme", p.GetAttestationScheme(),
"version", p.GetVersion(),
)
loader.loadedByName[name] = p

for _, mt := range p.GetSupportedMediaTypes() {
Expand Down
19 changes: 19 additions & 0 deletions builtin/builtin_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,22 @@ func (o *BuiltinManager[I]) LookupByAttestationScheme(scheme string) (I, error)
func (o *BuiltinManager[I]) LookupByMediaType(mediaType string) (I, error) {
return GetBuiltinHandleByMediaTypeUsing[I](o.loader, mediaType)
}

func (o *BuiltinManager[I]) GetPluginVersion(name string) (string, error) {
pluggable, ok := o.loader.loadedByName[name]
if !ok {
return "", plugin.ErrNotFound
}

return pluggable.GetVersion(), nil
}

func (o *BuiltinManager[I]) GetSchemeVersion(scheme string) (string, error) {
for _, p := range o.loader.loadedByName {
if _, ok := p.(I); ok && p.GetAttestationScheme() == scheme {
return p.GetVersion(), nil
}
}

return "", plugin.ErrNotFound
}
20 changes: 20 additions & 0 deletions handler/endorsement_rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ func (s *EndorsementRPCServer) GetSupportedMediaTypes(args interface{}, resp *[]
return nil
}

func (s *EndorsementRPCServer) GetVersion(args interface{}, resp *string) error {
*resp = s.Impl.GetVersion()
return nil
}

func (s EndorsementRPCServer) Decode(args []byte, resp *[]byte) error {
var decodeArgs struct {
Data []byte
Expand Down Expand Up @@ -148,6 +153,21 @@ func (c EndorsementRPCClient) GetSupportedMediaTypes() []string {
return resp
}

func (c EndorsementRPCClient) GetVersion() string {
var (
err error
resp string
unused interface{}
)

err = c.client.Call("Plugin.GetVersion", &unused, &resp)
if err != nil {
return ""
}

return resp
}

func (c EndorsementRPCClient) Decode(data []byte, mediaType string, caCertPool []byte) (*EndorsementHandlerResponse, error) {
var (
err error
Expand Down
20 changes: 20 additions & 0 deletions handler/evidence_rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ func (s *RPCServer) GetSupportedMediaTypes(args interface{}, resp *[]string) err
return nil
}

func (s *RPCServer) GetVersion(args interface{}, resp *string) error {
*resp = s.Impl.GetVersion()
return nil
}

type ExtractClaimsArgs struct {
Token []byte
TrustAnchors []string
Expand Down Expand Up @@ -163,6 +168,21 @@ func (s *RPCClient) GetSupportedMediaTypes() []string {
return resp
}

func (s *RPCClient) GetVersion() string {
var (
resp string
unused interface{}
)

err := s.client.Call("Plugin.GetVersion", &unused, &resp)
if err != nil {
log.Errorf("Plugin.GetVersion RPC call failed: %v", err) // nolint
return ""
}

return resp
}

func (s *RPCClient) ExtractEvidence(
token *proto.AttestationToken,
trustAnchors []string,
Expand Down
20 changes: 20 additions & 0 deletions handler/store_rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ func (s *StoreRPCServer) GetSupportedMediaTypes(args interface{}, resp *[]string
return nil
}

func (s *StoreRPCServer) GetVersion(args interface{}, resp *string) error {
*resp = s.Impl.GetVersion()
return nil
}

type SynthKeysArgs struct {
TenantID string
EndorsementJSON []byte
Expand Down Expand Up @@ -186,6 +191,21 @@ func (c StoreRPCClient) GetSupportedMediaTypes() []string {
return resp
}

func (c StoreRPCClient) GetVersion() string {
var (
err error
resp string
unused interface{}
)

err = c.client.Call("Plugin.GetVersion", &unused, &resp)
if err != nil {
return ""
}

return resp
}

func (s *StoreRPCClient) SynthKeysFromRefValue(tenantID string, refVal *Endorsement) ([]string, error) {
var (
err error
Expand Down
8 changes: 8 additions & 0 deletions plugin/goplugin_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type IPluginContext interface {
GetTypeName() string
GetPath() string
GetHandle() interface{}
GetVersion() string
Close()
}

Expand All @@ -33,6 +34,8 @@ type PluginContext[I IPluggable] struct {
Name string
// Name of the attestatin scheme implemented by this plugin
Scheme string
// Version of this plugin implementation
Version string
// SupportedMediaTypes are the types of input this plugin can process.
// This is is the method by which a plugin is selected.
SupportedMediaTypes []string
Expand Down Expand Up @@ -63,6 +66,10 @@ func (o PluginContext[I]) GetHandle() interface{} {
return o.Handle
}

func (o PluginContext[I]) GetVersion() string {
return o.Version
}

func (o PluginContext[I]) Close() {
if o.client != nil {
o.client.Kill()
Expand Down Expand Up @@ -121,6 +128,7 @@ func createPluginContext[I IPluggable](
Path: path,
Name: handle.GetName(),
Scheme: handle.GetAttestationScheme(),
Version: handle.GetVersion(),
SupportedMediaTypes: handle.GetSupportedMediaTypes(),
Handle: handle,
client: client,
Expand Down
7 changes: 7 additions & 0 deletions plugin/goplugin_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,13 @@ func DiscoverGoPluginUsing[I IPluggable](o *GoPluginLoader) error {
}
o.loadedByName[pluginName] = pluginContext

o.logger.Infow("loaded plugin",
"name", pluginName,
"scheme", pluginContext.GetAttestationScheme(),
"version", pluginContext.GetVersion(),
"path", path,
)

for _, mediaType := range pluginContext.SupportedMediaTypes {
if existing, ok := o.loadedByMediaType[mediaType]; ok {
return fmt.Errorf(
Expand Down
21 changes: 21 additions & 0 deletions plugin/goplugin_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,24 @@ func (o *GoPluginManager[I]) LookupByAttestationScheme(name string) (I, error) {
func (o *GoPluginManager[I]) LookupByMediaType(mediaType string) (I, error) {
return GetGoPluginHandleByMediaTypeUsing[I](o.loader, mediaType)
}

func (o *GoPluginManager[I]) GetPluginVersion(name string) (string, error) {
pluginContext, ok := o.loader.loadedByName[name]
if !ok {
return "", ErrNotFound
}

return pluginContext.GetVersion(), nil
}

func (o *GoPluginManager[I]) GetSchemeVersion(scheme string) (string, error) {
for _, ictx := range o.loader.loadedByName {
if ictx.GetAttestationScheme() == scheme {
if _, ok := ictx.(*PluginContext[I]); ok {
return ictx.GetVersion(), nil
}
}
}

return "", ErrNotFound
}
9 changes: 9 additions & 0 deletions plugin/imanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,13 @@ type IManager[I IPluggable] interface {
// the specified name. If there is no such plugin, an error is
// returned.
LookupByAttestationScheme(name string) (I, error)

// GetPluginVersion returns the version string for a plugin identified
// by its name. If the plugin is not found, an error is returned.
GetPluginVersion(name string) (string, error)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is misleading. Per current inplementation, plugins do not independent version and this returns the version for the scheme, not the plugin. I would rename this to GetSchemeVersionForPlugin


// GetSchemeVersion returns the version string for the plugin that
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This returns the version for the scheme, not for a plugin (a scheme has three plugins associated with it, but, currently, they do not have independent versions).

// implements the specified attestation scheme. If no plugin implements
// the scheme, an error is returned.
GetSchemeVersion(scheme string) (string, error)
}
6 changes: 6 additions & 0 deletions plugin/ipluggable.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,10 @@ type IPluggable interface {
// GetSupportedMediaTypes returns a []string containing the media types
// this plugin is capable of handling.
GetSupportedMediaTypes() []string

// GetVersion returns a string containing the version of this plugin
// implementation. The version should follow semantic versioning (e.g., "1.0.0").
// This allows clients to understand the level of support and capabilities
// associated with a particular plugin implementation.
GetVersion() string
}
21 changes: 21 additions & 0 deletions plugin/test/ammo.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type IAmmo interface {
GetName() string
GetAttestationScheme() string
GetSupportedMediaTypes() []string
GetVersion() string
GetCapacity() int
}

Expand Down Expand Up @@ -65,6 +66,21 @@ func (o *AmmoRPCClient) GetSupportedMediaTypes() []string {
return resp
}

func (o *AmmoRPCClient) GetVersion() string {
var (
resp string
unused interface{}
)

err := o.client.Call("Plugin.GetVersion", &unused, &resp)
if err != nil {
log.Printf("Plugin.GetVersion RPC call failed: %v", err) // nolint
return ""
}

return resp
}

func (o *AmmoRPCClient) GetCapacity() int {
var (
resp int
Expand Down Expand Up @@ -99,6 +115,11 @@ func (o *AmmoRPCServer) GetSupportedMediaTypes(args interface{}, resp *[]string)
return nil
}

func (o *AmmoRPCServer) GetVersion(args interface{}, resp *string) error {
*resp = o.Impl.GetVersion()
return nil
}

func (o *AmmoRPCServer) GetCapacity(args interface{}, resp *int) error {
*resp = o.Impl.GetCapacity()
return nil
Expand Down
4 changes: 4 additions & 0 deletions plugin/test/gascartridge/gascartridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ func (o GasCartridge) GetSupportedMediaTypes() []string {
return []string{"tibanna gas"}
}

func (o GasCartridge) GetVersion() string {
return "1.0.0"
}

func (o GasCartridge) GetCapacity() int {
return 500
}
Expand Down
Loading