Skip to content
Merged
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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,16 @@ api:

plugins:
<plugin_identifier>: # Can have as many of these as you like
protocol_version: 2 # Optional: Defaults to 1 for backwards compatibility
source: <plugin_source>
labels:
type: plugin-check
host: 12345
policies:
- <policy>
- <policy>
policy_data: # Optional: Mapping for supported policies. Can be any data structure
Comment thread
reecebedding marked this conversation as resolved.
<data key>: <data value>
config:
<config1>: <value>
<config2>: <value>
Expand Down
44 changes: 30 additions & 14 deletions cmd/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import (
"golang.org/x/sync/singleflight"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/structpb"
)

type apiAuthConfig struct {
Expand All @@ -60,12 +61,13 @@ type agentPolicy string
type agentPluginConfig map[string]string

type agentPlugin struct {
ProtocolVersion int32 `mapstructure:"protocol_version"`
Schedule *string `mapstructure:"schedule,omitempty"`
Source string `mapstructure:"source"`
Policies []agentPolicy `mapstructure:"policies"`
Config agentPluginConfig `mapstructure:"config"`
Labels map[string]string `mapstructure:"labels"`
ProtocolVersion int32 `mapstructure:"protocol_version"`
Schedule *string `mapstructure:"schedule,omitempty"`
Source string `mapstructure:"source"`
Policies []agentPolicy `mapstructure:"policies"`
Config agentPluginConfig `mapstructure:"config"`
Labels map[string]string `mapstructure:"labels"`
PolicyData map[string]interface{} `mapstructure:"policy_data,omitempty"`
Comment thread
reecebedding marked this conversation as resolved.
Comment thread
reecebedding marked this conversation as resolved.
protocolSet bool
}

Expand Down Expand Up @@ -394,6 +396,19 @@ func initRunner(name string, protocolVersion int32, runnerInstance runner.Runner
return err
}

func configureRunner(name string, runnerInstance runner.RunnerV2, config agentPluginConfig, policyData map[string]interface{}) error {
policyDataStruct, err := mapToStruct(policyData)
if err != nil {
return fmt.Errorf("invalid policy_data for plugin %s: %w", name, err)
}

_, err = runnerInstance.Configure(&proto.ConfigureRequest{
Config: config,
PolicyData: policyDataStruct,
})
return err
}

func loadConfig(cmd *cobra.Command, v *viper.Viper) (*agentConfig, error) {
err := v.ReadInConfig()
if err != nil {
Expand Down Expand Up @@ -946,6 +961,13 @@ func copyStringMap(input map[string]string) map[string]string {
return output
}

func mapToStruct(m map[string]interface{}) (*structpb.Struct, error) {
if m == nil {
return nil, nil
}
return structpb.NewStruct(m)
}

func pluginEvidenceLabels(config *agentConfig, pluginName string, pluginConfig *agentPlugin) map[string]string {
return pluginEvidenceLabelsWithHash(config, pluginName, pluginConfig, agentConfigurationHash(config))
}
Expand Down Expand Up @@ -1351,10 +1373,7 @@ func (ar *AgentRunner) runAllPlugins(ctx context.Context) error {
if err := func() error {
defer cleanupRunner()

_, err = runnerInstance.Configure(&proto.ConfigureRequest{
Config: pluginConfig.Config,
})
if err != nil {
if err := configureRunner(pluginName, runnerInstance, pluginConfig.Config, pluginConfig.PolicyData); err != nil {
// What do we do here ?
//endTimer := time.Now()
//_, err = client.Results.Create(&sdk.Result{
Expand Down Expand Up @@ -1500,10 +1519,7 @@ func (ar *AgentRunner) runPlugin(ctx context.Context, name string, plugin *agent
}
defer cleanupRunner()

_, err = runnerInstance.Configure(&proto.ConfigureRequest{
Config: plugin.Config,
})
if err != nil {
if err := configureRunner(name, runnerInstance, plugin.Config, plugin.PolicyData); err != nil {
return err
}

Expand Down
56 changes: 54 additions & 2 deletions cmd/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ import (
)

type initTestRunner struct {
initErr error
configureCalls int
configureErr error
configureRequest *proto.ConfigureRequest
initErr error
}

type emptyError struct{}
Expand All @@ -37,7 +40,9 @@ func (e emptyError) Error() string {
}

func (r *initTestRunner) Configure(request *proto.ConfigureRequest) (*proto.ConfigureResponse, error) {
return &proto.ConfigureResponse{}, nil
r.configureCalls++
r.configureRequest = request
return &proto.ConfigureResponse{}, r.configureErr
}

func (r *initTestRunner) Eval(request *proto.EvalRequest, a runner.ApiHelper) (*proto.EvalResponse, error) {
Expand Down Expand Up @@ -982,6 +987,53 @@ func TestInitRunner(t *testing.T) {
})
}

func TestConfigureRunner(t *testing.T) {
t.Run("passes config and policy data to runner", func(t *testing.T) {
testRunner := &initTestRunner{}

err := configureRunner(
"test-plugin",
testRunner,
agentPluginConfig{"endpoint": "localhost"},
map[string]interface{}{"allowed_versions": map[string]interface{}{"wget": "1.20.3"}},
)
if err != nil {
t.Fatalf("configureRunner() error = %v, expected nil", err)
}

if testRunner.configureCalls != 1 {
t.Fatalf("Configure called %d times, expected 1", testRunner.configureCalls)
}
if got := testRunner.configureRequest.Config["endpoint"]; got != "localhost" {
t.Fatalf("Configure config endpoint = %q, expected %q", got, "localhost")
}
allowedVersions := testRunner.configureRequest.PolicyData.Fields["allowed_versions"].GetStructValue()
if got := allowedVersions.Fields["wget"].GetStringValue(); got != "1.20.3" {
t.Fatalf("Configure policy_data allowed_versions.wget = %q, expected %q", got, "1.20.3")
}
})

t.Run("rejects unsupported policy data before configuring runner", func(t *testing.T) {
testRunner := &initTestRunner{}

err := configureRunner(
"test-plugin",
testRunner,
nil,
map[string]interface{}{"unsupported": make(chan int)},
)
if err == nil {
t.Fatal("configureRunner() error = nil, expected invalid policy_data error")
}
if !strings.Contains(err.Error(), "invalid policy_data for plugin test-plugin") {
t.Fatalf("configureRunner() error = %q, expected plugin policy_data context", err.Error())
}
if testRunner.configureCalls != 0 {
t.Fatalf("Configure called %d times, expected 0", testRunner.configureCalls)
}
})
}

func TestAgentRunnerBuildsAuthenticatedSDKClient(t *testing.T) {
var (
tokenRequests int
Expand Down
9 changes: 9 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ plugins:
<config1>: <value1>
<config2>: <value2>
...
policy_data: # Optional dynamic data for policies
<key>: <value>
...

agent_evidence:
enabled: true
Expand All @@ -53,6 +56,12 @@ The `policies` field is a list of paths to the policy files that the plugin will
The `config` field is a map of configuration values that the plugin will use to connect to the data source. The values
will be passed to the plugin when it is run.

The `policy_data` field is an optional map of dynamic data that will be passed to the plugin's policy manager. This data
can be of any shape and is made available to OPA/Rego policies during evaluation. This allows you to provide runtime
Comment thread
reecebedding marked this conversation as resolved.
configuration to policies without modifying the policy files themselves.

Usage: `satisfied if input.value == data.allowed_value`

You can specify as many plugins as you wish, as long as each identifier is unique. You can even reuse the same plugin
multiple times with different configurations.

Expand Down
Loading
Loading