diff --git a/.github/workflows/veracode-check-run.yml b/.github/workflows/veracode-check-run.yml index 7f327b473..52841f493 100644 --- a/.github/workflows/veracode-check-run.yml +++ b/.github/workflows/veracode-check-run.yml @@ -1,6 +1,10 @@ name: 'Organization workflow action' on: workflow_call: + outputs: + run_id: + description: "Unique check run id" + value: ${{ jobs.create_check_run.outputs.run_id }} inputs: run_id: description: 'ID of workflow run (provided via GitHub syntax `github.run_id`)' @@ -33,6 +37,8 @@ on: jobs: create_check_run: runs-on: ubuntu-latest + outputs: + run_id: ${{ fromJson(steps.create_check_run.outputs.data).id }} steps: # Create check run - name: GitHub API Request to create a check @@ -57,9 +63,11 @@ jobs: "repository_name": "${{ inputs.repositroy_name }}", "check_run_id": ${{ fromJson(steps.create_check_run.outputs.data).id }} }' > workflow-metadata.json + echo "run_id=${{ fromJson(steps.create_check_run.outputs.data).id }}" >> "GITHUB_OUTPUT" shell: bash + - name: Save metadata - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4.3.0 with: name: workflow-metadata path: workflow-metadata.json \ No newline at end of file diff --git a/.github/workflows/veracode-code-analysis.yml b/.github/workflows/veracode-code-analysis.yml index 8408a50b5..74b5c58ee 100644 --- a/.github/workflows/veracode-code-analysis.yml +++ b/.github/workflows/veracode-code-analysis.yml @@ -16,7 +16,56 @@ on: go-policy-scan] jobs: + find_veracode_policy_name: + name: Validate Policy Name + runs-on: ubuntu-latest + outputs: + total_elements: ${{ steps.generate_header.outputs.total_elements }} + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Encode policy name + id: encode_step + run: | + ENCODED_NAME=$(echo -n "${{ github.event.client_payload.policy_name }}" | jq -s -R -r @uri) + echo "Encoded name: $ENCODED_NAME" + echo "ENCODED_NAME=$ENCODED_NAME" >> $GITHUB_ENV + + - name: Call API to validate policy name + id: generate_header + run: | + ID='${{ secrets.VERACODE_API_ID }}' + KEY='${{ secrets.VERACODE_API_KEY }}' + host='api.veracode.com' + method='GET' + case $ID in + vera01ei-*) + host='api.veracode.eu' + ID="${ID/vera01ei-/}" # One-liner for cleaning up ID + ID="${ID:-''}" # Set to '' if the result is empty + KEY="${KEY/vera01es-/}" # One-liner for cleaning up KEY + KEY="${KEY:-''}" # Set to '' if the result is empty + echo 'Region: EU' + ;; + *) + echo 'Region: US' + ;; + esac + + url="https://$host/appsec/v1/policies?name=$ENCODED_NAME" + RESULT=$(node -e "require('./veracode-helper/generate_signature').calculateVeracodeAuthHeader('$url', '$method', '$host', '$ID', '$KEY')") + echo "result:" $RESULT + api_response=$(curl -X GET "$url" -H "Host: $host" -H "Authorization: $RESULT") + echo "API Response: $api_response" + echo "{apiResponse}={api_response}" >> GITHUB_OUTPUT + total_elements=$(echo "$api_response" | jq -r '.page.total_elements') + echo "total_elements=$total_elements" >> "$GITHUB_OUTPUT" + echo "mesage:${{ github.event.client_payload.annotationObj.message }}" + echo "break: ${{ github.event.client_payload.break_build_policy_findings }}" + register: + needs: find_veracode_policy_name uses: ./.github/workflows/veracode-check-run.yml with: check_run_name: ${{ github.workflow }} @@ -25,10 +74,36 @@ jobs: repositroy_name: ${{ github.event.client_payload.repository.name }} event_type: ${{ github.event.client_payload.event_type }} github_token: ${{ github.event.client_payload.token }} - run_id: ${{ github.run_id }} + run_id: ${{ github.run_id }} + + update_check: + needs: register + if: ${{ needs.find_veracode_policy_name.outputs.total_elements == 0 }} + uses: ./.github/workflows/veracode-update-check-run.yml + with: + check_run_name: ${{ github.workflow }} + head_sha: ${{ github.event.client_payload.sha }} + repository_owner: ${{ github.event.client_payload.repository.owner }} + repository_name: ${{ github.event.client_payload.repository.name }} + event_type: ${{ github.event.client_payload.event_type }} + github_token: ${{ github.event.client_payload.token }} + run_id: ${{ needs.register.outputs.run_id }} + show_annotation: ${{ needs.find_veracode_policy_name.outputs.total_elements == 0 }} + message: ${{ github.event.client_payload.annotationObj.message }} + annotation_level: ${{ github.event.client_payload.annotationObj.annotation_level }} + break_build_policy_findings: ${{github.event.client_payload.break_build_policy_findings }} + conclusion: ${{ github.event.client_payload.annotationObj.conclusion }} + status: ${{ github.event.client_payload.annotationObj.status }} + title: ${{ github.event.client_payload.annotationObj.title }} + summary: ${{ github.event.client_payload.annotationObj.summary }} + start_line: ${{ github.event.client_payload.annotationObj.start_line }} + end_line: ${{ github.event.client_payload.annotationObj.end_line }} + path: ${{ github.event.client_payload.annotationObj.path }} + build: needs: register + if: ${{ !(needs.find_veracode_policy_name.outputs.total_elements == 0 && github.event.client_payload.break_build_policy_findings == true) }} uses: ./.github/workflows/veracode-build-artifact-for-scanning.yml with: repository: ${{ github.event.client_payload.repository.full_name }} diff --git a/.github/workflows/veracode-update-check-run.yml b/.github/workflows/veracode-update-check-run.yml new file mode 100644 index 000000000..050f668cb --- /dev/null +++ b/.github/workflows/veracode-update-check-run.yml @@ -0,0 +1,118 @@ +name: Veracode Check Run Updation +on: + workflow_call: + inputs: + run_id: + description: 'ID of workflow run (provided via GitHub syntax `github.run_id`)' + required: true + type: string + repository_owner: + description: 'repository_owner of original commit (provided by GitHub app via `github.event.client_payload.repository.owner`)' + required: true + type: string + repository_name: + description: 'repository_name of original commit (provided by GitHub app via `github.event.client_payload.repository.name`)' + required: true + type: string + check_run_name: + description: 'Name of check (Use `github.workflow` to use the name of the workflow)' + required: true + type: string + head_sha: + description: 'head_sha of original commit (provided by GitHub app via `github.event.client_payload.sha`)' + required: true + type: string + github_token: + description: 'github_token is a token (provided by GitHub app via `github.event.client_payload.token`)' + required: true + type: string + event_type: + description: 'event_type triggered by the GitHub App (provided by GitHub app via `github.event.client_payload.event_type`)' + required: true + type: string + show_annotation: + description: 'Boolean flag to indicate if the annotation has to be displayed' + required: false + type: boolean + default: false + message: + description: 'Annotation message that needs to be displayed' + required: false + type: string + conclusion: + description: 'Workflow conclusion' + required: false + type: string + status: + description: 'Workflow status' + required: false + type: string + title: + description: 'title of Annotation' + required: false + type: string + annotation_level: + description: 'Annotation level is whether warning or failure' + required: false + type: string + start_line: + description: 'Start line of the Policy' + required: false + type: string + end_line: + description: 'End line of the Policy' + required: false + type: string + summary: + description: 'Summary of the Annotation' + required: false + type: string + path: + description: 'Path for Configuration file' + required: false + type: string + break_build_policy_findings: + description: 'Breaking the build' + required: false + type: string +jobs: + update_check_run: + name: Update Check Run Job + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Update Check + uses: octokit/request-action@v2.x + id: update_check_run + env: + GITHUB_TOKEN: ${{ inputs.github_token }} + with: + route: PATCH /repos/${{ inputs.repository_owner }}/${{ inputs.repository_name }}/check-runs/${{ inputs.run_id }} + name: "Veracode Policy Name Validation" + conclusion: ${{ inputs.conclusion }} + status: ${{ inputs.status }} + output: | + title: ${{ inputs.title }} + summary: ${{ inputs.summary }} + annotations: + - path: ${{ inputs.path }} + title: ${{ inputs.title }} + annotation_level: ${{ inputs.annotation_level }} + message: ${{ inputs.message }} + start_line: ${{ inputs.start_line }} + end_line: ${{ inputs.end_line }} + + - name: Show annotations + if: ${{ inputs.show_annotation == true && inputs.break_build_policy_findings == 'false' }} + run: | + echo "::error::${{ inputs.message }}" + + + - name: Fail the Execution + if: ${{ inputs.break_build_policy_findings == 'true' }} + uses: actions/github-script@v7.0.1 + with: + script: | + core.setFailed( "${{ inputs.message }}") diff --git a/veracode-helper/generate_signature.js b/veracode-helper/generate_signature.js new file mode 100644 index 000000000..de030fe8a --- /dev/null +++ b/veracode-helper/generate_signature.js @@ -0,0 +1,55 @@ +const crypto = require('crypto'); +var url = require('url'); + +const authorizationScheme = 'VERACODE-HMAC-SHA-256'; +const requestVersion = "vcode_request_version_1"; +const nonceSize = 16; + +function computeHashHex(message, key_hex) { + // return CryptoJS.HmacSHA256(message, CryptoJS.enc.Hex.parse(key_hex)).toString(CryptoJS.enc.Hex); + hmac1 = crypto.createHmac('sha256', Buffer.from(key_hex,'hex')); + hmac1.update(message); + + // Return the digest in hexadecimal format + return hmac1.digest('hex'); +} + +function calculateDataSignature(apikey, nonceBytes, dateStamp, data) { + let kNonce = computeHashHex(nonceBytes, apikey); + let kDate = computeHashHex(dateStamp, kNonce); + let kSig = computeHashHex(requestVersion, kDate); + return computeHashHex(data, kSig); +} + +function newNonce() { + // return CryptoJS.lib.WordArray.random(nonceSize).toString().toUpperCase(); + return crypto.randomBytes(nonceSize).toString('hex').toUpperCase() +} + +function toHexBinary(input) { + // return CryptoJS.enc.Hex.stringify(CryptoJS.enc.Utf8.parse(input)); + return Buffer.from(input,'utf-8').toString('hex'); +} + +function removePrefixFromApiCredential(input) { + return input.split('-').at(-1); +} + +function calculateVeracodeAuthHeader(requestUrl, httpMethod, host, id, key) { + const formattedId = removePrefixFromApiCredential(id); + const formattedKey = removePrefixFromApiCredential(key); + + let parsedUrl = url.parse(requestUrl); + let data = `id=${formattedId}&host=${host}&url=${parsedUrl.path}&method=${httpMethod}`; + let dateStamp = Date.now().toString(); + let nonceBytes = newNonce(); + let dataSignature = calculateDataSignature(formattedKey, nonceBytes, dateStamp, data); + let authorizationParam = `id=${formattedId},ts=${dateStamp},nonce=${toHexBinary(nonceBytes)},sig=${dataSignature}`; + let hmac = authorizationScheme + " " + authorizationParam; + console.log(hmac) +} + + +module.exports = { + calculateVeracodeAuthHeader: calculateVeracodeAuthHeader +}; \ No newline at end of file