From 07942d1786aa3c0955232c02fa72a903e02d511f Mon Sep 17 00:00:00 2001 From: Jonathan Davies Date: Fri, 12 Sep 2025 17:06:37 +0100 Subject: [PATCH 1/6] feat: add functionality to get security team --- Makefile | 31 +++++++++++++++++++++++++++ main.go | 45 +++++++++++++++++++++++++++++++++------- main_integration_test.go | 26 ++++++++++++++++++++--- 3 files changed, 91 insertions(+), 11 deletions(-) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ffd530b --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +# The help target prints out all targets with their descriptions organized +# beneath their categories. The categories are represented by '##@' and the +# target descriptions by '##'. The awk commands is responsible for reading the +# entire set of makefiles included in this invocation, looking for lines of the +# file as xyz: ## something, and then pretty-format the target and help. Then, +# if there's a line with ##@ something, that gets pretty-printed as a category. +# More info on the usage of ANSI catalog characters for terminal formatting: +# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters +# More info on the awk command: +# http://linuxcommand.org/lc3_adv_awk.php + +# Check if OPA CLI is installed +OPA := $(shell command -v opa 2> /dev/null) +ifeq ($(OPA),) +$(error "opa CLI not found. Please install it: https://www.openpolicyagent.org/docs/latest/cli/") +endif + +##@ Help +help: ## Display this concise help, ie only the porcelain target + @awk 'BEGIN {FS = ":.*##"; printf "\033[1mUsage\033[0m\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-30s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +help-all: ## Display all help items, ie including plumbing targets + @awk 'BEGIN {FS = ":.*#"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?#/ { printf " \033[36m%-25s\033[0m %s\n", $$1, $$2 } /^#@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +# Bundle the policies into a tarball for OCI registry +clean: # Cleanup build artifacts + @rm -rf dist/* + +build: clean ## Build the policy bundle + @mkdir -p dist/ + @go build -o dist/plugin main.go \ No newline at end of file diff --git a/main.go b/main.go index b4fe349..2ad1cc3 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,8 @@ import ( "context" "errors" "fmt" + "slices" + policyManager "github.com/compliance-framework/agent/policy-manager" "github.com/compliance-framework/agent/runner" "github.com/compliance-framework/agent/runner/proto" @@ -11,13 +13,13 @@ import ( "github.com/hashicorp/go-hclog" goplugin "github.com/hashicorp/go-plugin" "github.com/mitchellh/mapstructure" - "slices" ) type PluginConfig struct { - Token string `mapstructure:"token"` - Organization *string `mapstructure:"organization"` - User *string `mapstructure:"user"` + Token string `mapstructure:"token"` + Organization *string `mapstructure:"organization"` + User *string `mapstructure:"user"` + SecurityTeamName *string `mapstructure:"security-team-name"` } type DependabotPlugin struct { @@ -29,7 +31,6 @@ type DependabotPlugin struct { } func (l *DependabotPlugin) Configure(req *proto.ConfigureRequest) (*proto.ConfigureResponse, error) { - //l.config = req.GetConfig() config := &PluginConfig{} mapstructure.Decode(req.GetConfig(), config) l.config = config @@ -42,7 +43,20 @@ func (l *DependabotPlugin) Configure(req *proto.ConfigureRequest) (*proto.Config func (l *DependabotPlugin) Eval(req *proto.EvalRequest, apiHelper runner.ApiHelper) (*proto.EvalResponse, error) { ctx := context.TODO() repochan, errchan := l.FetchRepositories(ctx) + + var securityTeamMembers []*github.User + if *l.config.SecurityTeamName != "" { + var err error + securityTeamMembers, err = l.FetchSecurityTeamMembers(ctx) + if err != nil { + return &proto.EvalResponse{ + Status: proto.ExecutionStatus_FAILURE, + }, err + } + } + done := false + for !done { select { case err, ok := <-errchan: @@ -66,7 +80,14 @@ func (l *DependabotPlugin) Eval(req *proto.EvalRequest, apiHelper runner.ApiHelp }, err } - evidences, err := l.EvaluatePolicies(ctx, repo, alerts, req) + input := make(map[string]interface{}) + input["alerts"] = alerts + if securityTeamMembers != nil { + l.logger.Info(fmt.Sprintf("Assigning security team members: %v", securityTeamMembers)) + input["security_team_members"] = securityTeamMembers + } + + evidences, err := l.EvaluatePolicies(ctx, repo, &input, req) if err != nil { return &proto.EvalResponse{ Status: proto.ExecutionStatus_FAILURE, @@ -87,6 +108,14 @@ func (l *DependabotPlugin) Eval(req *proto.EvalRequest, apiHelper runner.ApiHelp }, nil } +func (l *DependabotPlugin) FetchSecurityTeamMembers(ctx context.Context) ([]*github.User, error) { + members, _, err := l.githubClient.Teams.ListTeamMembersBySlug(ctx, *l.config.Organization, *l.config.SecurityTeamName, nil) + if err != nil { + return nil, err + } + return members, err +} + func (l *DependabotPlugin) FetchRepositoryDependabotAlerts(ctx context.Context, repo *github.Repository) ([]*github.DependabotAlert, error) { alerts, _, err := l.githubClient.Dependabot.ListRepoAlerts(ctx, repo.GetOwner().GetLogin(), repo.GetName(), &github.ListAlertsOptions{ ListOptions: github.ListOptions{ @@ -140,7 +169,7 @@ func (l *DependabotPlugin) FetchRepositories(ctx context.Context) (<-chan *githu return repositories, errs } -func (l *DependabotPlugin) EvaluatePolicies(ctx context.Context, repo *github.Repository, alerts []*github.DependabotAlert, req *proto.EvalRequest) ([]*proto.Evidence, error) { +func (l *DependabotPlugin) EvaluatePolicies(ctx context.Context, repo *github.Repository, input *map[string]interface{}, req *proto.EvalRequest) ([]*proto.Evidence, error) { var accumulatedErrors error activities := make([]*proto.Activity, 0) @@ -260,7 +289,7 @@ func (l *DependabotPlugin) EvaluatePolicies(ctx context.Context, repo *github.Re actors, activities, ) - evidence, err := processor.GenerateResults(ctx, policyPath, alerts) + evidence, err := processor.GenerateResults(ctx, policyPath, input) evidences = slices.Concat(evidences, evidence) if err != nil { accumulatedErrors = errors.Join(accumulatedErrors, err) diff --git a/main_integration_test.go b/main_integration_test.go index f2803c7..f97e0ab 100644 --- a/main_integration_test.go +++ b/main_integration_test.go @@ -4,15 +4,15 @@ package main import ( "context" + "os" + "testing" + policy_manager "github.com/compliance-framework/agent/policy-manager" "github.com/google/go-github/v71/github" "github.com/hashicorp/go-hclog" - "os" - "testing" ) func TestDependabotPlugin_Integration_FetchRepositories(t *testing.T) { - ctx := context.Background() plugin := DependabotPlugin{ @@ -76,3 +76,23 @@ func TestDependabotPlugin_Integration_FetchRepositoryDependabotAlerts(t *testing t.Log("Successfully collected alerts", len(alerts)) } + +func TestDependabotPlugin_Integration_FetchTeamMembers(t *testing.T) { + ctx := context.Background() + + plugin := DependabotPlugin{ + logger: hclog.NewNullLogger(), + config: &PluginConfig{ + Token: os.Getenv("GITHUB_TOKEN"), + Organization: policy_manager.Pointer("compliance-framework"), + SecurityTeamName: policy_manager.Pointer("security"), + }, + githubClient: github.NewClient(nil).WithAuthToken(os.Getenv("GITHUB_TOKEN")), + } + + members, err := plugin.FetchSecurityTeamMembers(ctx) + if err != nil { + t.Error(err) + } + t.Log("Successfully collected securtiy team members", members) +} From 783314868f83fd2553791deba1899c7c9c99ae00 Mon Sep 17 00:00:00 2001 From: Jonathan Davies Date: Mon, 15 Sep 2025 15:37:29 +0100 Subject: [PATCH 2/6] fix: remove log --- main.go | 1 - 1 file changed, 1 deletion(-) diff --git a/main.go b/main.go index 2ad1cc3..04cb782 100644 --- a/main.go +++ b/main.go @@ -83,7 +83,6 @@ func (l *DependabotPlugin) Eval(req *proto.EvalRequest, apiHelper runner.ApiHelp input := make(map[string]interface{}) input["alerts"] = alerts if securityTeamMembers != nil { - l.logger.Info(fmt.Sprintf("Assigning security team members: %v", securityTeamMembers)) input["security_team_members"] = securityTeamMembers } From 3ef8df2a1de316b463987501a1e5faa663319701 Mon Sep 17 00:00:00 2001 From: Jonathan Davies Date: Mon, 15 Sep 2025 17:30:50 +0100 Subject: [PATCH 3/6] fix: use a struct --- main.go | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/main.go b/main.go index 04cb782..1aac4ca 100644 --- a/main.go +++ b/main.go @@ -24,12 +24,16 @@ type PluginConfig struct { type DependabotPlugin struct { logger hclog.Logger - data map[string]interface{} - config *PluginConfig + config *PluginConfig githubClient *github.Client } +type DependabotData struct { + Alerts []*github.DependabotAlert + SecurityTeamMembers []*github.User +} + func (l *DependabotPlugin) Configure(req *proto.ConfigureRequest) (*proto.ConfigureResponse, error) { config := &PluginConfig{} mapstructure.Decode(req.GetConfig(), config) @@ -80,13 +84,14 @@ func (l *DependabotPlugin) Eval(req *proto.EvalRequest, apiHelper runner.ApiHelp }, err } - input := make(map[string]interface{}) - input["alerts"] = alerts + data := &DependabotData{ + Alerts: alerts, + } if securityTeamMembers != nil { - input["security_team_members"] = securityTeamMembers + data.SecurityTeamMembers = securityTeamMembers } - evidences, err := l.EvaluatePolicies(ctx, repo, &input, req) + evidences, err := l.EvaluatePolicies(ctx, repo, data, req) if err != nil { return &proto.EvalResponse{ Status: proto.ExecutionStatus_FAILURE, @@ -168,7 +173,7 @@ func (l *DependabotPlugin) FetchRepositories(ctx context.Context) (<-chan *githu return repositories, errs } -func (l *DependabotPlugin) EvaluatePolicies(ctx context.Context, repo *github.Repository, input *map[string]interface{}, req *proto.EvalRequest) ([]*proto.Evidence, error) { +func (l *DependabotPlugin) EvaluatePolicies(ctx context.Context, repo *github.Repository, data *DependabotData, req *proto.EvalRequest) ([]*proto.Evidence, error) { var accumulatedErrors error activities := make([]*proto.Activity, 0) @@ -288,7 +293,7 @@ func (l *DependabotPlugin) EvaluatePolicies(ctx context.Context, repo *github.Re actors, activities, ) - evidence, err := processor.GenerateResults(ctx, policyPath, input) + evidence, err := processor.GenerateResults(ctx, policyPath, data) evidences = slices.Concat(evidences, evidence) if err != nil { accumulatedErrors = errors.Join(accumulatedErrors, err) From 4e0ac6b2b8078076f2a9adbc6fe8b4770d6505a9 Mon Sep 17 00:00:00 2001 From: Jonathan Davies Date: Tue, 16 Sep 2025 13:43:12 +0100 Subject: [PATCH 4/6] Update main.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 1aac4ca..9ec3cdc 100644 --- a/main.go +++ b/main.go @@ -49,7 +49,7 @@ func (l *DependabotPlugin) Eval(req *proto.EvalRequest, apiHelper runner.ApiHelp repochan, errchan := l.FetchRepositories(ctx) var securityTeamMembers []*github.User - if *l.config.SecurityTeamName != "" { + if l.config.SecurityTeamName != nil && *l.config.SecurityTeamName != "" { var err error securityTeamMembers, err = l.FetchSecurityTeamMembers(ctx) if err != nil { From 4ee9427662e3c3b64da42b1081663d178962c902 Mon Sep 17 00:00:00 2001 From: Jonathan Davies Date: Tue, 16 Sep 2025 13:43:25 +0100 Subject: [PATCH 5/6] Update main.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 9ec3cdc..ceb98f5 100644 --- a/main.go +++ b/main.go @@ -117,7 +117,7 @@ func (l *DependabotPlugin) FetchSecurityTeamMembers(ctx context.Context) ([]*git if err != nil { return nil, err } - return members, err + return members, nil } func (l *DependabotPlugin) FetchRepositoryDependabotAlerts(ctx context.Context, repo *github.Repository) ([]*github.DependabotAlert, error) { From c6faabf02fe5af9fb0df7778f3b315b8f0009278 Mon Sep 17 00:00:00 2001 From: Jonathan Davies Date: Tue, 16 Sep 2025 13:43:36 +0100 Subject: [PATCH 6/6] Update main_integration_test.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- main_integration_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main_integration_test.go b/main_integration_test.go index f97e0ab..b21635b 100644 --- a/main_integration_test.go +++ b/main_integration_test.go @@ -94,5 +94,5 @@ func TestDependabotPlugin_Integration_FetchTeamMembers(t *testing.T) { if err != nil { t.Error(err) } - t.Log("Successfully collected securtiy team members", members) + t.Log("Successfully collected security team members", members) }