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