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: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ dist/
cf-plugin-local-ssh
plugin
.env
.envrc
.envrc
plugin-*
8 changes: 4 additions & 4 deletions README.MD → README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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.
8 changes: 4 additions & 4 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down
62 changes: 57 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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{},
})
Expand All @@ -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{
Expand All @@ -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++
Expand All @@ -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
}
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Loading