diff --git a/.gitignore b/.gitignore index d74d280..517fc78 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ dist/ cf-plugin-local-ssh plugin .env -.envrc \ No newline at end of file +.envrc +plugin-* diff --git a/README.MD b/README.md similarity index 91% rename from README.MD rename to README.md index 8726a41..3691e77 100644 --- a/README.MD +++ b/README.md @@ -13,7 +13,7 @@ To authenticate this plugin, you must provide a token which has at minimum the f ## Integration testing -This plugin contains unit tests as well as integration tests. +This plugin contains unit tests as well as integration tests. The Integration tests need a GitHub token to call to the GitHub API. @@ -33,9 +33,9 @@ package compliance_framework.deny_critical_severity ## Releases -This plugin is released using goreleaser to build binaries, and GOOCI to upload artifacts to OCI, -which will ensure a binary is built for most OS and Architecture combinations. +This plugin is released using goreleaser to build binaries, and GOOCI to upload artifacts to OCI, +which will ensure a binary is built for most OS and Architecture combinations. You can find the binaries on each release of this plugin in the GitHub releases page. -You can find the OCI implementations in the GitHub Packages page. +You can find the OCI implementations in the GitHub Packages page. diff --git a/examples/README.md b/examples/README.md index 5d0baa9..50e3ee5 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,11 +1,11 @@ -# Example output from Local SSH plugin +# Example output from Dependabot plugin -In this folder you'll find example data and policies for the Local SSH Plugin +In this folder you'll find example data and policies for the Dependabot Plugin ## Data -In `input/` you'll find an example of the data that is output from the plugin and passed to the policy files. This is -what you will write policies against to check for compliance. +In `input/` you'll find an example of the data that is output from the plugin and passed to the policy files. This is +what you will write policies against to check for compliance. These will be defined as `input` in Rego policies. diff --git a/main.go b/main.go index ceb98f5..3c3296b 100644 --- a/main.go +++ b/main.go @@ -60,6 +60,8 @@ func (l *DependabotPlugin) Eval(req *proto.EvalRequest, apiHelper runner.ApiHelp } done := false + // Track permission issues during alert collection + reposAlertsPermissionDenied := make([]string, 0) for !done { select { @@ -79,6 +81,11 @@ func (l *DependabotPlugin) Eval(req *proto.EvalRequest, apiHelper runner.ApiHelp alerts, err := l.FetchRepositoryDependabotAlerts(ctx, repo) if err != nil { + if isPermissionError(err) { + l.logger.Warn("Skipping repository due to insufficient permissions for alerts fetch", "repo", repo.GetFullName(), "error", err) + reposAlertsPermissionDenied = append(reposAlertsPermissionDenied, repo.GetFullName()) + continue + } return &proto.EvalResponse{ Status: proto.ExecutionStatus_FAILURE, }, err @@ -107,6 +114,10 @@ func (l *DependabotPlugin) Eval(req *proto.EvalRequest, apiHelper runner.ApiHelp } } + if len(reposAlertsPermissionDenied) > 0 { + l.logger.Info("Repositories skipped due to insufficient permissions (alerts)", "count", len(reposAlertsPermissionDenied), "repos", reposAlertsPermissionDenied) + } + return &proto.EvalResponse{ Status: proto.ExecutionStatus_SUCCESS, }, nil @@ -124,7 +135,7 @@ func (l *DependabotPlugin) FetchRepositoryDependabotAlerts(ctx context.Context, alerts, _, err := l.githubClient.Dependabot.ListRepoAlerts(ctx, repo.GetOwner().GetLogin(), repo.GetName(), &github.ListAlertsOptions{ ListOptions: github.ListOptions{ Page: 1, - PerPage: 200, + PerPage: 100, }, ListCursorOptions: github.ListCursorOptions{}, }) @@ -139,6 +150,10 @@ func (l *DependabotPlugin) FetchRepositories(ctx context.Context) (<-chan *githu defer close(errs) page := 1 done := false + // Tracking for logging visibility + emittedRepos := make([]string, 0) + noPermissionRepos := make([]string, 0) + archivedSkipped := 0 for !done { l.logger.Trace("Fetching repositories from Github API") repos, _, err := l.githubClient.Repositories.ListByOrg(ctx, *l.config.Organization, &github.RepositoryListByOrgOptions{ @@ -153,14 +168,25 @@ func (l *DependabotPlugin) FetchRepositories(ctx context.Context) (<-chan *githu break } for _, repo := range repos { + if repo.GetArchived() { + archivedSkipped++ + continue + } + alertsEnabled, _, err := l.githubClient.Repositories.GetVulnerabilityAlerts(ctx, repo.GetOwner().GetLogin(), repo.GetName()) if err != nil { + if isPermissionError(err) { + l.logger.Warn("Skipping repository due to insufficient permissions for vulnerability alerts check", "repo", repo.GetFullName(), "error", err) + noPermissionRepos = append(noPermissionRepos, repo.GetFullName()) + continue + } errs <- err done = true break } - if !repo.GetArchived() && alertsEnabled { + if alertsEnabled { repositories <- repo + emittedRepos = append(emittedRepos, repo.GetFullName()) } } page++ @@ -169,6 +195,14 @@ func (l *DependabotPlugin) FetchRepositories(ctx context.Context) (<-chan *githu break } } + // Emit a summary for engineers to understand visibility + l.logger.Info("Repository enumeration summary", "emitted", len(emittedRepos), "skipped_permissions", len(noPermissionRepos), "skipped_archived", archivedSkipped) + if len(emittedRepos) > 0 { + l.logger.Debug("Repositories with sufficient permissions (and alerts enabled)", "repos", emittedRepos) + } + if len(noPermissionRepos) > 0 { + l.logger.Info("Repositories without sufficient permissions", "repos", noPermissionRepos) + } }() return repositories, errs } @@ -195,13 +229,13 @@ func (l *DependabotPlugin) EvaluatePolicies(ctx context.Context, repo *github.Re Props: nil, }, { - Title: "Continuous Compliance Framework - Local SSH Plugin", + Title: "Continuous Compliance Framework - Dependabot Plugin", Type: "tool", Links: []*proto.Link{ { - Href: "https://github.com/compliance-framework/plugin-local-ssh", + Href: "https://github.com/compliance-framework/plugin-dependabot", Rel: policyManager.Pointer("reference"), - Text: policyManager.Pointer("The Continuous Compliance Framework' Local SSH Plugin"), + Text: policyManager.Pointer("The Continuous Compliance Framework Dependabot Plugin"), }, }, Props: nil, @@ -305,6 +339,24 @@ func (l *DependabotPlugin) EvaluatePolicies(ctx context.Context, repo *github.Re return evidences, accumulatedErrors } +// isPermissionError returns true if the error from the GitHub client indicates +// a permissions or visibility issue (e.g., 401/403/404). +func isPermissionError(err error) bool { + if err == nil { + return false + } + var ger *github.ErrorResponse + if errors.As(err, &ger) { + if ger.Response != nil { + switch ger.Response.StatusCode { + case 401, 403, 404: + return true + } + } + } + return false +} + func main() { logger := hclog.New(&hclog.LoggerOptions{ Level: hclog.Debug,