## 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|