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
6 changes: 6 additions & 0 deletions changelog/v0.29.1/ci-update-gh-issue-writer.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
changelog:
- type: NON_USER_FACING
description: |
Updated security scan GH issue writer to:
- create single issue for a given major.minor.patch, e.g. 2.1.x.
- append suffix to GH issue title so we can create multiple issues for a repo.
13 changes: 10 additions & 3 deletions securityscanutils/commands/scan_repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type scanRepoOptions struct {
// action to take when a vulnerability is discovered. supported actions are:
// none (default): do nothing when a vulnerability is discovered
// github-issue-latest (preferred): create a github issue only for the latest patch version of each minor version, when a vulnerability is discovered
// github-issue-minor: create/update a single github issue per minor (e.g. 2.0.x)
// github-issue-all: create a github issue for every version where a vulnerability is discovered
// output-locally: create a file in the generated output dir containing the final Markdown for each repo / version
vulnerabilityAction string
Expand All @@ -51,20 +52,24 @@ type scanRepoOptions struct {
additionalContextFile string

enablePreRelease bool

issueTitleSuffix string
}

func (m *scanRepoOptions) addToFlags(flags *pflag.FlagSet) {
flags.StringVarP(&m.githubRepository, "github-repo", "g", "", "github repository to scan")
flags.StringVarP(&m.imageRepository, "image-repo", "r", securityscanutils.QuayRepository, "image repository to scan")

flags.StringVarP(&m.vulnerabilityAction, "vulnerability-action", "a", "none", "action to take when a vulnerability is discovered {none, github-issue-all, github-issue-latest, output-locally}")
flags.StringVarP(&m.vulnerabilityAction, "vulnerability-action", "a", "none", "action to take when a vulnerability is discovered {none, github-issue-all, github-issue-latest, github-issue-minor, output-locally}")

flags.StringVarP(&m.releaseVersionConstraint, "release-constraint", "c", "", "version constraint for releases to scan")
flags.BoolVar(&m.enablePreRelease, "enable-pre-release", false, "enable pre-release versions to be scanned")

flags.StringVarP(&m.imagesVersionConstraintFile, "image-constraint-file", "i", "", "name of file with mapping of version to images")
flags.StringVarP(&m.additionalContextFile, "additional-context-file", "d", "", "name of file with any additional context to add to the top of the generated vulnerability report")

flags.StringVar(&m.issueTitleSuffix, "issue-title-suffix", "", "text to append to the GitHub issue title (appended in parentheses)")

cliutils.MustMarkFlagRequired(flags, "github-repo")
cliutils.MustMarkFlagRequired(flags, "release-constraint")
cliutils.MustMarkFlagRequired(flags, "image-constraint-file")
Expand Down Expand Up @@ -97,8 +102,10 @@ func doScanRepo(ctx context.Context, opts *scanRepoOptions) error {
OutputResultLocally: opts.vulnerabilityAction == "output-locally",
CreateGithubIssuePerVersion: opts.vulnerabilityAction == "github-issue-all",
CreateGithubIssueForLatestPatchVersion: opts.vulnerabilityAction == "github-issue-latest",
AdditionalContext: additionalContext,
EnablePreRelease: opts.enablePreRelease,
CreateGithubIssueForMinorLatestPatchVersion: opts.vulnerabilityAction == "github-issue-minor",
AdditionalContext: additionalContext,
EnablePreRelease: opts.enablePreRelease,
IssueTitleSuffix: opts.issueTitleSuffix,
},
},
},
Expand Down
33 changes: 31 additions & 2 deletions securityscanutils/issuewriter/github_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,37 @@ type GithubIssueWriter struct {
// A local cache of all existing GitHub issues
// Used to ensure that we are updating existing issues that were created by previous scans
allGithubIssues []*github.Issue

// If true, the issue title will be created per minor (e.g. "Security Alert: 2.0.x")
// instead of per exact patch (e.g. "Security Alert: 2.0.1")
useMinorIssueTitle bool

// Optional suffix appended to the issue title in parentheses.
titleSuffix string
}

var _ IssueWriter = &GithubIssueWriter{}

func NewGithubIssueWriter(repo GithubRepo, client *github.Client, issuePredicate githubutils.RepositoryReleasePredicate) IssueWriter {
func NewGithubIssueWriter(repo GithubRepo, client *github.Client, issuePredicate githubutils.RepositoryReleasePredicate, titleSuffix string) IssueWriter {
return &GithubIssueWriter{
repo: repo,
client: client,
createGithubIssuePredicate: issuePredicate,
allGithubIssues: nil, // initially nil, we'll lazy load these
useMinorIssueTitle: false,
titleSuffix: titleSuffix,
}
}

// NewGithubIssueWriterWithMinorTitle constructs a GithubIssueWriter that titles issues per minor version (X.Y.x)
func NewGithubIssueWriterWithMinorTitle(repo GithubRepo, client *github.Client, issuePredicate githubutils.RepositoryReleasePredicate, titleSuffix string) IssueWriter {
return &GithubIssueWriter{
repo: repo,
client: client,
createGithubIssuePredicate: issuePredicate,
allGithubIssues: nil,
useMinorIssueTitle: true,
titleSuffix: titleSuffix,
}
}

Expand Down Expand Up @@ -92,7 +113,15 @@ func (g *GithubIssueWriter) Write(
// will not be included in the filtered list
versionToScan, _ := semver.NewVersion(release.GetTagName())

issueTitle := fmt.Sprintf("Security Alert: %s", versionToScan.String())
var issueTitle string
if g.useMinorIssueTitle {
issueTitle = fmt.Sprintf("Security Alert: %d.%d.x", versionToScan.Major(), versionToScan.Minor())
} else {
issueTitle = fmt.Sprintf("Security Alert: %s", versionToScan.String())
}
if g.titleSuffix != "" {
issueTitle = fmt.Sprintf("%s (%s)", issueTitle, g.titleSuffix)
}
issueRequest := &github.IssueRequest{
Title: github.String(issueTitle),
Body: github.String(vulnerabilityMarkdown),
Expand Down
2 changes: 1 addition & 1 deletion securityscanutils/issuewriter/local_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func (l *LocalIssueWriter) Write(_ context.Context, release *github.RepositoryRe
if err != nil {
return err
}
_, err = fmt.Fprintf(f, contents)
_, err = fmt.Fprint(f, contents)
if err != nil {
return err
}
Expand Down
22 changes: 18 additions & 4 deletions securityscanutils/securityscan.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ type SecurityScanOpts struct {

// Enable scanning of pre-release versions
EnablePreRelease bool

// Create/Update a single GitHub issue per minor version (e.g., "Security Alert: 2.0.x")
// rather than per exact patch version. When enabled, the writer will target a minor-scoped
// issue title. Scanning behavior is unchanged; it will scan the selected release(s).
CreateGithubIssueForMinorLatestPatchVersion bool

// Optional string appended to the GitHub issue title in parentheses.
IssueTitleSuffix string
}

// GenerateSecurityScans generates .md files and writes them to the configured OutputDir for each repo
Expand Down Expand Up @@ -182,18 +190,24 @@ func (s *SecurityScanner) initializeRepoConfiguration(ctx context.Context, repo
}
// Default to not creating any issues
var issuePredicate githubutils.RepositoryReleasePredicate = &githubutils.NoReleasesPredicate{}
useGithubWriter := repoOptions.CreateGithubIssuePerVersion || repoOptions.CreateGithubIssueForLatestPatchVersion
useGithubWriter := repoOptions.CreateGithubIssuePerVersion ||
repoOptions.CreateGithubIssueForLatestPatchVersion ||
repoOptions.CreateGithubIssueForMinorLatestPatchVersion
if repoOptions.CreateGithubIssuePerVersion {
// Create Github issue for all releases, if configured
issuePredicate = &githubutils.AllReleasesPredicate{}
}

if repoOptions.CreateGithubIssueForLatestPatchVersion {
// Create Github issues for all releases in the set
if repoOptions.CreateGithubIssueForLatestPatchVersion || repoOptions.CreateGithubIssueForMinorLatestPatchVersion {
// For both "latest" and "minor-latest" modes, only write for the latest patch releases
issuePredicate = NewLatestPatchRepositoryReleasePredicate(releasesToScan)
}
if useGithubWriter {
repo.issueWriter = issuewriter.NewGithubIssueWriter(githubRepo, s.githubClient, issuePredicate)
if repo.Opts.CreateGithubIssueForMinorLatestPatchVersion {
repo.issueWriter = issuewriter.NewGithubIssueWriterWithMinorTitle(githubRepo, s.githubClient, issuePredicate, repo.Opts.IssueTitleSuffix)
} else {
repo.issueWriter = issuewriter.NewGithubIssueWriter(githubRepo, s.githubClient, issuePredicate, repo.Opts.IssueTitleSuffix)
}
logger.Debugf("GithubIssueWriter configured with Predicate: %+v", issuePredicate)
} else if repo.Opts.OutputResultLocally {
repo.issueWriter, err = issuewriter.NewLocalIssueWriter(path.Join(repo.Opts.OutputDir, githubRepo.RepoName, "issue_results"))
Expand Down