Skip to content

Commit

Permalink
Merge pull request #7 from compliance-framework/feat/BCH-770-nats-ass…
Browse files Browse the repository at this point in the history
…essment-plans

Send results to NATS to record against assessment plan
  • Loading branch information
chris-cmsoft authored Dec 6, 2024
2 parents e51b07f + ab5cdda commit 29ad1fd
Show file tree
Hide file tree
Showing 10 changed files with 240 additions and 136 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ nats:
plugins:
<plugin_identifier>: # Can have as many of these as you like
source: <plugin_source>
assessment_plan_ids:
assessment-plan-ids:
- <assessment_plan_id>
- <assessment_plan_id>
policies:
Expand Down
108 changes: 69 additions & 39 deletions cmd/agent.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package cmd

import (
"encoding/json"
"fmt"
"github.com/chris-cmsoft/concom/internal"
"github.com/chris-cmsoft/concom/internal/event"
"github.com/chris-cmsoft/concom/runner"
"github.com/chris-cmsoft/concom/runner/proto"
"github.com/compliance-framework/gooci/pkg/oci"
Expand All @@ -14,10 +14,10 @@ import (
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
"github.com/nats-io/nats.go"
"github.com/open-policy-agent/opa/rego"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.mongodb.org/mongo-driver/bson/primitive"
"log"
"os"
"os/exec"
Expand All @@ -38,10 +38,10 @@ type agentPolicy string
type agentPluginConfig map[string]string

type agentPlugin struct {
AssessmentPlanIds []*string `json:"assessment_plan_ids"`
Source *string `json:"source"`
Policies []*agentPolicy `json:"policy"`
Config agentPluginConfig `json:"config"`
AssessmentPlanIds []string `mapstructure:"assessment-plan-ids"`
Source string `mapstructure:"source"`
Policies []agentPolicy `mapstructure:"policies"`
Config agentPluginConfig `mapstructure:"config"`
}

type agentConfig struct {
Expand Down Expand Up @@ -159,7 +159,7 @@ func agentRunner(cmd *cobra.Command, args []string) error {
v.SetConfigFile(configPath)
v.AutomaticEnv()

loadConfig := func(configPath string) (*agentConfig, error) {
loadConfig := func() (*agentConfig, error) {
err := v.ReadInConfig()
if err != nil {
return nil, err
Expand All @@ -177,7 +177,7 @@ func agentRunner(cmd *cobra.Command, args []string) error {
return config, nil
}

config, err := loadConfig(configPath)
config, err := loadConfig()
if err != nil {
return err
}
Expand All @@ -191,6 +191,9 @@ func agentRunner(cmd *cobra.Command, args []string) error {
agentRunner := AgentRunner{
logger: logger,
config: *config,

natsBus: event.NewNatsBus(logger),
pluginLocations: map[string]string{},
}

v.OnConfigChange(func(in fsnotify.Event) {
Expand All @@ -205,7 +208,7 @@ func agentRunner(cmd *cobra.Command, args []string) error {
// This will exit the whole process of the agent. This might not be ideal.
// Maybe a better strategy here is to re-use the old config and log an error, so the process can continue
// until the config is fixed ?
config, err := loadConfig(configPath)
config, err := loadConfig()
if err != nil {
logger.Error("Error downloading plugins", "error", err)
panic(err)
Expand Down Expand Up @@ -240,7 +243,8 @@ type AgentRunner struct {

mu sync.Mutex

config agentConfig
config agentConfig
natsBus *event.NatsBus

pluginLocations map[string]string

Expand All @@ -250,29 +254,29 @@ type AgentRunner struct {
func (ar *AgentRunner) Run() error {
ar.logger.Info("Starting agent", "daemon", ar.config.Daemon, "nats_uri", ar.config.Nats.Url)

nc, err := nats.Connect(ar.config.Nats.Url)
err := ar.natsBus.Connect(ar.config.Nats.Url)
if err != nil {
log.Fatal(err)
}

defer nc.Close()
defer ar.natsBus.Close()

err = ar.DownloadPlugins()
if err != nil {
return err
}

if ar.config.Daemon == true {
ar.runDaemon(nc)
ar.runDaemon()
return nil
}

return ar.runInstance(nc)
return ar.runInstance()
}

// Should never return, either handles any error or panics.
// TODO: We should take a cancellable context here, so the caller can cancel the daemon at any time, and continue to whatever is appropriate
func (ar *AgentRunner) runDaemon(nc *nats.Conn) {
func (ar *AgentRunner) runDaemon() {
sigs := make(chan os.Signal, 1)

signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
Expand All @@ -288,7 +292,7 @@ func (ar *AgentRunner) runDaemon(nc *nats.Conn) {
go daemon.SdNotify(false, "READY=1")

for {
err := ar.runInstance(nc)
err := ar.runInstance()

if err != nil {
ar.logger.Error("error running instance", "error", err)
Expand All @@ -305,7 +309,7 @@ func (ar *AgentRunner) runDaemon(nc *nats.Conn) {
//
// Returns:
// - error: any error that occurred during the run
func (ar *AgentRunner) runInstance(nc *nats.Conn) error {
func (ar *AgentRunner) runInstance() error {
ar.mu.Lock()
defer ar.mu.Unlock()

Expand All @@ -318,7 +322,18 @@ func (ar *AgentRunner) runInstance(nc *nats.Conn) error {
Level: hclog.Level(ar.config.logVerbosity()),
})

source := ar.pluginLocations[*pluginConfig.Source]
source := ar.pluginLocations[pluginConfig.Source]

assessmentPlanIds := []string{}
for _, assessmentPlanId := range pluginConfig.AssessmentPlanIds {
planIdObject, err := primitive.ObjectIDFromHex(assessmentPlanId)
if err != nil {
return err
}
assessmentPlanIds = append(assessmentPlanIds, planIdObject.Hex())
}

logger.Debug("Using assessment plan ids", "ids", assessmentPlanIds)

logger.Debug("Running plugin", "source", source)

Expand All @@ -336,19 +351,38 @@ func (ar *AgentRunner) runInstance(nc *nats.Conn) error {
Config: pluginConfig.Config,
})
if err != nil {
for _, assessmentPlanId := range assessmentPlanIds {
result := runner.ErrorResult(assessmentPlanId, err)
if pubErr := event.Publish(ar.natsBus, result, "job.result"); pubErr != nil {
logger.Error("Error publishing configure result", "error", pubErr)
}
}
return err
}

_, err = runnerInstance.PrepareForEval(&proto.PrepareForEvalRequest{})
if err != nil {
for _, assessmentPlanId := range assessmentPlanIds {
result := runner.ErrorResult(assessmentPlanId, err)
if pubErr := event.Publish(ar.natsBus, result, "job.result"); pubErr != nil {
logger.Error("Error publishing evaslutae result", "error", pubErr)
}
}
return err
}

for _, inputBundle := range pluginConfig.Policies {
res, err := runnerInstance.Eval(&proto.EvalRequest{
BundlePath: string(*inputBundle),
BundlePath: string(inputBundle),
})

if err != nil {
for _, assessmentPlanId := range assessmentPlanIds {
result := runner.ErrorResult(assessmentPlanId, err)
if pubErr := event.Publish(ar.natsBus, result, "job.result"); pubErr != nil {
logger.Error("Error publishing evaslutae result", "error", pubErr)
}
}
return err
}

Expand All @@ -357,22 +391,21 @@ func (ar *AgentRunner) runInstance(nc *nats.Conn) error {
fmt.Println("Observations:", res.Observations)
fmt.Println("Log Entries:", res.Logs)

// Publish findings to nats subjects
findings, err := json.Marshal(res.Findings)
if err != nil {
return err
}
if err := nc.Publish("Findings", findings); err != nil {
return err
}

// Publish observations to nats subjects
observations, err := json.Marshal(res.Observations)
if err != nil {
return err
}
if err := nc.Publish("Observations", observations); err != nil {
return err
for _, assessmentPlanId := range assessmentPlanIds {
result := runner.Result{
Status: res.Status,
AssessmentId: assessmentPlanId,
Error: err,
Observations: res.Observations,
Findings: res.Findings,
Risks: res.Risks,
Logs: res.Logs,
}

// Publish findings to nats
if pubErr := event.Publish(ar.natsBus, result, "job.result"); pubErr != nil {
logger.Error("Error publishing result", "error", pubErr)
}
}
}
}
Expand Down Expand Up @@ -423,7 +456,7 @@ func (ar *AgentRunner) DownloadPlugins() error {
pluginSources := map[string]struct{}{}

for _, pluginConfig := range ar.config.Plugins {
pluginSources[*pluginConfig.Source] = struct{}{}
pluginSources[pluginConfig.Source] = struct{}{}
}

for source := range pluginSources {
Expand Down Expand Up @@ -472,9 +505,6 @@ func (ar *AgentRunner) DownloadPlugins() error {
pluginBinary := path.Join(destination, "plugin")
ar.logger.Debug("Plugin downloaded successfully", "Destination", pluginBinary)

if ar.pluginLocations == nil {
ar.pluginLocations = map[string]string{}
}
// Update the source in the agent configuration to the new path
ar.pluginLocations[source] = pluginBinary
} else {
Expand Down
8 changes: 4 additions & 4 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ nats:

plugins:
<plugin_identifier>: # Can have as many of these as you like
assessment_plan_ids:
assessment-plan-ids:
- <assessment_plan_id>
- <assessment_plan_id>
source: <plugin_source>
Expand All @@ -36,7 +36,7 @@ The `nats_domain` and `nats_port` items are the domain and port of the NATS serv
The `plugin_identifier` is a unique identifier for the plugin, and is used to identify the plugin in the logs, you can
name this whatever you like but it must be unique.

The `assessment_plan_ids` are the ids of the assessment plans that the plugin is associated with.
The `assessment-plan-ids` are the ids of the assessment plans that the plugin is associated with.

The `plugin_source` is the path to the plugin binary that the agent will run. This can be a relative or absolute path or
even a URL to a remote plugin.
Expand All @@ -57,7 +57,7 @@ nats:

plugins:
local-ssh-security:
assessment_plan_ids:
assessment-plan-ids:
- "12341234-1234-1234-123412341234"

source: "../plugin-local-ssh/cf-plugin-local-ssh"
Expand All @@ -70,7 +70,7 @@ plugins:
password: "password"

local-ssh-security2:
assessment_plan_id:
assessment-plan-ids:
- "45674567-4567-4567-456745674567"

source: "../plugin-local-ssh/cf-plugin-local-ssh"
Expand Down
12 changes: 10 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ require (
github.com/google/uuid v1.6.0
github.com/hashicorp/go-hclog v1.5.0
github.com/hashicorp/go-plugin v1.6.2
github.com/nats-io/nats-server/v2 v2.10.22
github.com/nats-io/nats.go v1.37.0
github.com/open-policy-agent/opa v0.69.0
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.9.0
go.mongodb.org/mongo-driver v1.17.1
google.golang.org/grpc v1.67.0
google.golang.org/protobuf v1.34.2
gopkg.in/yaml.v2 v2.4.0
Expand All @@ -25,6 +28,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/docker/cli v27.1.1+incompatible // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect
Expand All @@ -44,16 +48,19 @@ require (
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/minio/highwayhash v1.0.3 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nats-io/jwt/v2 v2.5.8 // indirect
github.com/nats-io/nkeys v0.4.7 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/oklog/run v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.20.4 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.57.0 // indirect
Expand All @@ -78,12 +85,13 @@ require (
go.opentelemetry.io/otel/trace v1.28.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.18.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/time v0.7.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
Loading

0 comments on commit 29ad1fd

Please sign in to comment.