Plugin Reference

Package import

import "/go/src/github.com/cyberark/secretless-broker/pkg/secretless/plugin/v1"
Overview
Index

Overview ▾

Package v1 plugins are ways to add extended functionality to the Secretless Broker.

Note: Given the speed of development, it is possible this documentation may become outdated. Please use this document as a reference, but rely on the GitHub source code as the source of truth.

Supported Plugin Types

The following types of plugins are currently supported:

- Listeners
- Handlers
- Providers
- Configuration managers
- Connection managers

There is additionally an EventNotifier class that is currently used to propagate events from listeners and handlers to the plugin manager. Since it is expected that this class may be removed as we move more of the abstract functionality to the plugin manager itself, the EventNotifier class is only minimally covered in this documentation.

Loading Plugins on Secretless Broker Start

When you start Secretless Broker, it will by default look in /usr/local/lib/secretless for any plugin shared library files to load. You can specify an alternate directory at startup by using the plugin directory flag:

./secretless-broker -p /etc/lib/secretless

When Secretless Broker starts, all plugins are currently loaded in the following manner:

1. The plugin directory (by default set to /usr/local/lib/secretless) is checked for any shared library (*.so) files. Sub-directory traversal is not supported at this time.

2. Each plugin shared library is loaded and validated to ensure it contains all required variables and methods. In the "Plugin Minimum Requirements" section below we discuss these requirements in greater detail. To briefly summarize, Secretless Broker expects any plugin to include definitions for the following variables and methods:

- var PluginAPIVersion
- var PluginInfo
- func GetHandlers
- func GetListeners
- func GetProviders
- func GetConfigurationManagers
- func GetConnectionManagers

3. For each plugin, every component factory is enumerated:

- Handler plugins are added to handler factory map
- Listener plugins are added to listener factory map
- Provider plugins are added to provider factory map
- Connection manager plugins are added to connection manager factory map
- Configuration manager plugins are added to configuration manager factory map

4. Connection manager plugins are instantiated

5. The chosen configuration manager plugin is instantiated

6. The program waits for a valid configuration to be provided

7. After the configuration is provided and loaded, providers and listeners/handlers are instantiated as needed

Plugin Minimum Requirements

In this section, we will go over the minimum requirements for the variables and methods that every custom plugin must include in order to be properly loaded into the Secretless Broker.

// PluginAPIVersion is the target API version of the Secretless Broker and must
// match the supported version defined in
// internal/pkg/plugin/manager.go:_IsSupportedPluginAPIVersion
string PluginAPIVersion

// PluginInfo is a map that has information about the plugin that the daemon
// might use for logging, prioritization, and masking.
// While extraneous keys in the map are ignored, the map must contain the
// following keys:
// - "version": indicates the version of the plugin
// - "id": a computer-friendly plugin ID. Naming should be constrained to
//         short, spaceless ASCII lowercase alphanumeric set with a limited
//         set of special characters (`-`, `_`, and `/`).
// - "name": a user-friendly plugin name. This name will be used in most
//           user-facing messages about the plugin and should be constrained
//           in length to <30 chars.
// - "description": a plugin description; should be less than 30 characters.
map[string]string PluginInfo

// GetListeners returns a map of listener IDs to their factory methods
// The factory methods accept v1.ListenerOptions when invoked and return a
// new v1.Listener
func GetListeners() map[string]func(plugin_v1.ListenerOptions) plugin_v1.Listener

// GetHandlers returns a map of handler IDs to their factory methods
// The factory methods accept v1.HandlerOptions when invoked and return a
// new v1.Handler
func GetHandlers() map[string]func(plugin_v1.HandlerOptions) plugin_v1.Handler

// GetProviders returns a map of provider IDs to their factory methods
// The factory methods accept v1.ProviderOptions when invoked and return a
// new v1.Provider (and/or an error)
func GetProviders() map[string]func(plugin_v1.ProviderOptions) (plugin_v1.Provider, error)

// GetConnectionManagers returns a map of connection manager IDs to their
// factory methods
// The factory methods return a new v1.ConnectionManager when invoked
// Note: it is expected that the factory methods will also eventually have
//       v1.ConnectionManagerOptions as an argument when invoked, for
//       eventual consistency with the other factory maps
func GetConnectionManagers() map[string]func() plugin_v1.ConnectionManager

// GetConfigurationManagers returns a map of configuration manager IDs to their
// factory methods
// The factory methods accept v1.ConfigurationManagerOptions when invoked and
// return a new v1.ConfigurationManager
func GetConfigurationManagers() map[string]func(plugin_v1.ConfigurationManagerOptions) plugin_v1.ConfigurationManager

Example plugin

The following shows a sample plugin that conforms to the expected API. The full sample plugin is available in GitHub at https://github.com/cyberark/secretless-broker/tree/master/test/plugin/example.

package main

import (
  plugin_v1 "github.com/cyberark/secretless-broker/pkg/secretless/plugin/v1"
  "github.com/cyberark/secretless-broker/test/plugin/example"
)

// PluginAPIVersion is the API version being used
var PluginAPIVersion = "0.0.8"

// PluginInfo describes the plugin
var PluginInfo = map[string]string{
  "version":     "0.0.8",
  "id":          "example-plugin",
  "name":        "Example Plugin",
  "description": "Example plugin to demonstrate plugin functionality",
}

// GetListeners returns the echo listener
func GetListeners() map[string]func(plugin_v1.ListenerOptions) plugin_v1.Listener {
  return map[string]func(plugin_v1.ListenerOptions) plugin_v1.Listener{
    "echo": example.ListenerFactory,
  }
}

// GetHandlers returns the example handler
func GetHandlers() map[string]func(plugin_v1.HandlerOptions) plugin_v1.Handler {
  return map[string]func(plugin_v1.HandlerOptions) plugin_v1.Handler{
    "example-handler": example.HandlerFactory,
  }
}

// GetProviders returns the example provider
func GetProviders() map[string]func(plugin_v1.ProviderOptions) (plugin_v1.Provider, error) {
  return map[string]func(plugin_v1.ProviderOptions) (plugin_v1.Provider, error){
    "example-provider": example.ProviderFactory,
  }
}

// GetConnectionManagers returns the example connection manager
func GetConnectionManagers() map[string]func() plugin_v1.ConnectionManager {
  return map[string]func() plugin_v1.ConnectionManager{
    "example-plugin-connection-manager": example.ConnManagerFactory,
  }
}

// GetConfigurationManagers returns the example configuration manager
func GetConfigurationManagers() map[string]func(plugin_v1.ConfigurationManagerOptions) plugin_v1.ConfigurationManager {
  return map[string]func(plugin_v1.ConfigurationManagerOptions) plugin_v1.ConfigurationManager{
    "example-plugin-config-manager": example.ConfigManagerFactory,
  }
}

Index ▾

func PipeHandlerWithStream(handler Handler, stream func(net.Conn, net.Conn, func(b []byte)), eventNotifier EventNotifier, done func())
type BaseHandler
    func NewBaseHandler(options HandlerOptions) BaseHandler
    func (h *BaseHandler) Authenticate(map[string][]byte, *http.Request) error
    func (h *BaseHandler) GetBackendConnection() net.Conn
    func (h *BaseHandler) GetClientConnection() net.Conn
    func (h *BaseHandler) GetConfig() config.Handler
    func (h *BaseHandler) LoadKeys(keyring agent.Agent) error
    func (h *BaseHandler) Shutdown()
type BaseListener
    func NewBaseListener(options ListenerOptions) BaseListener
    func (l *BaseListener) AddHandler(handler Handler)
    func (l *BaseListener) GetConfig() config.Listener
    func (l *BaseListener) GetConnections() []net.Conn
    func (l *BaseListener) GetHandlers() []Handler
    func (l *BaseListener) GetListener() net.Listener
    func (l *BaseListener) GetName() string
    func (l *BaseListener) GetNotifier() EventNotifier
    func (l *BaseListener) Listen()
    func (l *BaseListener) RemoveHandler(targetHandler Handler)
    func (l *BaseListener) Shutdown() error
    func (l *BaseListener) Validate() error
type ConfigurationChangedHandler
type ConfigurationManager
type ConfigurationManagerOptions
type ConnectionManager
type EventNotifier
type Handler
type HandlerOptions
type HandlerShutdownNotifier
type Listener
type ListenerOptions
type Provider
type ProviderOptions
type Resolver

Package files

configuration_manager.go connection_manager.go doc.go event_notifier.go handler.go listener.go provider.go resolver.go

func PipeHandlerWithStream

func PipeHandlerWithStream(handler Handler, stream func(net.Conn, net.Conn, func(b []byte)), eventNotifier EventNotifier, done func())

PipeHandlerWithStream performs continuous bidirectional transfer of data between handler client and backend takes arguments -> [handler]: Handler-compliant struct. Handler#GetClientConnection and Handler#GetBackendConnection provide client and backend connections (net.Conn) [stream]: function performing continuous bidirectional transfer [eventNotifier]: EventNotifier-compliant struct. EventNotifier#ClientData is passed transfer bytes [done]: function called once when transfer ceases

type BaseHandler

BaseHandler provides default (shared/common) implementations of Handler interface methods, where it makes sense - the rest of the methods panic if not implemented in the "DerivedHandler" e.g. BaseHandler#Authenticate.

The intention is to keep things DRY by embedding BaseHandler in "DerivedHandler"

There is no requirement to use BaseHandler.

type BaseHandler struct {
    BackendConnection net.Conn
    ClientConnection  net.Conn
    EventNotifier     EventNotifier
    HandlerConfig     config.Handler
    Resolver          Resolver
    ShutdownNotifier  HandlerShutdownNotifier
}

func NewBaseHandler

func NewBaseHandler(options HandlerOptions) BaseHandler

NewBaseHandler creates a BaseHandler from HandlerOptions

func (*BaseHandler) Authenticate

func (h *BaseHandler) Authenticate(map[string][]byte, *http.Request) error

Authenticate implements plugin_v1.Handler

func (*BaseHandler) GetBackendConnection

func (h *BaseHandler) GetBackendConnection() net.Conn

GetBackendConnection implements plugin_v1.Handler

func (*BaseHandler) GetClientConnection

func (h *BaseHandler) GetClientConnection() net.Conn

GetClientConnection implements plugin_v1.Handler

func (*BaseHandler) GetConfig

func (h *BaseHandler) GetConfig() config.Handler

GetConfig implements plugin_v1.Handler

func (*BaseHandler) LoadKeys

func (h *BaseHandler) LoadKeys(keyring agent.Agent) error

LoadKeys implements plugin_v1.Handler

func (*BaseHandler) Shutdown

func (h *BaseHandler) Shutdown()

Shutdown implements plugin_v1.Handler

type BaseListener

BaseListener provides default (shared/common) implementations of Listener interface methods, where it makes sense - the rest of the methods panic if not implemented in the "DerivedListener" e.g. BaseListener#GetName.

The intention is to keep things DRY by embedding BaseListener in "DerivedListener".

There is no requirement to use BaseListener.

type BaseListener struct {
    Config         config.Listener
    EventNotifier  EventNotifier
    HandlerConfigs []config.Handler
    IsClosed       bool
    NetListener    net.Listener
    Resolver       Resolver
    RunHandlerFunc func(id string, options HandlerOptions) Handler
    // contains filtered or unexported fields
}

func NewBaseListener

func NewBaseListener(options ListenerOptions) BaseListener

NewBaseListener creates a BaseListener from ListenerOptions

func (*BaseListener) AddHandler

func (l *BaseListener) AddHandler(handler Handler)

AddHandler appends a given Handler to the slice of Handlers held by BaseListener

func (*BaseListener) GetConfig

func (l *BaseListener) GetConfig() config.Listener

GetConfig implements plugin_v1.Listener

func (*BaseListener) GetConnections

func (l *BaseListener) GetConnections() []net.Conn

GetConnections implements plugin_v1.Listener

func (*BaseListener) GetHandlers

func (l *BaseListener) GetHandlers() []Handler

GetHandlers implements plugin_v1.Listener

func (*BaseListener) GetListener

func (l *BaseListener) GetListener() net.Listener

GetListener implements plugin_v1.Listener

func (*BaseListener) GetName

func (l *BaseListener) GetName() string

GetName returns the internal name given to this listener

func (*BaseListener) GetNotifier

func (l *BaseListener) GetNotifier() EventNotifier

GetNotifier implements plugin_v1.Listener

func (*BaseListener) Listen

func (l *BaseListener) Listen()

Listen implements plugin_v1.Listener

func (*BaseListener) RemoveHandler

func (l *BaseListener) RemoveHandler(targetHandler Handler)

RemoveHandler removes a given Handler from the slice of Handlers held by BaseListener

func (*BaseListener) Shutdown

func (l *BaseListener) Shutdown() error

Shutdown implements plugin_v1.Listener

func (*BaseListener) Validate

func (l *BaseListener) Validate() error

Validate implements plugin_v1.Listener

type ConfigurationChangedHandler

ConfigurationChangedHandler interface specifies what method is required to support being a target of a ConfigurationManger object.

type ConfigurationChangedHandler interface {
    // ConfigurationChanged is a method that gets triggered when a ConfigurationManager
    // has a new configuration that should be loaded.
    ConfigurationChanged(string, config.Config) error
}

type ConfigurationManager

ConfigurationManager is the interface used to obtain configuration data and to trigger updates

type ConfigurationManager interface {
    // Initialize is called to instantiate the ConfigurationManager and provide
    // a handler that will be notified of configuration object updates.
    Initialize(handler ConfigurationChangedHandler, configSpec string) error

    // GetName returns the internal name that the ConfigurationManager was
    // instantiated with.
    GetName() string
}

type ConfigurationManagerOptions

ConfigurationManagerOptions contains the configuration for the configuration manager instantiation.

type ConfigurationManagerOptions struct {
    // Name is the internal name that the configuraton manager will have. This
    // may be different from the name passed back from the factory.
    Name string
}

type ConnectionManager

ConnectionManager is an interface to be implemented by plugins that want to manage connections for handlers and listeners.

type ConnectionManager interface {
    // Initialize is called before proxy initialization
    Initialize(config.Config, func(config.Config) error) error

    // CreateListener is called for every listener created by Proxy
    CreateListener(Listener)

    // NewConnection is called for each new client connection before being
    // passed to a handler
    NewConnection(Listener, net.Conn)

    // CloseConnect is called when a client connection is closed
    CloseConnection(net.Conn)

    // CreateHandler is called after listener creates a new handler
    CreateHandler(Handler, net.Conn)

    // DestroyHandler is called before a handler is removed
    DestroyHandler(Handler)

    // ResolveVariable is called when a provider resolves a variable
    ResolveVariable(provider Provider, id string, value []byte)

    // ClientData is called for each inbound packet from clients
    ClientData(net.Conn, []byte)

    // ServerData is called for each inbound packet from the backend
    ServerData(net.Conn, []byte)

    // Shutdown is called when secretless caught a signal to exit
    Shutdown()
}

type EventNotifier

EventNotifier is the interface which is used to pass event up from handlers/ listeners/managers back up to the main plugin manager

type EventNotifier interface {
    // NewConnection is called for each new client connection before being
    // passed to a handler
    NewConnection(Listener, net.Conn)

    // ClientData is called for each inbound packet from clients
    ClientData(net.Conn, []byte)

    // CreateHandler is called after listener creates a new handler
    CreateHandler(Handler, net.Conn)

    // CreateListener is called for every listener created by Proxy
    CreateListener(Listener)

    // ResolveVariable is called when a provider resolves a variable
    ResolveVariable(provider Provider, id string, value []byte)

    // ServerData is called for each inbound packet from the backend
    ServerData(net.Conn, []byte)
}

type Handler

Handler is an interface which takes a connection and connects it to a backend TODO: Remove Authenticate as it's only used by http listener TODO: Remove LoadKeys as it's only used by sshagent listener

type Handler interface {
    Authenticate(map[string][]byte, *http.Request) error
    GetConfig() config.Handler
    GetClientConnection() net.Conn
    GetBackendConnection() net.Conn
    LoadKeys(keyring agent.Agent) error
    Shutdown()
}

type HandlerOptions

HandlerOptions contains the configuration for the handler

type HandlerOptions struct {
    HandlerConfig    config.Handler
    Channels         <-chan ssh.NewChannel
    ClientConnection net.Conn
    EventNotifier    EventNotifier
    ShutdownNotifier HandlerShutdownNotifier
    Resolver         Resolver
}

type HandlerShutdownNotifier

HandlerShutdownNotifier is a function signature for notifying of a Handler's Shutdown

type HandlerShutdownNotifier func(Handler)

type Listener

Listener is the interface which accepts client connections and passes them to a handler

type Listener interface {
    GetConfig() config.Listener
    GetConnections() []net.Conn
    GetHandlers() []Handler
    GetListener() net.Listener
    GetName() string
    GetNotifier() EventNotifier
    Listen()
    Validate() error
    Shutdown() error
}

type ListenerOptions

ListenerOptions contains thetype Proxy struct { configuration for the listener

type ListenerOptions struct {
    EventNotifier  EventNotifier
    HandlerConfigs []config.Handler
    ListenerConfig config.Listener
    NetListener    net.Listener
    Resolver       Resolver
    RunHandlerFunc func(string, HandlerOptions) Handler
}

type Provider

Provider is the interface used to obtain values from a secret vault backend.

type Provider interface {
    // GetName returns the name that the Provider was instantiated with
    GetName() string

    // GetValue takes in an id of a variable and returns its resolved value
    GetValue(id string) ([]byte, error)
}

type ProviderOptions

ProviderOptions contains the configuration for the provider instantiation

type ProviderOptions struct {
    // Name is the internal name that the provider will have. This may be different from
    // the name passed back from the provider factory.
    Name string
}

type Resolver

Resolver is the interface which is used to pass a generic resolver down to the Listeners/Handlers.

type Resolver interface {
    // GetProvider gets back an instance of a named provider and creates it if
    // one already doesn't exist
    GetProvider(name string) (Provider, error)

    // Resolve accepts an array of variables and returns a map of resolved ones
    Resolve(variables []config.Variable) (result map[string][]byte, err error)
}

Ready to use Secretless Broker in your Kubernetes environment? Check out our Kubernetes tutorial or our deployment guides!