feat(ci): add Aviator remediation action with automated PR creation (SSC & FoD)#1037
feat(ci): add Aviator remediation action with automated PR creation (SSC & FoD)#1037SangameshV wants to merge 46 commits into
Conversation
…ed the same from generic actions
…add debug logging
There was a problem hiding this comment.
Pull request overview
This PR adds an automated Aviator remediation workflow to the built-in SSC and FoD CI action zips, including applying remediations, committing changes, pushing a branch, and creating a PR/MR through the detected CI provider (GitHub/GitLab).
Changes:
- Bump action schema version to 2.9.0.
- Add new SSC/FoD actions (
apply-remediations,create-pr) and wire them into the existing SSC/FoDci.yamlpipelines. - Extend CI helper APIs (GitHub/GitLab) and add new
#git.*SpEL functions (JGit-backed) to support git status/branch/commit/push operations from actions.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| gradle.properties | Bumps action schema version to 2.9.0. |
| fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/ci.yaml | Adds remediation + PR creation steps to SSC pipeline and fixes Aviator wait argument wiring. |
| fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/apply-remediations.yaml | New SSC action that applies Aviator remediations to the local source tree. |
| fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/actions/zip/create-pr.yaml | New SSC action that commits/pushes changes and creates a PR/MR. |
| fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/ci.yaml | Adds remediation + PR creation steps to FoD pipeline. |
| fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/apply-remediations.yaml | New FoD action that applies Aviator remediations to the local source tree. |
| fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/actions/zip/create-pr.yaml | New FoD action that commits/pushes changes and creates a PR/MR. |
| fcli-core/fcli-common-ci/src/main/java/com/fortify/cli/common/ci/gitlab/GitLabProject.java | Adds GitLab merge request creation API call. |
| fcli-core/fcli-common-ci/src/main/java/com/fortify/cli/common/ci/github/GitHubRepo.java | Adds GitHub pull request creation API call. |
| fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/ci/gitlab/ActionGitLabProject.java | Exposes GitLab MR creation as a SpEL function for actions. |
| fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/ci/github/ActionGitHubRepo.java | Exposes GitHub PR creation as a SpEL function for actions. |
| fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/runner/ActionRunnerContextLocal.java | Registers new git SpEL variable for action execution. |
| fcli-core/fcli-common-action/src/main/java/com/fortify/cli/common/action/helper/git/ActionGitSpelFunctions.java | New JGit-backed SpEL functions for repo inspection + git operations used by create-pr. |
| fcli-core/fcli-app/src/main/resources/META-INF/native-image/fcli/fcli-app/jgit/reflect-config.json | Updates native-image reflection configuration for JGit/LFS-related classes. |
| // GitLab CI | ||
| token = EnvHelper.env("CI_JOB_TOKEN"); | ||
| if (StringUtils.isNotBlank(token)) { | ||
| return new UsernamePasswordCredentialsProvider("gitlab-ci-token", token); | ||
| } |
| var remoteUrl = repo.getConfig().getString("remote", remote, "url"); | ||
| if (remoteUrl != null && !remoteUrl.endsWith(".git")) { | ||
| remoteUrl = remoteUrl + ".git"; | ||
| repo.getConfig().setString("remote", remote, "url", remoteUrl); | ||
| repo.getConfig().save(); | ||
| } |
| - if: ${#isBlank(gitRepoInfo.repository.remoteUrl)} | ||
| throw: "Git repository has no remote URL configured. Cannot create PR without remote repository access." | ||
| - if: ${gitRepoInfo!=null} | ||
| log.info: "Git repository: workspace=${gitRepoInfo.repository.workspaceDir}, remote=${gitRepoInfo.repository.remoteUrl?:'not configured'}, branch=${gitRepoInfo.branch.short_?:'detached HEAD'}" |
| - var.set: | ||
| githubToken: "${#env('GITHUB_TOKEN')?:#env('GH_TOKEN')}" | ||
| gitlabToken: "${#env('CI_JOB_TOKEN')}" | ||
| azureToken: "${#env('SYSTEM_ACCESSTOKEN')}" | ||
| bitbucketToken: "${#env('BITBUCKET_TOKEN')}" |
| - var.set: | ||
| githubToken: "${#env('GITHUB_TOKEN')?:#env('GH_TOKEN')}" | ||
| gitlabToken: "${#env('CI_JOB_TOKEN')}" | ||
| azureToken: "${#env('SYSTEM_ACCESSTOKEN')}" | ||
| bitbucketToken: "${#env('BITBUCKET_TOKEN')}" |
|
|
||
| - var.set: | ||
| sourceDir: ${global.ci.sourceDir?:'.'} | ||
| artifactSelector: "${#isNotBlank(cli.aviatorArtifact) ? '--artifact-id '+cli.aviatorArtifact : '--av '+av+' --latest'}" |
| - `SOURCE_DIR` — Source directory where changes are detected (default: CI workspace or current directory) | ||
| - `PR_TITLE` — PR/MR title (default: "fix: Fortify auto-remediation fixes [generated by fcli]") | ||
| - `PR_BODY` — PR/MR body (default: auto-generated description) | ||
| - `PR_BRANCH_PREFIX` — Branch name prefix (default: "fcli/remediation") | ||
| - `PR_COMMIT_MESSAGE` — Commit message (default: "fix: apply Fortify auto-remediation fixes [generated by fcli]") |
| - `SOURCE_DIR` — Source directory where changes are detected (default: CI workspace or current directory) | ||
| - `PR_TITLE` — PR/MR title (default: "fix: Fortify auto-remediation fixes [generated by fcli]") | ||
| - `PR_BODY` — PR/MR body (default: auto-generated description) | ||
| - `PR_BRANCH_PREFIX` — Branch name prefix (default: "fcli/remediation") | ||
| - `PR_COMMIT_MESSAGE` — Commit message (default: "fix: apply Fortify auto-remediation fixes [generated by fcli]") |
rsenden
left a comment
There was a problem hiding this comment.
Note that some of the comments posted on FoD files may also apply to SSC, or vice versa.
Other than individual comments, I'm wondering whether we can improve architecture to be more logical and more user-friendly, which would supersede most of the individual comments.
Please investigate whether something like the following in the ci.yaml files would work:
AVIATOR_REMEDIATE:
cmd: ${#fcliCmd(<SSC/FoD apply-remediations cmd>)}
skip.if-reason:
<same as before, possibly enhanced for FoD to check remediations are available>
on.success:
- var.set:
createPrResult: ${#<CI-specific SpEL function>(subject, body, ...)}
- log.info: Aviator remediations ${global.ci.prTerminology} created: ${createPrResult.prLink}
I.e.:
- We'd get rid of
apply-remedations.yamlandcreate-pr.yamlactions - Functionality from
create-pr.yamlwould move to Java code, exposed through SpEL function(s) - Running apply-remediations and creating the PR/MR consolidated in a single
ci.yamlstep
Main questions:
- How to easily call a CI-specific SpEL function, like
#github.repo().raisePR(...)vs#gitlab.project().raiseMR(...)in a generic way, without hardcoding knowledge about supported CI systems and what functions they expose, directly inci.yaml. Maybe just add araisePR(...)method onActionCiSpelFunctions(or have a separate SpelFunctions class that handles cross-CI PR logic), which callsdetect()and then calls the appropriate CI-specific SpEL function on the detected CI system? - How to handle add/commit/push? Probably easiest to do this from Java code, such that SSC/FoD
ci.yamlfiles just need to call a single SpEL function
Also something to consider; what if the build that was executed by scancentral package caused any changes in current git state (like adding generated code in non-ignored paths). We'd want the PR to only include code that was added/removed/updated by the apply-remediations command, so possibly we should stash any changes before running apply-remediations, and unstash after pushing the Aviator changes/raising the PR?
| @Reflectable | ||
| @SpelFunctionPrefix("git.") | ||
| @Slf4j | ||
| public class ActionGitSpelFunctions { |
There was a problem hiding this comment.
Some of the methods in this class are very long; please refactor using private helper methods. Also, please make sure that proper imports are used instead of fully qualified class names.
| } | ||
| } | ||
|
|
||
| private CredentialsProvider detectCredentialsProvider() { |
There was a problem hiding this comment.
This tightly binds generic Git functionality to individual CI systems. When we add support for a CI system, we'd expect having to create corresponding CI-specific classes, but it may be easily overlooked that this method needs to be updated as well.
Can we refactor this, for example by having some getGitCredentialsProvider method in each of the CI implementations, and then either have the fcli action yaml explicitly retrieve credentials from currently active CI environment and pass this to ActionGitSpelFunctions methods, or have ActionGitSpelFunctions somehow retrieve this from the currently active CI environment implementation?
Please check what architecture would fit best here, for example in which of the CI-specific classes the getGitCredentialsProvider method should live (if we choose to have such a method), and how ActionGitSpelFunctions can gain access to that.
Also, what if for example workflow is running in ADO, but repo is on GitHub? I guess in that case, SYSTEM_ACCESSTOKEN wouldn't give access to the GitHub repo, so would be good to do some research as to how other (non-Fortify) pipeline integrations handle such scenarios. For now, we can consider this an edge case, but it might affect optimal architecture for the above, so good to have a look at this.
| the fixes to the local source tree. | ||
|
|
||
| This action requires: | ||
| - A FoD session to be active (for downloading the audited FPR with remediations) |
There was a problem hiding this comment.
This is true for all actions in the FoD module, so unnecessary to explicitly list this as requirement
| on.success: | ||
| - var.set: { postScan.skipReason: } # Reset postScan.skipReason to allow post-scan tasks to run | ||
|
|
||
| APPLY_REMEDIATIONS: |
There was a problem hiding this comment.
For consistency with other Aviator-related steps, probably better to rename steps and env vars to something like AVIATOR_REMEDIATE.
|
|
||
|
|
||
| on.success: | ||
| - var.set: |
There was a problem hiding this comment.
Why do we need to explicitly retrieve artifact id from the output, rather than just relying on fcli --store option and using ::aviator_audit:: (like we did before) instead of ${aviator.artifactId}
| skip.if-reason: | ||
| - ${#actionCmdSkipFromEnvReason('APPLY_REMEDIATIONS', true)} # Skip unless DO_APPLY_REMEDIATIONS==true or APPLY_REMEDIATIONS_ACTION/EXTRA_OPTS defined | ||
| - ${postScan.skipReason} # Skip if no scans were run | ||
| - ${#env('DO_AVIATOR_AUDIT')!='true'?'Aviator audit not enabled (DO_AVIATOR_AUDIT!=true), no remediations available':''} # Skip if Aviator audit was not enabled |
There was a problem hiding this comment.
Any more reliable way to check whether Aviator Audit was run? Maybe check FoD whether Aviator audit was run, or, even better, check whether remediations are available?
| - ${#env('DO_AVIATOR_AUDIT')!='true'?'Aviator audit not enabled (DO_AVIATOR_AUDIT!=true), no remediations available':''} # Skip if Aviator audit was not enabled | ||
| - ${SAST_WAIT.dependencySkipReason} # Skip if SAST scan/wait was skipped or failed | ||
|
|
||
| CREATE_PR: |
There was a problem hiding this comment.
In a CI environment, it doesn't make sense to apply remediations but not raise a PR; if Aviator remediation was performed, we should unconditionally raise a PR (i.e., DO_AVIATOR_REMEDIATE controls both applying remediations and raising the associated PR).
| @@ -0,0 +1,177 @@ | |||
| # yaml-language-server: $schema=https://fortify.github.io/fcli/schemas/action/fcli-action-schema-dev-2.x.json | |||
There was a problem hiding this comment.
This file seems to be almost an exact duplicate of the FoD create-pr.yaml action (apart from rest.target.default value, but as we don't do any SSC/FoD REST calls, we don't need this); please avoid this duplication, for example:
- We still have code in
ActionLoaderHelperto load actions fromcommonActionsResourceZip, although I don't know whether we still have Gradle build step to generatecom/fortify/cli/common/actions.zip, and whether that requires common actions to be in a specific module (fcli-common-actionwould now be the most appropriate module for defining common actions). If we'd movecreate-pr.yamlto a common action, it would automatically be available forfcli fod action,fcli ssc action, andfcli action. - Simply move to
fcli-action, i.e., we'd runfcli action run pr-commentfor both SSC and FoD workflows - Get rid of this action altogether (see other comments about architecture)
|
|
||
| - run.fcli: | ||
| APPLY_REMEDIATIONS: | ||
| cmd: fod aviator apply-remediations --rel "${rel}" --source-dir "${sourceDir}" |
There was a problem hiding this comment.
For interactive use, having this action doesn't make much sense; none of the CI env detection and FoD/SSC env vars are relevant in non-CI environments, hence users can just as well run the apply-remediations command directly. For CI use, we already have all the relevant data available in ci.yaml, so it would make more sense to directly run apply-remediations command from ci.yaml.
…ing to print the read results of called fcli action
…changes and create-pr actions.
…ctive results storing
Summary
Introduces a new FCLI action to automatically apply Aviator remediation fixes and create a pull request with the generated changes.
This implementation supports both SSC (Software Security Center) and FoD (Fortify on Demand) pipelines.
Background
Aviator generates remediation changes for identified issues, but there was no automated way to:
Changes
✅ New Feature: Automated Remediation Action