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
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ jobs:
AZURE_PROJECT: ${{ secrets.AZURE_PROJECT }}
AZURE_REPOS: ${{ secrets.AZURE_REPOS }}
AZURE_TOKEN: ${{ secrets.AZURE_TOKEN }}
AZURE_NEW_ORG: "azureAccountTests"
AZURE_PROJECT_NAME: "testsProject"
AZURE_PR_NUMBER: 1
AZURE_NEW_TOKEN: ${{ secrets.AZURE_NEW_TOKEN }}
BITBUCKET_WORKSPACE: ${{ secrets.BITBUCKET_WORKSPACE }}
BITBUCKET_REPOS: ${{ secrets.BITBUCKET_REPOS }}
BITBUCKET_USERNAME: ${{ secrets.BITBUCKET_USERNAME }}
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/manual-integration-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ jobs:
AZURE_PROJECT: ${{ secrets.AZURE_PROJECT }}
AZURE_REPOS: ${{ secrets.AZURE_REPOS }}
AZURE_TOKEN: ${{ secrets.AZURE_TOKEN }}
AZURE_NEW_ORG: "azureAccountTests"
AZURE_PROJECT_NAME: "testsProject"
AZURE_PR_NUMBER: 1
AZURE_NEW_TOKEN: ${{ secrets.AZURE_NEW_TOKEN }}
BITBUCKET_WORKSPACE: ${{ secrets.BITBUCKET_WORKSPACE }}
BITBUCKET_REPOS: ${{ secrets.BITBUCKET_REPOS }}
BITBUCKET_USERNAME: ${{ secrets.BITBUCKET_USERNAME }}
Expand Down
3 changes: 2 additions & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func main() {
bfl := viper.GetString(params.BflPathKey)
prDecorationGithubPath := viper.GetString(params.PRDecorationGithubPathKey)
prDecorationGitlabPath := viper.GetString(params.PRDecorationGitlabPathKey)
prDecorationAzurePath := viper.GetString(params.PRDecorationAzurePathKey)
descriptionsPath := viper.GetString(params.DescriptionsPathKey)
tenantConfigurationPath := viper.GetString(params.TenantConfigurationPathKey)
resultsPdfPath := viper.GetString(params.ResultsPdfReportPathKey)
Expand Down Expand Up @@ -72,7 +73,7 @@ func main() {
bitBucketServerWrapper := bitbucketserver.NewBitbucketServerWrapper()
gitLabWrapper := wrappers.NewGitLabWrapper()
bflWrapper := wrappers.NewBflHTTPWrapper(bfl)
prWrapper := wrappers.NewHTTPPRWrapper(prDecorationGithubPath, prDecorationGitlabPath)
prWrapper := wrappers.NewHTTPPRWrapper(prDecorationGithubPath, prDecorationGitlabPath, prDecorationAzurePath)
learnMoreWrapper := wrappers.NewHTTPLearnMoreWrapper(descriptionsPath)
tenantConfigurationWrapper := wrappers.NewHTTPTenantConfigurationWrapper(tenantConfigurationPath)
jwtWrapper := wrappers.NewJwtWrapper()
Expand Down
132 changes: 132 additions & 0 deletions internal/commands/util/pr.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (

const (
failedCreatingGithubPrDecoration = "Failed creating github PR Decoration"
failedCreatingAzurePrDecoration = "Failed creating azure PR Decoration"
failedCreatingGitlabPrDecoration = "Failed creating gitlab MR Decoration"
errorCodeFormat = "%s: CODE: %d, %s\n"
policyErrorFormat = "%s: Failed to get scanID policy information"
Expand All @@ -27,6 +28,8 @@ const (
gitlabOnPremURLSuffix = "/api/v4/"
githubCloudURL = "https://api.github.com/repos/"
gitlabCloudURL = "https://gitlab.com" + gitlabOnPremURLSuffix
azureCloudURL = "https://dev.azure.com/"
errorAzureOnPremParams = "code-repository-url must be set when code-repository-username is set"
)

func NewPRDecorationCommand(prWrapper wrappers.PRWrapper, policyWrapper wrappers.PolicyWrapper, scansWrapper wrappers.ScansWrapper) *cobra.Command {
Expand All @@ -42,9 +45,11 @@ func NewPRDecorationCommand(prWrapper wrappers.PRWrapper, policyWrapper wrappers

prDecorationGithub := PRDecorationGithub(prWrapper, policyWrapper, scansWrapper)
prDecorationGitlab := PRDecorationGitlab(prWrapper, policyWrapper, scansWrapper)
prDecorationAzure := PRDecorationAzure(prWrapper, policyWrapper, scansWrapper)

cmd.AddCommand(prDecorationGithub)
cmd.AddCommand(prDecorationGitlab)
cmd.AddCommand(prDecorationAzure)
return cmd
}

Expand Down Expand Up @@ -159,6 +164,47 @@ func PRDecorationGitlab(prWrapper wrappers.PRWrapper, policyWrapper wrappers.Pol
return prDecorationGitlab
}

func PRDecorationAzure(prWrapper wrappers.PRWrapper, policyWrapper wrappers.PolicyWrapper, scansWrapper wrappers.ScansWrapper) *cobra.Command {
prDecorationAzure := &cobra.Command{
Use: "azure",
Short: "Decorate azure PR with vulnerabilities",
Long: "Decorate azure PR with vulnerabilities",
Example: heredoc.Doc(
`
$ cx utils pr azure --scan-id <scan-id> --token <AAD> --namespace <organization> --project <project-name or project-id>
--pr-number <pr number> --code-repository-url <code-repository-url>
`,
),
Annotations: map[string]string{
"command:doc": heredoc.Doc(
`https://checkmarx.com/resource/documents/en/34965-68653-utils.html
`,
),
},
RunE: runPRDecorationAzure(prWrapper, policyWrapper, scansWrapper),
}

prDecorationAzure.Flags().String(params.ScanIDFlag, "", "Scan ID to retrieve results from")
prDecorationAzure.Flags().String(params.SCMTokenFlag, "", params.AzureTokenUsage)
prDecorationAzure.Flags().String(params.NamespaceFlag, "", fmt.Sprintf(params.NamespaceFlagUsage, "Azure"))
prDecorationAzure.Flags().String(params.AzureProjectFlag, "", fmt.Sprintf(params.AzureProjectFlagUsage))
prDecorationAzure.Flags().Int(params.PRNumberFlag, 0, params.PRNumberFlagUsage)
prDecorationAzure.Flags().String(params.CodeRepositoryFlag, "", params.CodeRepositoryFlagUsage)
prDecorationAzure.Flags().String(params.CodeRespositoryUsernameFlag, "", fmt.Sprintf(params.CodeRespositoryUsernameFlagUsage))

// Set the value for token to mask the scm token
_ = viper.BindPFlag(params.SCMTokenFlag, prDecorationAzure.Flags().Lookup(params.SCMTokenFlag))

// mark some fields as required\
_ = prDecorationAzure.MarkFlagRequired(params.ScanIDFlag)
_ = prDecorationAzure.MarkFlagRequired(params.SCMTokenFlag)
_ = prDecorationAzure.MarkFlagRequired(params.NamespaceFlag)
_ = prDecorationAzure.MarkFlagRequired(params.AzureProjectFlag)
_ = prDecorationAzure.MarkFlagRequired(params.PRNumberFlag)

return prDecorationAzure
}

func runPRDecoration(prWrapper wrappers.PRWrapper, policyWrapper wrappers.PolicyWrapper, scansWrapper wrappers.ScansWrapper) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
scanID, _ := cmd.Flags().GetString(params.ScanIDFlag)
Expand Down Expand Up @@ -226,6 +272,13 @@ func updateAPIURLForGitlabOnPrem(apiURL string) string {
return gitlabCloudURL
}

func getAzureAPIURL(apiURL string) string {
if apiURL != "" {
return apiURL
}
return azureCloudURL
}

func runPRDecorationGitlab(prWrapper wrappers.PRWrapper, policyWrapper wrappers.PolicyWrapper, scansWrapper wrappers.ScansWrapper) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
scanID, _ := cmd.Flags().GetString(params.ScanIDFlag)
Expand Down Expand Up @@ -283,6 +336,85 @@ func runPRDecorationGitlab(prWrapper wrappers.PRWrapper, policyWrapper wrappers.
}
}

func runPRDecorationAzure(prWrapper wrappers.PRWrapper, policyWrapper wrappers.PolicyWrapper, scansWrapper wrappers.ScansWrapper) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
scanID, _ := cmd.Flags().GetString(params.ScanIDFlag)
scmTokenFlag, _ := cmd.Flags().GetString(params.SCMTokenFlag)
namespaceFlag, _ := cmd.Flags().GetString(params.NamespaceFlag)
projectNameFlag, _ := cmd.Flags().GetString(params.AzureProjectFlag)
prNumberFlag, _ := cmd.Flags().GetInt(params.PRNumberFlag)
apiURL, _ := cmd.Flags().GetString(params.CodeRepositoryFlag)
codeRepositoryUserName, _ := cmd.Flags().GetString(params.CodeRespositoryUsernameFlag)

errParams := validateAzureOnPremParameters(apiURL, codeRepositoryUserName)
if errParams != nil {
return errParams
}

scanRunningOrQueued, err := IsScanRunningOrQueued(scansWrapper, scanID)

if err != nil {
return err
}

if scanRunningOrQueued {
log.Println(noPRDecorationCreated)
return nil
}

// Retrieve policies related to the scan and project to include in the PR decoration
policies, policyError := getScanViolatedPolicies(scansWrapper, policyWrapper, scanID, cmd)
if policyError != nil {
return errors.Errorf(policyErrorFormat, failedCreatingAzurePrDecoration)
}

// Build and post the pr decoration
updatedAPIURL := getAzureAPIURL(apiURL)
updatedScmToken := updateScmTokenForAzure(scmTokenFlag, codeRepositoryUserName)
azureNameSpace := createAzureNameSpace(namespaceFlag, projectNameFlag)

prModel := &wrappers.AzurePRModel{
ScanID: scanID,
ScmToken: updatedScmToken,
Namespace: azureNameSpace,
PrNumber: prNumberFlag,
Policies: policies,
APIURL: updatedAPIURL,
}
prResponse, errorModel, err := prWrapper.PostAzurePRDecoration(prModel)
if err != nil {
return err
}

if errorModel != nil {
return errors.Errorf(errorCodeFormat, failedCreatingAzurePrDecoration, errorModel.Code, errorModel.Message)
}

logger.Print(prResponse)

return nil
}
}

func validateAzureOnPremParameters(apiURL, codeRepositoryUserName string) error {
if apiURL == "" && codeRepositoryUserName != "" {
log.Println(errorAzureOnPremParams)
return errors.New(errorAzureOnPremParams)
}
return nil
}

func createAzureNameSpace(namespace, projectName string) string {
return fmt.Sprintf("%s/%s", namespace, projectName)
}

func updateScmTokenForAzure(scmTokenFlag, codeRepositoryUserName string) string {
if codeRepositoryUserName != "" {
return fmt.Sprintf("%s:%s", codeRepositoryUserName, scmTokenFlag)
}
return scmTokenFlag
}

func getScanViolatedPolicies(scansWrapper wrappers.ScansWrapper, policyWrapper wrappers.PolicyWrapper, scanID string, cmd *cobra.Command) ([]wrappers.PrPolicy, error) {
// retrieve scan model to get the projectID
scanResponseModel, errorScanModel, err := scansWrapper.GetByID(scanID)
Expand Down
56 changes: 54 additions & 2 deletions internal/commands/util/pr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,34 @@ import (
"gotest.tools/assert"
)

func TestNewPRDecorationCommandMustExist(t *testing.T) {
const (
token = "token"
)

func TestNewGithubPRDecorationCommandMustExist(t *testing.T) {
cmd := PRDecorationGithub(nil, nil, nil)
assert.Assert(t, cmd != nil, "PR decoration command must exist")

err := cmd.Execute()
assert.ErrorContains(t, err, "scan-id")
}

func TestNewMRDecorationCommandMustExist(t *testing.T) {
func TestNewGitlabMRDecorationCommandMustExist(t *testing.T) {
cmd := PRDecorationGitlab(nil, nil, nil)
assert.Assert(t, cmd != nil, "MR decoration command must exist")

err := cmd.Execute()
assert.ErrorContains(t, err, "scan-id")
}

func TestNewAzurePRDecorationCommandMustExist(t *testing.T) {
cmd := PRDecorationAzure(nil, nil, nil)
assert.Assert(t, cmd != nil, "PR decoration command must exist")

err := cmd.Execute()
assert.ErrorContains(t, err, "scan-id")
}

func TestIsScanRunning_WhenScanRunning_ShouldReturnTrue(t *testing.T) {
scansMockWrapper := &mock.ScansMockWrapper{Running: true}

Expand Down Expand Up @@ -66,3 +78,43 @@ func TestUpdateAPIURLForGitlabOnPrem_whenAPIURLIsNotSet_ShouldReturnCloudAPIURL(
cloudAPIURL := updateAPIURLForGitlabOnPrem("")
asserts.Equal(t, gitlabCloudURL, cloudAPIURL)
}

func TestGetAzureAPIURL_whenAPIURLIsSet_ShouldUpdateAPIURL(t *testing.T) {
selfHostedURL := "https://azure.example.com"
updatedAPIURL := getAzureAPIURL(selfHostedURL)
asserts.Equal(t, selfHostedURL, updatedAPIURL)
}

func TestGetAzureAPIURL_whenAPIURLIsNotSet_ShouldReturnCloudAPIURL(t *testing.T) {
cloudAPIURL := getAzureAPIURL("")
asserts.Equal(t, azureCloudURL, cloudAPIURL)
}

func TestUpdateScmTokenForAzureOnPrem_whenUserNameIsSet_ShouldUpdateToken(t *testing.T) {
username := "username"
expectedToken := username + ":" + token
updatedToken := updateScmTokenForAzure(token, username)
asserts.Equal(t, expectedToken, updatedToken)
}

func TestUpdateScmTokenForAzureOnPrem_whenUserNameNotSet_ShouldNotUpdateToken(t *testing.T) {
username := ""
expectedToken := token
updatedToken := updateScmTokenForAzure(token, username)
asserts.Equal(t, expectedToken, updatedToken)
}

func TestCreateAzureNameSpace_ShouldCreateNamespace(t *testing.T) {
azureNamespace := createAzureNameSpace("organization", "project")
asserts.Equal(t, "organization/project", azureNamespace)
}

func TestValidateAzureOnPremParameters_WhenParametersAreValid_ShouldReturnNil(t *testing.T) {
err := validateAzureOnPremParameters("https://azure.example.com", "username")
asserts.Nil(t, err)
}

func TestValidateAzureOnPremParameters_WhenParametersAreNotValid_ShouldReturnError(t *testing.T) {
err := validateAzureOnPremParameters("", "username")
asserts.NotNil(t, err)
}
1 change: 1 addition & 0 deletions internal/params/binds.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var EnvVarsBinds = []struct {
{BflPathKey, BflPathEnv, "api/bfl"},
{PRDecorationGithubPathKey, PRDecorationGithubPathEnv, "api/flow-publisher/pr/github"},
{PRDecorationGitlabPathKey, PRDecorationGitlabPathEnv, "api/flow-publisher/pr/gitlab"},
{PRDecorationAzurePathKey, PRDecorationAzurePathEnv, "api/flow-publisher/pr/azure"},
{DescriptionsPathKey, DescriptionsPathEnv, "api/queries/descriptions"},
{TenantConfigurationPathKey, TenantConfigurationPathEnv, "api/configuration/tenant"},
{UploadsPathKey, UploadsPathEnv, "api/uploads"},
Expand Down
1 change: 1 addition & 0 deletions internal/params/envs.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const (
BflPathEnv = "CX_BFL_PATH"
PRDecorationGithubPathEnv = "CX_PR_DECORATION_GITHUB_PATH"
PRDecorationGitlabPathEnv = "CX_PR_DECORATION_GITLAB_PATH"
PRDecorationAzurePathEnv = "CX_PR_DECORATION_AZURE_PATH"
SastRmPathEnv = "CX_SAST_RM_PATH"
UploadsPathEnv = "CX_UPLOADS_PATH"
TokenExpirySecondsEnv = "CX_TOKEN_EXPIRY_SECONDS"
Expand Down
24 changes: 14 additions & 10 deletions internal/params/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,16 +161,20 @@ const (
ScaFilterUsage = "SCA filter"

// PR decoration flags
NamespaceFlag = "namespace"
NamespaceFlagUsage = "%s namespace is required to post the comments"
RepoNameFlag = "repo-name"
RepoNameFlagUsage = "%s repository details"
PRNumberFlag = "pr-number"
PRNumberFlagUsage = "Pull Request number for posting notifications and comments"
PRIidFlag = "mr-iid"
PRIidFlagUsage = "Gitlab IID (internal ID) of the merge request"
PRGitlabProjectFlag = "gitlab-project-id"
PRGitlabProjectFlagUsage = "Gitlab project ID"
NamespaceFlag = "namespace"
NamespaceFlagUsage = "%s namespace is required to post the comments"
RepoNameFlag = "repo-name"
RepoNameFlagUsage = "%s repository details"
PRNumberFlag = "pr-number"
PRNumberFlagUsage = "Pull Request number for posting notifications and comments"
PRIidFlag = "mr-iid"
PRIidFlagUsage = "Gitlab IID (internal ID) of the merge request"
PRGitlabProjectFlag = "gitlab-project-id"
PRGitlabProjectFlagUsage = "Gitlab project ID"
AzureProjectFlag = "project"
AzureProjectFlagUsage = "Azure project name or project ID"
CodeRespositoryUsernameFlag = "code-repository-username"
CodeRespositoryUsernameFlagUsage = "Azure username for code repository"

// Chat (General)
ChatAPIKey = "chat-apikey"
Expand Down
1 change: 1 addition & 0 deletions internal/params/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var (
BflPathKey = strings.ToLower(BflPathEnv)
PRDecorationGithubPathKey = strings.ToLower(PRDecorationGithubPathEnv)
PRDecorationGitlabPathKey = strings.ToLower(PRDecorationGitlabPathEnv)
PRDecorationAzurePathKey = strings.ToLower(PRDecorationAzurePathEnv)
UploadsPathKey = strings.ToLower(UploadsPathEnv)
SastRmPathKey = strings.ToLower(SastRmPathEnv)
AccessKeyIDConfigKey = strings.ToLower(AccessKeyIDEnv)
Expand Down
4 changes: 4 additions & 0 deletions internal/wrappers/mock/pr-mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ func (pr *PRMockWrapper) PostPRDecoration(model *wrappers.PRModel) (
func (pr *PRMockWrapper) PostGitlabPRDecoration(model *wrappers.GitlabPRModel) (string, *wrappers.WebError, error) {
return "MR comment created successfully.", nil, nil
}

func (pr *PRMockWrapper) PostAzurePRDecoration(model *wrappers.AzurePRModel) (string, *wrappers.WebError, error) {
return "PR comment created successfully.", nil, nil
}
Loading
Loading