---
# System prepended metadata

title: Ratify plugin investigation

---



 

## Tasks

	- Define new plugin model with dynamic plugin support.
      Currently evluating  https://pkg.go.dev/plugin 
	- Investigate tls auth for grpc calls
	- Migrate existing verifiers
    - Migrate existing stores

## Investigation
### Option1: pkg.go.dev/plugin
Package plugin implements loading and symbol resolution of Go plugins. This is an option however with following major drawback:
- does not support windows
- plugins must be compiled with the same version of golang as host  

### Option2: grpc golang
It is also an option to work with plain grpc, Ratify and Plugin author will be to to manage client and server communication. See basic sample [here](https://grpc.io/docs/languages/go/basics/)

### Option3:  [Hashicorp go plugin](https://github.com/hashicorp/go-plugin)
Plugin author: you just implement an interface as if it were going to run in the same process. Ratify: you just use and call functions on an interface as if it were in the same process. The plugin system handles the communication in between.
 
Step1: Ratify will define set of grpc service 
 
```go
syntax="proto3";

package certstore;
 
message GetResponse {
    bytes value = 1;
}

message GetRequest {
    message AttributeMapEntry {
        string key = 1;
        string value = 2;
      }
    repeated AttributeMapEntry attrib = 1;
}

service CertStorePlugin {
    rpc Get(GetRequest) returns (GetResponse);
}

```

step2: provide some boiler plate implementation of the interface that communicates over a net/rpc connection:
```go=
package certstore

import (
	"context"

	"google.golang.org/grpc"

	"github.com/hashicorp/go-plugin"
)

// Handshake is a common handshake that is shared by plugin and host.
var Handshake = plugin.HandshakeConfig{
	// This isn't required when using VersionedPlugins
	ProtocolVersion:  1,
	MagicCookieKey:   "BASIC_PLUGIN",
	MagicCookieValue: "hello",
}

// PluginMap is the map of plugins we can dispense.
var PluginMap = map[string]plugin.Plugin{
	"certstore_grpc": &CertStoreGRPCPlugin{},
}

// KV is the interface that we're exposing as a plugin.
type CertStore interface {
	//Get(attrib []*GetRequest_AttributeMapEntry) ([]byte, error)
	Get(attrib map[string]string) ([]byte, error)
}

// This is the implementation of plugin.Plugin so we can serve/consume this.
type CertStorePlugin struct {
	// Concrete implementation, written in Go. This is only used for plugins
	// that are written in Go.
	Impl CertStore
}

// This is the implementation of plugin.GRPCPlugin so we can serve/consume this.
type CertStoreGRPCPlugin struct {
	// GRPCPlugin must still implement the Plugin interface
	plugin.Plugin
	// Concrete implementation, written in Go. This is only used for plugins
	// that are written in Go.
	Impl CertStore
}

func (p *CertStoreGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error {
	RegisterCertStorePluginServer(s, &GRPCServer{Impl: p.Impl})
	return nil
}

func (p *CertStoreGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) {
	return &GRPCClient{client: NewCertStorePluginClient(c)}, nil
}

```
Step3: Plugin authors serve a plugin from the main function. This will be built and copied into the plugin default path 
```go
type AKV struct{}

func (AKV) Get(attrib map[string]string) ([]byte, error) {
	logrus.Info("Message from implementation of the plugin, size of attrib", len(attrib))

	providers := certificateprovider.GetCertificateProviders()
	provider := providers["inline"]
	 
	results, _, err := provider.GetCertificates(context.Background(), attrib)
		 
	str = "we should return cert or byte array," + strconv.Itoa(len(results))
 
	return []byte(str), nil
}

func main() {
	plugin.Serve(&plugin.ServeConfig{
		HandshakeConfig: cs.Handshake,
		Plugins: map[string]plugin.Plugin{
			"kv": &cs.CertStoreGRPCPlugin{Impl: &AKV{}},
		},

		// A non-nil value here enables gRPC serving for this plugin...
		GRPCServer: plugin.DefaultGRPCServer,
	})
}
```
 
Step4: Ratify calls plugin.Client to launch a subprocess 

```go=
// We're a host. Start by launching the plugin process.
	client := plugin.NewClient(&plugin.ClientConfig{
		HandshakeConfig: cs.Handshake,
		Plugins:         cs.PluginMap,
		Cmd:             exec.Command("sh", "-c", "/pluginpath/inline-go-grpc"),
		AllowedProtocols: []plugin.Protocol{
			plugin.ProtocolNetRPC, plugin.ProtocolGRPC},
	})
	defer client.Kill()

	// Connect via RPC
	rpcClient, err := client.Client()
	if err != nil {
		fmt.Println("Error:", err.Error())
	}

	// Request the plugin, todo ratify crd controller need to register plugin
	raw, err := rpcClient.Dispense("certstore_grpc")  
	if err != nil {
		fmt.Println("Error:", err.Error())	 
	}

	// We should have a KV store now! This feels like a normal interface
	// implementation but is in fact over an RPC connection.
	certstore := raw.(cs.CertStore) 

	result, err := certstore.Get(attrib)
	if err != nil {
		fmt.Println("Error:", err.Error())
	}

	fmt.Println(string(result))
```
 
Pending investigation:
- Plugin auth ( TLS auth should be supported over grpc)
- MPL 2.0 license?

## Proposed timeline

From current investigation this migration work require refactoring of all business logic in ratify including Executor, Verifier, Store , crd manager. 

Since we are already in RC , we will have to maintain existing plugin model till the end of 1.x.  I believe its safer to introduce these large refactoring in the next alpha version.


| |RC|1.0|1.x ?|2.0 alpha/beta|2.0 RC|
|:----|:----|:----|:----|:----|:----|
|Verifier/store grpc plugin|existing plugin model  |existing plugin model  |No changes here as 1.x should be a Stable version|Grpc plugin behind feature flag. Support both plugin|Grpc only|
|Cert Store as a plugin| | | |CertStore plugin behind feature flag|Cerstore|


