OSS_pygoat-devsecops-basic #7
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: OSS_pygoat-devsecops-basic | |
on: | |
# push: | |
# branches: | |
# - main | |
workflow_dispatch: | |
permissions: | |
id-token: write | |
contents: read | |
env: | |
#DEFECTDOJO_PRODUCTID: 4 | |
# basic settings | |
useCommonDefectDojoProduct: true # for DEMO purposes | |
pushDockerImage: false # for DEMO purposes | |
continue-on-error: true # for DEMO purposes | |
# # advanced settings | |
# useCommonDefectDojoProduct: false # advanced setting | |
# pushDockerImage: true # advanced setting | |
# continue-on-error: false # advanced setting | |
useAzureCliForK8s: false | |
doImageScanWithDockerHubScout: false | |
adjustDNS: false | |
listArtifacts: true | |
DEFECTDOJO_URL: https://defectdojo-002.cad4devops.com:8443/api/v2 | |
DEFECTDOJO_COMMONUSER: Student000 | |
DEFECTDOJO_COMMONPRODUCTNAME: GitHub-OSS-pygoat-devsecops-workshop-001-product-000 | |
DEFECTDOJO_TOKEN: ${{ secrets.DEFECTDOJO_TOKEN }} | |
appUrl: http://gh-pygoat-test.cad4devops.com | |
containerName: owaspzapproxy | |
doActiveScan: false | |
dockerWaitTime: 30s | |
tag: "${{ github.run_id }}" | |
host: gh-pygoat-test.cad4devops.com | |
kubernetesServiceConnection: OSS_Microk8s | |
loopWaitTime: 10s | |
maxAlerts: 5000 | |
owaspImageName: softwaresecurityproject/zap-stable | |
owaspZapApiPort: 8090 | |
owaspZapWebPort: 8080 | |
owaspzap-report-json: owasp-zap.json | |
owaspzap-report-sarif: owasp-zap.sarif | |
sarifTemplate: sarif-json | |
traditionalJsonTemplate: traditional-json | |
jobs: | |
build-build_and_push_app: | |
name: Build and Push App | |
runs-on: ubuntu-latest | |
env: | |
image: ${{ vars.DOCKER_USERNAME }}.azurecr.io/devsecops-pygoat | |
environment: | |
name: dev | |
steps: | |
- name: Fixes docker image name if not being pushed to Azure Container Registry | |
run: |- | |
echo "image=devsecops-pygoat" >> $GITHUB_ENV | |
if: vars.DOCKER_USERNAME == '' | |
- name: test action get auth token | |
if: env.useCommonDefectDojoProduct == 'true' | |
run: |- | |
echo "testing action" | |
# get token from common user | |
echo "DEFECTDOJO_COMMONUSER: ${{ env.DEFECTDOJO_COMMONUSER }}" | |
echo "DEFECTDOJO_COMMONPASSWORD: ${{ secrets.DEFECTDOJO_COMMONPASSWORD }}" | |
# throw error if token is not set | |
if [ -z "${{ secrets.DEFECTDOJO_COMMONPASSWORD }}" ]; then | |
echo "Error: DEFECTDOJO_COMMONPASSWORD is not set. Please set the secret in the dev environment." | |
exit 1 | |
fi | |
DEFECTDOJO_TOKEN_COMMON=`curl --fail --location --request POST "${{ env.DEFECTDOJO_URL }}/api-token-auth/" \ | |
--header 'Content-Type: application/json' \ | |
--data-raw "{ | |
\"username\": \"${{ env.DEFECTDOJO_COMMONUSER }}\", | |
\"password\": \"${{ secrets.DEFECTDOJO_COMMONPASSWORD }}\" | |
}" | jq -r '.token'` | |
if [ -z "${DEFECTDOJO_TOKEN_COMMON}" ]; then | |
echo "Error: Failed to get token from common user ${{ env.DEFECTDOJO_COMMONUSER }}. Please check the credentials." | |
exit 1 | |
else | |
#echo "DEFECTDOJO_TOKEN: $DEFECTDOJO_TOKEN_COMMON" | |
echo "DEFECTDOJO_TOKEN is set" | |
echo "DEFECTDOJO_TOKEN=$DEFECTDOJO_TOKEN_COMMON" >> $GITHUB_ENV | |
exit 0 | |
fi | |
- name: test action get product id | |
if: env.useCommonDefectDojoProduct == 'true' | |
run: |- | |
echo "testing action" | |
# now get the product id | |
echo "DEFECTDOJO_COMMONPRODUCTNAME: ${{ env.DEFECTDOJO_COMMONPRODUCTNAME }}" | |
DEFECTDOJO_PRODUCTID_COMMON=`curl --fail --location --request GET "${{ env.DEFECTDOJO_URL }}/products/?name=${{ env.DEFECTDOJO_COMMONPRODUCTNAME }}" \ | |
--header "Authorization: Token ${{ env.DEFECTDOJO_TOKEN }}" | jq -r '.results[0].id'` | |
if [ -z "${DEFECTDOJO_PRODUCTID_COMMON}" ]; then | |
echo "Error: Failed to get product id for ${{ env.DEFECTDOJO_COMMONPRODUCTNAME }}. Please check the product name." | |
exit 1 | |
else | |
echo "DEFECTDOJO_PRODUCTID: $DEFECTDOJO_PRODUCTID_COMMON" | |
echo "DEFECTDOJO_PRODUCTID is set" | |
echo "DEFECTDOJO_PRODUCTID=$DEFECTDOJO_PRODUCTID_COMMON" >> $GITHUB_ENV | |
exit 0 | |
fi | |
- name: test env settings | |
if: env.useCommonDefectDojoProduct == 'true' | |
run: |- | |
#echo $DEFECTDOJO_TOKEN | |
#echo ${{ env.DEFECTDOJO_TOKEN }} | |
echo $DEFECTDOJO_PRODUCTID | |
echo ${{ env.DEFECTDOJO_PRODUCTID }} | |
- name: checkout | |
uses: actions/[email protected] | |
- name: Setup Python | |
uses: actions/[email protected] | |
with: | |
python-version: "3.8" | |
architecture: x64 | |
- name: Install Dependencies | |
run: pip install -r requirements.txt | |
- name: Check some variables | |
run: |- | |
echo "DOCKER_REGISTRY: ${{ vars.DOCKER_USERNAME }}.azurecr.io, DOCKER_USERNAME: ${{ vars.DOCKER_USERNAME }}" | |
echo "DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}" | |
- name: Build Docker image | |
run: docker build . --file "Dockerfile" -t ${{ env.image }}:${{ github.run_id }} -t ${{ env.image }}:latest | |
- name: Docker Login | |
if: env.pushDockerImage == 'true' | |
uses: docker/[email protected] | |
with: | |
registry: "${{ vars.DOCKER_USERNAME }}.azurecr.io" | |
username: "${{ vars.DOCKER_USERNAME }}" | |
password: "${{ secrets.DOCKER_PASSWORD }}" | |
- name: Push Docker image ${{ env.image }}:${{ github.run_id }} | |
if: env.pushDockerImage == 'true' | |
run: docker push ${{ env.image }}:${{ github.run_id }} | |
- name: Push Docker image ${{ env.image }}:latest | |
if: env.pushDockerImage == 'true' | |
run: docker push ${{ env.image }}:latest | |
test-run_devopsshield_scan_linux: | |
name: Run DevOps Shield Scan Linux | |
needs: | |
- build-build_and_push_app | |
runs-on: ubuntu-latest | |
environment: | |
name: dev | |
steps: | |
- name: checkout | |
uses: actions/[email protected] | |
- name: DevOps Shield Security Scanner Linux | |
run: |- | |
# Run DevOps Shield CLI with dosType=GitHub for current repository | |
echo "Current Repo: ${{ github.repository }}" | |
echo "Current Org: ${{ github.repository_owner }}" | |
echo "GitHub Token: ${{ secrets.TOKEN_FOR_DOS }}" | |
# throw error if token is not set | |
if [ -z "${{ secrets.TOKEN_FOR_DOS }}" ]; then | |
echo "Error: TOKEN_FOR_DOS is not set. Please set the secret in the dev environment. You need to create a personal access token (classic). In general, you only need read permissions." | |
exit 1 | |
fi | |
# check os linux or windows | |
echo "Linux OS" | |
docker run --name devopsshield \ | |
-v "${{ github.workspace }}:/devopsshield" \ | |
--rm -t \ | |
-e dosOrganizationName=${{ github.repository_owner }} \ | |
-e dosPatToken=${{ secrets.TOKEN_FOR_DOS }} \ | |
-e dosType=GitHub \ | |
devopsshield/devopsshield | |
ls ${{ github.workspace }} | |
cat ${{ github.workspace }}/DevOpsShield-SecurityScanner-Report.sarif | |
cat ${{ github.workspace }}/DevOpsShield-SecurityScanner-Report.csv | |
mkdir "${{ github.workspace }}/devops-shield-reports-linux" | |
cp ${{ github.workspace }}/DevOpsShield-SecurityScanner-Report.sarif "${{ github.workspace }}/devops-shield-reports-linux" | |
cp ${{ github.workspace }}/DevOpsShield-SecurityScanner-Report.csv "${{ github.workspace }}/devops-shield-reports-linux" | |
shell: bash | |
- name: Fix DOS SARIF (temporary workaround) | |
run: |- | |
Write-Host "Fixing DOS Sarif" | |
# read sarif and fix it | |
$SarifFile = "${{ github.workspace }}/devops-shield-reports-linux/DevOpsShield-SecurityScanner-Report.sarif" | |
$SarifFileFixed = "${{ github.workspace }}/devops-shield-reports-linux/DevOpsShield-SecurityScanner-Report-Fixed.sarif" | |
$sarifObject = Get-Content -Path $SarifFile | ConvertFrom-Json -Depth 100 | |
foreach ($run in $sarifObject.runs) { | |
foreach ($result in $run.results) { | |
# echo ruleId | |
Write-Host $result.ruleId | |
# loop through fixes | |
foreach ($fix in $result.fixes) { | |
# echo fix | |
Write-Host $fix | |
# echo fix description | |
$description = $fix.description | |
# echo description id | |
$descriptionId = $description.id | |
Write-Host `t`t$descriptionId | |
# echo description arguments | |
$descriptionArguments = $description.arguments | |
foreach ($argument in $descriptionArguments) { | |
Write-Host `t`t`t$argument | |
} | |
# check if there are more than one argument | |
if ($descriptionArguments.Count -gt 1) { | |
# throw error | |
Write-Error "Error: More than one argument found" | |
exit 1 | |
} | |
# prepare new description text | |
$newText = "$descriptionId - $($descriptionArguments[0])" | |
$fix.description = $newText | |
# echo new fix description | |
Write-Host `t`t$newText | |
# change description object | |
# create new custom object for description with a single text property | |
$newDescription = [PSCustomObject]@{ text = $newText } | |
# replace description object with new object | |
$fix.description = $newDescription | |
} | |
} | |
} | |
$sarifObject | ConvertTo-Json -Depth 100 | Set-Content -Path $SarifFileFixed | |
shell: pwsh | |
- name: Upload DevOps Shield Reports | |
uses: actions/[email protected] | |
with: | |
name: devops-shield-reports-linux | |
path: "${{ github.workspace }}/devops-shield-reports-linux" | |
test-run_sca_analysis: | |
name: Run SCA Analysis | |
needs: | |
- build-build_and_push_app | |
runs-on: ubuntu-latest | |
env: | |
image: ${{ vars.DOCKER_USERNAME }}.azurecr.io/devsecops-pygoat | |
environment: | |
name: dev | |
steps: | |
- name: checkout | |
uses: actions/[email protected] | |
- name: Safety Dependency Check | |
run: |- | |
pip install safety | |
mkdir -p ${{ github.workspace }}/dependency-check-reports | |
safety check -r requirements.txt --continue-on-error --output json > ${{ github.workspace }}/dependency-check-reports/dependency-check-report-safety-check.json | |
- name: Pip Audit Dependency Check | |
run: |- | |
pip install pip-audit | |
mkdir -p ${{ github.workspace }}/dependency-check-reports | |
# suppress the error code to continue the pipeline even if there are vulnerabilities | |
pip-audit -r requirements.txt --format json --output ${{ github.workspace }}/dependency-check-reports/dependency-check-report-pip-audit.json || true | |
#cat ${{ github.workspace }}/dependency-check-reports/dependency-check-report-pip-audit.json | |
- uses: actions/[email protected] | |
with: | |
name: dependency-check-reports | |
path: "${{ github.workspace }}/dependency-check-reports" | |
- name: Docker Login | |
if: env.doImageScanWithDockerHubScout == 'true' | |
uses: docker/[email protected] | |
with: | |
registry: "${{ vars.DOCKER_USERNAME }}.azurecr.io" | |
username: "${{ vars.DOCKER_USERNAME }}" | |
password: "${{ secrets.DOCKER_PASSWORD }}" | |
- name: Image Scanning | |
if: env.doImageScanWithDockerHubScout == 'true' | |
run: |- | |
# Install the Docker Scout CLI | |
curl -sSfL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh | sh -s -- | |
# Login to Docker Hub required for Docker Scout CLI | |
#docker login -u ${{ vars.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} | |
# Get a CVE report for the built image and fail the pipeline when critical or high CVEs are detected | |
docker scout cves ${{ env.image }}:${{ env.tag }} --only-severity critical,high --format sarif --output ${{ github.workspace }}/image-scan-report.json | |
- name: Publish Image Scan Report | |
if: success() || failure() | |
uses: actions/[email protected] | |
with: | |
name: image-scan-report | |
path: "${{ github.workspace }}/image-scan-report.json" | |
test-run_unit_tests: | |
name: Run Unit Tests | |
needs: | |
- build-build_and_push_app | |
runs-on: ubuntu-latest | |
steps: | |
- name: checkout | |
uses: actions/[email protected] | |
- name: Setup Python | |
uses: actions/[email protected] | |
with: | |
python-version: "3.8" | |
architecture: x64 | |
- name: Install Dependencies | |
run: pip install -r requirements.txt | |
- name: UnitTests with PyTest | |
run: |- | |
python -m pip install pytest-azurepipelines pytest-cov | |
python -m pytest introduction/tests/unit/ --junitxml=${{ github.workspace }}/TEST-output.xml --cov=. --cov-report=xml | |
- name: Publish UnitTest Report | |
if: success() || failure() | |
uses: actions/[email protected] | |
with: | |
name: unit-test-results | |
path: "${{ github.workspace }}/TEST-output.xml" | |
test-run_sast_analysis: | |
name: Run SAST Analysis | |
needs: | |
- build-build_and_push_app | |
runs-on: ubuntu-latest | |
steps: | |
- name: checkout | |
uses: actions/[email protected] | |
- name: Bandit Scan | |
run: |- | |
pip3 install --upgrade pip | |
pip3 install --upgrade setuptools | |
pip3 install bandit | |
bandit -ll -ii -r ./introduction -f json -o ${{ github.workspace }}/sast-report.json --exit-zero | |
- name: Publish SAST Scan Report | |
if: success() || failure() | |
uses: actions/[email protected] | |
with: | |
name: bandit-sast-report | |
path: "${{ github.workspace }}/sast-report.json" | |
test-upload_reports: | |
name: Upload Reports | |
needs: | |
- test-run_devopsshield_scan_linux | |
- test-run_sast_analysis | |
- test-run_unit_tests | |
- test-run_sca_analysis | |
- build-build_and_push_app | |
runs-on: ubuntu-latest | |
environment: | |
name: dev | |
env: | |
DEFECTDOJO_PRODUCTID: ${{ vars.DEFECTDOJO_PRODUCTID }} | |
DEFECTDOJO_TOKEN: ${{ secrets.DEFECTDOJO_TOKEN }} | |
DEFECTDOJO_COMMONPASSWORD: ${{ secrets.DEFECTDOJO_COMMONPASSWORD }} | |
DEFECTDOJO_ENGAGEMENT_REASON: "CI/CD Pipeline Scan" | |
DEFECTDOJO_ANCHORE_DISABLE: "false" | |
DEFECTDOJO_ENGAGEMENT_API_TEST: "true" | |
DEFECTDOJO_ENGAGEMENT_BUILD_SERVER: "null" | |
DEFECTDOJO_ENGAGEMENT_CHECK_LIST: "true" | |
DEFECTDOJO_ENGAGEMENT_DEDUPLICATION_ON_ENGAGEMENT: "true" | |
DEFECTDOJO_ENGAGEMENT_ORCHESTRATION_ENGINE: "null" | |
DEFECTDOJO_ENGAGEMENT_PEN_TEST: "true" | |
DEFECTDOJO_ENGAGEMENT_PERIOD: 7 | |
DEFECTDOJO_ENGAGEMENT_SOURCE_CODE_MANAGEMENT_SERVER: "null" | |
DEFECTDOJO_ENGAGEMENT_STATUS: Not Started | |
DEFECTDOJO_ENGAGEMENT_THREAT_MODEL: "true" | |
DEFECTDOJO_NOT_ON_MASTER: "false" | |
DEFECTDOJO_SCAN_ACTIVE: "true" | |
DEFECTDOJO_SCAN_CLOSE_OLD_FINDINGS: "true" | |
DEFECTDOJO_SCAN_ENVIRONMENT: Default | |
DEFECTDOJO_SCAN_MINIMUM_SEVERITY: Info | |
DEFECTDOJO_SCAN_PUSH_TO_JIRA: "false" | |
DEFECTDOJO_SCAN_TEST_TYPE: SAST and SCA Scan | |
DEFECTDOJO_SCAN_VERIFIED: "true" | |
steps: | |
- name: checkout | |
uses: actions/[email protected] | |
- name: Download Reports From Pipeline Artifacts | |
uses: actions/[email protected] | |
with: | |
path: "${{ github.workspace }}" | |
- name: check if secrets are set | |
if: false | |
run: |- | |
echo "DEFECTDOJO_URL: ${{ env.DEFECTDOJO_URL }}" | |
echo "DEFECTDOJO_PRODUCTID: ${{ vars.DEFECTDOJO_PRODUCTID }}" | |
echo "DEFECTDOJO_TOKEN: ${{ secrets.DEFECTDOJO_TOKEN }}" | |
echo "DEFECTDOJO_COMMONUSER: ${{ env.DEFECTDOJO_COMMONUSER }}" | |
echo "DEFECTDOJO_COMMONPASSWORD: ${{ secrets.DEFECTDOJO_COMMONPASSWORD }}" | |
# throw error if token is not set and common password is not set | |
if [ -z "${{ secrets.DEFEECTDOJO_TOKEN }}" ]; then | |
echo "Warning: DEFEECTDOJO_TOKEN is not set. Please set the secret in the dev environment." | |
echo "Checking for DEFECTDOJO_COMMONPASSWORD" | |
if [ -z "${{ secrets.DEFEECTDOJO_COMMONPASSWORD }}" ]; then | |
echo "Error: DEFECTDOJO_TOKEN is not set. Please set the secret in the dev environment." | |
echo "Error: DEFECTDOJO_COMMONPASSWORD is not set. Please set the secret in the dev environment." | |
echo "You need to set either DEFECTDOJO_TOKEN or DEFECTDOJO_COMMONPASSWORD to run the pipeline." | |
exit 1 | |
else | |
echo "DEFECTDOJO_COMMONPASSWORD is set" | |
echo "Trying to get token from common user ${{ env.DEFECTDOJO_COMMONUSER }}..." | |
# get token from common user | |
DEFECTDOJO_TOKEN=`curl --fail --location --request POST "${{ env.DEFECTDOJO_URL }}/api-token-auth/" \ | |
--header 'Content-Type: application/json' \ | |
--data-raw "{ | |
\"username\": \"${{ env.DEFECTDOJO_COMMONUSER }}\", | |
\"password\": \"${{ secrets.DEFECTDOJO_COMMONPASSWORD }}\" | |
}" | jq -r '.token'` | |
if [ -z "${DEFECTDOJO_TOKEN}" ]; then | |
echo "Error: Failed to get token from common user ${{ env.DEFECTDOJO_COMMONUSER }}. Please check the credentials." | |
exit 1 | |
else | |
echo "DEFECTDOJO_TOKEN: $DEFECTDOJO_TOKEN" | |
echo "DEFECTDOJO_TOKEN is set" | |
exit 0 | |
fi | |
exit 0 | |
fi | |
else | |
echo "DEFECTDOJO_TOKEN is set" | |
exit 0 | |
fi | |
- name: check if secrets are set | |
if: env.useCommonDefectDojoProduct == 'true' | |
shell: pwsh | |
run: |- | |
echo "DEFECTDOJO_URL: $env:DEFECTDOJO_URL" | |
echo "DEFECTDOJO_PRODUCTID: $env:DEFECTDOJO_PRODUCTID" | |
echo "DEFECTDOJO_TOKEN: $env:DEFECTDOJO_TOKEN" | |
echo "DEFECTDOJO_COMMONUSER: $env:DEFECTDOJO_COMMONUSER" | |
echo "DEFECTDOJO_COMMONPASSWORD: $env:DEFECTDOJO_COMMONPASSWORD" | |
# throw error if token is not set and common password is not set | |
$defectDojoTokenIsSet = $null -ne $env:DEFECTDOJO_TOKEN -and $env:DEFECTDOJO_TOKEN -ne "" | |
Write-Host "DefectDojo Token is set: $defectDojoTokenIsSet" | |
$defectDojoCommonPasswordIsSet = $null -ne $env:DEFECTDOJO_COMMONPASSWORD -and $env:DEFECTDOJO_COMMONPASSWORD -ne "" | |
Write-Host "DefectDojo Common Password is set: $defectDojoCommonPasswordIsSet" | |
if ($defectDojoTokenIsSet) { | |
Write-Host "DEFECTDOJO_TOKEN is set" | |
exit 0 | |
} | |
else { | |
Write-Host "Warning: DEFEECTDOJO_TOKEN is not set. Please set the secret in the dev environment." | |
Write-Host "checking for DEFECTDOJO_COMMONPASSWORD" | |
if ($defectDojoCommonPasswordIsSet) { | |
Write-Host "DEFECTDOJO_COMMONPASSWORD is set" | |
Write-Host "Trying to get token from common user ${{ env.DEFECTDOJO_COMMONUSER }}..." | |
# get token from common user | |
$DEFECTDOJO_TOKEN_COMMON = (Invoke-RestMethod -Method Post -Uri "${env:DEFECTDOJO_URL}/api-token-auth/" ` | |
-ContentType 'application/json' ` | |
-Body (@{ | |
"username" = "$env:DEFECTDOJO_COMMONUSER"; | |
"password" = "$env:DEFECTDOJO_COMMONPASSWORD" | |
} | ConvertTo-Json)).token | |
if ($DEFECTDOJO_TOKEN_COMMON) { | |
#Write-Host "DEFECTDOJO_TOKEN: $DEFECTDOJO_TOKEN_COMMON" | |
Write-Host "DEFECTDOJO_TOKEN is set" | |
#echo "DEFECTDOJO_TOKEN=$DEFECTDOJO_TOKEN_COMMON" >> $GITHUB_ENV | |
echo "DEFECTDOJO_TOKEN=$DEFECTDOJO_TOKEN_COMMON" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append | |
# now to fetch the product id | |
# Get the product id | |
$DEFECTDOJO_PRODUCTID_COMMON = Invoke-RestMethod -Method Get -Uri "${env:DEFECTDOJO_URL}/products/?name=${env:DEFECTDOJO_COMMONPRODUCTNAME}" ` | |
-Headers @{ "Authorization" = "Token $DEFECTDOJO_TOKEN_COMMON" } | |
Write-Host "DEFECTDOJO_PRODUCTID_COMMON: $DEFECTDOJO_PRODUCTID_COMMON" | |
if ($DEFECTDOJO_PRODUCTID_COMMON) { | |
$DEFECTDOJO_PRODUCTID_COMMON_ID = $DEFECTDOJO_PRODUCTID_COMMON.results[0].id | |
Write-Host "DEFECTDOJO_PRODUCTID: $DEFECTDOJO_PRODUCTID_COMMON_ID" | |
Write-Host "DEFECTDOJO_PRODUCTID is set" | |
#echo "DEFECTDOJO_PRODUCTID=$DEFECTDOJO_PRODUCTID_COMMON_ID" >> $GITHUB_ENV | |
echo "DEFECTDOJO_PRODUCTID=$DEFECTDOJO_PRODUCTID_COMMON_ID" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append | |
exit 0 | |
} | |
else { | |
Write-Host "Error: Failed to get product id for $DEFECTDOJO_COMMONPRODUCTNAME. Please check the product name." | |
exit 1 | |
} | |
} | |
else { | |
Write-Host "Error: Failed to get token from common user ${env:DEFECTDOJO_COMMONUSER}. Please check the credentials." | |
exit 1 | |
} | |
} | |
else { | |
Write-Host "Error: DEFECTDOJO_TOKEN is not set. Please set the secret in the dev environment." | |
Write-Host "Error: DEFECTDOJO_COMMONPASSWORD is not set. Please set the secret in the dev environment." | |
Write-Host "You need to set either DEFECTDOJO_TOKEN or DEFECTDOJO_COMMONPASSWORD to run the pipeline." | |
exit 1 | |
} | |
} | |
- name: test env settings | |
if: env.useCommonDefectDojoProduct == 'true' | |
shell: pwsh | |
run: |- | |
#echo $env:DEFECTDOJO_TOKEN | |
echo $env:DEFECTDOJO_PRODUCTID | |
- name: Create DefectDojo Engagement with Common Product | |
if: env.useCommonDefectDojoProduct == 'true' | |
run: |- | |
TODAY=`date +%Y-%m-%d` | |
ENDDAY=$(date -d "+${{ env.DEFECTDOJO_ENGAGEMENT_PERIOD }} days" +%Y-%m-%d) | |
ENGAGEMENTID=`curl --fail --location --request POST "${{ env.DEFECTDOJO_URL }}/engagements/" \ | |
--header "Authorization: Token ${{ env.DEFECTDOJO_TOKEN }}" \ | |
--header 'Content-Type: application/json' \ | |
--data-raw "{ | |
\"tags\": [\"GITHUB\"], | |
\"name\": \"pygoat-${{ github.run_id }}\", | |
\"description\": \"${{ github.event.head_commit.message }}\", | |
\"version\": \"${{ github.ref }}\", | |
\"first_contacted\": \"${TODAY}\", | |
\"target_start\": \"${TODAY}\", | |
\"target_end\": \"${ENDDAY}\", | |
\"reason\": \"${{ env.DEFECTDOJO_ENGAGEMENT_REASON }}\", | |
\"tracker\": \"${{ github.server_url }}/${{ github.repository }}/\", | |
\"threat_model\": \"${{ env.DEFECTDOJO_ENGAGEMENT_THREAT_MODEL }}\", | |
\"api_test\": \"${{ env.DEFECTDOJO_ENGAGEMENT_API_TEST }}\", | |
\"pen_test\": \"${{ env.DEFECTDOJO_ENGAGEMENT_PEN_TEST }}\", | |
\"check_list\": \"${{ env.DEFECTDOJO_ENGAGEMENT_CHECK_LIST }}\", | |
\"status\": \"${{ env.DEFECTDOJO_ENGAGEMENT_STATUS }}\", | |
\"engagement_type\": \"CI/CD\", | |
\"build_id\": \"${{ github.run_id }}\", | |
\"commit_hash\": \"${{ github.sha }}\", | |
\"branch_tag\": \"${{ github.ref }}\", | |
\"deduplication_on_engagement\": \"${{ env.DEFECTDOJO_ENGAGEMENT_DEDUPLICATION_ON_ENGAGEMENT }}\", | |
\"product\": \"${{ env.DEFECTDOJO_PRODUCTID }}\", | |
\"source_code_management_uri\": \"${{ github.server_url }}/${{ github.repository }}\", | |
\"build_server\": ${{ env.DEFECTDOJO_ENGAGEMENT_BUILD_SERVER }}, | |
\"source_code_management_server\": ${{ env.DEFECTDOJO_ENGAGEMENT_SOURCE_CODE_MANAGEMENT_SERVER }}, | |
\"orchestration_engine\": ${{ env.DEFECTDOJO_ENGAGEMENT_ORCHESTRATION_ENGINE }} | |
}" | jq -r '.id'` && | |
echo ${ENGAGEMENTID} > ENGAGEMENTID.env | |
- name: Create DefectDojo Engagement | |
if: env.useCommonDefectDojoProduct == 'false' | |
run: |- | |
TODAY=`date +%Y-%m-%d` | |
ENDDAY=$(date -d "+${{ env.DEFECTDOJO_ENGAGEMENT_PERIOD }} days" +%Y-%m-%d) | |
ENGAGEMENTID=`curl --fail --location --request POST "${{ env.DEFECTDOJO_URL }}/engagements/" \ | |
--header "Authorization: Token ${{ secrets.DEFECTDOJO_TOKEN }}" \ | |
--header 'Content-Type: application/json' \ | |
--data-raw "{ | |
\"tags\": [\"GITHUB\"], | |
\"name\": \"pygoat-${{ github.run_id }}\", | |
\"description\": \"${{ github.event.head_commit.message }}\", | |
\"version\": \"${{ github.ref }}\", | |
\"first_contacted\": \"${TODAY}\", | |
\"target_start\": \"${TODAY}\", | |
\"target_end\": \"${ENDDAY}\", | |
\"reason\": \"${{ env.DEFECTDOJO_ENGAGEMENT_REASON }}\", | |
\"tracker\": \"${{ github.server_url }}/${{ github.repository }}/\", | |
\"threat_model\": \"${{ env.DEFECTDOJO_ENGAGEMENT_THREAT_MODEL }}\", | |
\"api_test\": \"${{ env.DEFECTDOJO_ENGAGEMENT_API_TEST }}\", | |
\"pen_test\": \"${{ env.DEFECTDOJO_ENGAGEMENT_PEN_TEST }}\", | |
\"check_list\": \"${{ env.DEFECTDOJO_ENGAGEMENT_CHECK_LIST }}\", | |
\"status\": \"${{ env.DEFECTDOJO_ENGAGEMENT_STATUS }}\", | |
\"engagement_type\": \"CI/CD\", | |
\"build_id\": \"${{ github.run_id }}\", | |
\"commit_hash\": \"${{ github.sha }}\", | |
\"branch_tag\": \"${{ github.ref }}\", | |
\"deduplication_on_engagement\": \"${{ env.DEFECTDOJO_ENGAGEMENT_DEDUPLICATION_ON_ENGAGEMENT }}\", | |
\"product\": \"${{ env.DEFECTDOJO_PRODUCTID }}\", | |
\"source_code_management_uri\": \"${{ github.server_url }}/${{ github.repository }}\", | |
\"build_server\": ${{ env.DEFECTDOJO_ENGAGEMENT_BUILD_SERVER }}, | |
\"source_code_management_server\": ${{ env.DEFECTDOJO_ENGAGEMENT_SOURCE_CODE_MANAGEMENT_SERVER }}, | |
\"orchestration_engine\": ${{ env.DEFECTDOJO_ENGAGEMENT_ORCHESTRATION_ENGINE }} | |
}" | jq -r '.id'` && | |
echo ${ENGAGEMENTID} > ENGAGEMENTID.env | |
- name: list artifacts | |
if: env.listArtifacts == 'true' | |
run: |- | |
ls -l ${{ github.workspace }} | |
#ls -l ${{ github.workspace }}/image-scan-report | |
ls -l ${{ github.workspace }}/dependency-check-reports | |
ls -l ${{ github.workspace }}/bandit-sast-report | |
ls -l ${{ github.workspace }}/devops-shield-reports-linux | |
- name: Upload Reports To DefectDojo with Common Product | |
if: env.useCommonDefectDojoProduct == 'true' | |
run: |- | |
TODAY=`date +%Y-%m-%d` | |
ENGAGEMENTID=`cat ENGAGEMENTID.env` | |
echo "TODAY: $TODAY" | |
echo "ENGAGEMENTID: $ENGAGEMENTID" | |
array=('type=("SARIF" "pip-audit Scan" "SARIF" "Bandit Scan")' 'file=("devops-shield-reports-linux/DevOpsShield-SecurityScanner-Report-Fixed.sarif" "dependency-check-reports/dependency-check-report-pip-audit.json" "image-scan-report/image-scan-report.json" "bandit-sast-report/sast-report.json")') | |
for elt in "${array[@]}";do eval $elt;done | |
for scan in 0 1 2 3; do \ | |
echo "" | |
echo "======================= Scan No $scan =======================" | |
echo "Uploading ${type[$scan]} report" | |
echo "Uploading ${file[$scan]}" | |
#echo "Contents are:" | |
#cat ${{ github.workspace }}/${file[$scan]} | |
# check if the file exists | |
if [ -f ${{ github.workspace }}/${file[$scan]} ]; then | |
echo "File exists" | |
else | |
echo "File does not exist" | |
continue | |
fi | |
curl --fail --location --request POST "${{ env.DEFECTDOJO_URL }}/import-scan/" \ | |
--header "Authorization: Token ${{ env.DEFECTDOJO_TOKEN }}" \ | |
--form "scan_date=${TODAY}" \ | |
--form "minimum_severity=${{ env.DEFECTDOJO_SCAN_MINIMUM_SEVERITY }}" \ | |
--form "active=${{ env.DEFECTDOJO_SCAN_ACTIVE }}" \ | |
--form "verified=${{ env.DEFECTDOJO_SCAN_VERIFIED }}" \ | |
--form "scan_type=${type[$scan]}" \ | |
--form "engagement=${ENGAGEMENTID}" \ | |
--form "file=@${{ github.workspace }}/${file[$scan]}" \ | |
--form "close_old_findings=${{ env.DEFECTDOJO_SCAN_CLOSE_OLD_FINDINGS }}" \ | |
--form "push_to_jira=${{ env.DEFECTDOJO_SCAN_PUSH_TO_JIRA }}" \ | |
--form "test_type=${{ env.DEFECTDOJO_SCAN_TEST_TYPE }}" \ | |
--form "environment=${{ env.DEFECTDOJO_SCAN_ENVIRONMENT }}" | |
done | |
- name: Upload Reports To DefectDojo | |
if: env.useCommonDefectDojoProduct == 'false' | |
run: |- | |
TODAY=`date +%Y-%m-%d` | |
ENGAGEMENTID=`cat ENGAGEMENTID.env` | |
echo "TODAY: $TODAY" | |
echo "ENGAGEMENTID: $ENGAGEMENTID" | |
array=('type=("SARIF" "pip-audit Scan" "SARIF" "Bandit Scan")' 'file=("devops-shield-reports-linux/DevOpsShield-SecurityScanner-Report-Fixed.sarif" "dependency-check-reports/dependency-check-report-pip-audit.json" "image-scan-report/image-scan-report.json" "bandit-sast-report/sast-report.json")') | |
for elt in "${array[@]}";do eval $elt;done | |
for scan in 0 1 2 3; do \ | |
echo "" | |
echo "======================= Scan No $scan =======================" | |
echo "Uploading ${type[$scan]} report" | |
echo "Uploading ${file[$scan]}" | |
#echo "Contents are:" | |
#cat ${{ github.workspace }}/${file[$scan]} | |
# check if the file exists | |
if [ -f ${{ github.workspace }}/${file[$scan]} ]; then | |
echo "File exists" | |
else | |
echo "File does not exist" | |
continue | |
fi | |
curl --fail --location --request POST "${{ env.DEFECTDOJO_URL }}/import-scan/" \ | |
--header "Authorization: Token ${{ secrets.DEFECTDOJO_TOKEN }}" \ | |
--form "scan_date=${TODAY}" \ | |
--form "minimum_severity=${{ env.DEFECTDOJO_SCAN_MINIMUM_SEVERITY }}" \ | |
--form "active=${{ env.DEFECTDOJO_SCAN_ACTIVE }}" \ | |
--form "verified=${{ env.DEFECTDOJO_SCAN_VERIFIED }}" \ | |
--form "scan_type=${type[$scan]}" \ | |
--form "engagement=${ENGAGEMENTID}" \ | |
--form "file=@${{ github.workspace }}/${file[$scan]}" \ | |
--form "close_old_findings=${{ env.DEFECTDOJO_SCAN_CLOSE_OLD_FINDINGS }}" \ | |
--form "push_to_jira=${{ env.DEFECTDOJO_SCAN_PUSH_TO_JIRA }}" \ | |
--form "test_type=${{ env.DEFECTDOJO_SCAN_TEST_TYPE }}" \ | |
--form "environment=${{ env.DEFECTDOJO_SCAN_ENVIRONMENT }}" | |
done | |
deploy_test-deploy_to_test_k8s_cluster: | |
name: Deploy To Test K8S Cluster | |
needs: | |
- test-upload_reports | |
runs-on: ubuntu-latest | |
env: | |
RESOURCE_GROUP: rg-k8s-pygoat-dev-001 | |
CLUSTER_NAME: aks-k8s-pygoat-dev-001 | |
NAMESPACE: pygoat-test | |
Pygoat_Service: pygoat-svc | |
DnsResourceGroup: rg-dns-prod | |
zoneName: cad4devops.com | |
dnsRecordSetName: gh-pygoat-test | |
image: ${{ vars.DOCKER_USERNAME }}.azurecr.io/devsecops-pygoat | |
environment: | |
name: OSS_pygoat-test | |
steps: | |
- name: checkout | |
uses: actions/[email protected] | |
- name: download artifact | |
uses: actions/[email protected] | |
- uses: actions/[email protected] | |
- uses: cschleiden/replace-tokens@v1 | |
with: | |
tokenPrefix: "#{" | |
tokenSuffix: "}#" | |
files: '["${{ github.workspace }}/manifests/k8s-*.yaml"]' | |
env: | |
host: ${{ env.host }} | |
image: ${{ env.image }} | |
tag: ${{ env.tag }} | |
- name: See the files | |
run: |- | |
cat ${{ github.workspace }}/manifests/k8s-deployment.yaml | |
cat ${{ github.workspace }}/manifests/k8s-ingress.yaml | |
- name: Azure login | |
if: env.useAzureCliForK8s == 'true' | |
uses: azure/login@v2 | |
with: | |
client-id: ${{ secrets.AZURE_CLIENT_ID }} | |
tenant-id: ${{ secrets.AZURE_TENANT_ID }} | |
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
- name: Create k8s namespace ${{ env.NAMESPACE }} | |
if: env.useAzureCliForK8s == 'true' | |
uses: azure/cli@v2 | |
with: | |
azcliversion: latest | |
inlineScript: | | |
az account show | |
az aks get-credentials --resource-group ${{ env.RESOURCE_GROUP }} --name ${{ env.CLUSTER_NAME }} | |
az aks install-cli | |
echo "Sleeping for 10 seconds - waiting for kubectl to be ready" | |
sleep 10 | |
kubectl version --client | |
kubectl get nodes | |
kubectl get namespaces | |
# create namespace if it does not exist | |
kubectl create namespace ${{ env.NAMESPACE }} --dry-run=client -o yaml | kubectl apply -f - | |
- uses: tale/kubectl-action@v1 | |
continue-on-error: true | |
with: | |
base64-kube-config: ${{ secrets.KUBE_CONFIG }} | |
kubectl-version: v1.28.9 | |
- name: Create k8s namespace ${{ env.NAMESPACE }} | |
continue-on-error: true | |
run: |- | |
kubectl version --client | |
kubectl get nodes | |
kubectl get namespaces | |
# create namespace if it does not exist | |
kubectl create namespace ${{ env.NAMESPACE }} --dry-run=client -o yaml | kubectl apply -f - | |
- name: Install k8s manifests - namespace ${{ env.NAMESPACE }} | |
if: env.useAzureCliForK8s == 'true' | |
uses: azure/cli@v2 | |
with: | |
azcliversion: latest | |
inlineScript: | | |
az account show | |
az aks get-credentials --resource-group ${{ env.RESOURCE_GROUP }} --name ${{ env.CLUSTER_NAME }} | |
az aks install-cli | |
echo "Sleeping for 10 seconds - waiting for kubectl to be ready" | |
sleep 10 | |
kubectl version --client | |
# apply the manifests | |
kubectl apply -f ${{ github.workspace }}/manifests/k8s-deployment.yaml -n ${{ env.NAMESPACE }} | |
kubectl apply -f ${{ github.workspace }}/manifests/k8s-service.yaml -n ${{ env.NAMESPACE }} | |
kubectl apply -f ${{ github.workspace }}/manifests/k8s-ingress.yaml -n ${{ env.NAMESPACE }} | |
# wait for the service to be ready | |
kubectl wait --for=condition=available --timeout=600s deployment.apps/pygoat-app -n ${{ env.NAMESPACE }} | |
- name: Install k8s manifests - namespace ${{ env.NAMESPACE }} | |
continue-on-error: true | |
run: |- | |
kubectl version --client | |
# apply the manifests | |
kubectl apply -f ${{ github.workspace }}/manifests/k8s-deployment.yaml -n ${{ env.NAMESPACE }} | |
kubectl apply -f ${{ github.workspace }}/manifests/k8s-service.yaml -n ${{ env.NAMESPACE }} | |
kubectl apply -f ${{ github.workspace }}/manifests/k8s-ingress.yaml -n ${{ env.NAMESPACE }} | |
# wait for the service to be ready | |
kubectl wait --for=condition=available --timeout=600s deployment.apps/pygoat-app -n ${{ env.NAMESPACE }} | |
- name: Adjust DNS for environment | |
if: env.adjustDNS == 'true' | |
uses: azure/cli@v2 | |
with: | |
azcliversion: latest | |
inlineScript: | | |
az account show | |
az aks get-credentials --resource-group ${{ env.RESOURCE_GROUP }} --name ${{ env.CLUSTER_NAME }} | |
az aks install-cli | |
echo "Sleeping for 10 seconds - waiting for kubectl to be ready" | |
sleep 10 | |
kubectl version --client | |
kubectl get pods -n ${{ env.NAMESPACE }} | |
kubectl get services -n ${{ env.NAMESPACE }} | |
# get external IP from the service | |
externalIp=$(kubectl get service ${{ env.Pygoat_Service }} -n ${{ env.NAMESPACE }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') | |
echo "External IP: $externalIp" | |
az network dns record-set a add-record \ | |
-g ${{ env.DnsResourceGroup }} \ | |
-z ${{ env.zoneName }} \ | |
-n ${{ env.dnsRecordSetName }} \ | |
-a $externalIp | |
- name: Get Resources - namespace ${{ env.NAMESPACE }} | |
continue-on-error: true | |
run: |- | |
kubectl version --client | |
kubectl get pods -n ${{ env.NAMESPACE }} | |
kubectl get services -n ${{ env.NAMESPACE }} | |
# get external IP from the service | |
externalIp=$(kubectl get service ${{ env.Pygoat_Service }} -n ${{ env.NAMESPACE }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') | |
echo "External IP: $externalIp" | |
dast-run_integration_tests: | |
name: Run Integration Tests | |
needs: | |
- deploy_test-deploy_to_test_k8s_cluster | |
runs-on: ubuntu-latest | |
steps: | |
- name: checkout | |
uses: actions/[email protected] | |
- name: Integration Tests with Selenium | |
if: success() || failure() | |
run: |- | |
python -m pip install -r requirements.txt | |
python -m pip install pytest-cov | |
python -m pytest introduction/tests/integration/ --junitxml=${{ github.workspace }}/selenium-test-output.xml --cov=. --cov-report=xml | |
- name: Publish Selenium Report | |
if: success() || failure() | |
uses: actions/[email protected] | |
with: | |
name: selenium-test-results | |
path: "${{ github.workspace }}/selenium-test-output.xml" | |
dast-run_dast_scan: | |
name: Run DAST Scan | |
needs: | |
- deploy_test-deploy_to_test_k8s_cluster | |
runs-on: ubuntu-latest | |
steps: | |
- name: checkout | |
uses: actions/[email protected] | |
- name: load owasp detached | |
run: "docker run --name ${{ env.containerName }} -d -u zap \\\n -p ${{ env.owaspZapWebPort }}:${{ env.owaspZapWebPort }} \\\n -p ${{ env.owaspZapApiPort }}:${{ env.owaspZapApiPort }} \\\n -i ${{ env.owaspImageName }} zap.sh -daemon \\\n -port ${{ env.owaspZapApiPort }} -host 0.0.0.0 \\\n -config api.disablekey=true \\\n -config api.addrs.addr.name=.* \\\n -config api.addrs.addr.regex=true\n\n# wait for docker to load\necho \"waiting for docker to load for ${{ env.dockerWaitTime }}\"\nsleep ${{ env.dockerWaitTime }}\n\n# wait for zap to start\necho \"waiting for zap to start with loop time ${{ env.loopWaitTime }}\"\necho \"\"\nwhile [ \"$(curl -s http://localhost:${{ env.owaspZapApiPort }}/JSON/core/view/version/ | jq -r '.version')\" == \"null\" ]; do\n echo \"\"\n echo \"waiting for zap to start with loop time ${{ env.loopWaitTime }}\"\n sleep ${{ env.loopWaitTime }}\n curl -s http://localhost:${{ env.owaspZapApiPort }}/JSON/core/view/version/ | jq -r '.version'\ndone\n\n# check zap version\necho \"\"\ncurl \"http://localhost:${{ env.owaspZapApiPort }}/JSON/core/view/version/\"\n\n# To start the spider scan\necho \"\"\ncurl \"http://localhost:${{ env.owaspZapApiPort }}/JSON/spider/action/scan/?url=${{ env.appUrl }}\"\n\n# loop until response is 100\necho \"\"\nwhile [ \"$(curl -s http://localhost:${{ env.owaspZapApiPort }}/JSON/spider/view/status/?scanId=0 | jq -r '.status')\" != \"100\" ]; do\n echo \"\"\n echo \"waiting for spider to finish with loop time ${{ env.loopWaitTime }}\"\n sleep ${{ env.loopWaitTime }}\n curl -s http://localhost:${{ env.owaspZapApiPort }}/JSON/spider/view/status/?scanId=0 | jq -r '.status'\ndone\n\n# To view the status of spider\necho \"\"\ncurl \"http://localhost:${{ env.owaspZapApiPort }}/JSON/spider/view/status/?scanId=0\"\n\n# To start the the active scan if doActiveScan is true\nif [ \"${{ env.doActiveScan }}\" == \"true\" ]; then\n echo \"\"\n echo \"starting active scan\"\n curl \"http://localhost:${{ env.owaspZapApiPort }}/JSON/ascan/action/scan/?url=${{ env.appUrl }}&recurse=true&inScopeOnly=&scanPolicyName=&method=&postData=&contextId=\"\n \n # To view the the status of active scan\n echo \"\"\n curl \"http://localhost:${{ env.owaspZapApiPort }}/JSON/ascan/view/status/?scanId=0\"\n\n # loop until response is 100\n echo \"\"\n while [ \"$(curl -s http://localhost:${{ env.owaspZapApiPort }}/JSON/ascan/view/status/?scanId=0 | jq -r '.status')\" != \"100\" ]; do\n echo \"\"\n echo \"waiting for active scan to finish with loop time ${{ env.loopWaitTime }}\"\n sleep ${{ env.loopWaitTime }}\n curl -s http://localhost:${{ env.owaspZapApiPort }}/JSON/ascan/view/status/?scanId=0 | jq -r '.status'\n done\n\n # To view the alerts of active scan\n echo \"\"\n curl \"http://localhost:${{ env.owaspZapApiPort }}/JSON/ascan/view/status/?scanId=0\"\n\n # To view the alerts of active scan\n echo \"\"\n echo \"viewing ${{ env.maxAlerts }} alerts\"\n curl \"http://localhost:${{ env.owaspZapApiPort }}/JSON/core/view/alerts/?baseurl=${{ env.appUrl }}&start=0&count=${{ env.maxAlerts }}\"\nfi\n\necho \"\"\necho \"generating JSON report ${{ env.owaspzap-report-json }}\"\n#curl \"http://localhost:${{ env.owaspZapApiPort }}/OTHER/core/other/jsonreport/\"\ncurl \"http://localhost:${{ env.owaspZapApiPort }}/JSON/reports/action/generate/?template=${{ env.traditionalJsonTemplate }}&title=repoTitle&reportFileName=${{ env.owaspzap-report-json }}\"\necho \"\"\necho \"contents of ${{ env.owaspzap-report-json }}\"\ndocker exec ${{ env.containerName }} cat /home/zap/${{ env.owaspzap-report-json }}\necho \"\"\necho \"generating SARIF report ${{ env.owaspzap-report-sarif }}.json\"\ncurl \"http://localhost:${{ env.owaspZapApiPort }}/JSON/reports/action/generate/?template=${{ env.sarifTemplate }}&title=repoTitle&reportFileName=${{ env.owaspzap-report-sarif }}.json\"\necho \"\"\necho \"create folder for owaspzap reports\"\nmkdir -p \"${{ runner.temp }}/owaspzap\"\necho \"\"\necho \"contents of ${{ env.owaspzap-report-sarif }}.json\"\ndocker exec ${{ env.containerName }} cat /home/zap/${{ env.owaspzap-report-sarif }}.json\necho \"\"\necho \"copying $(owaspzap-report-sarif-json) to artifact directory\"\ndocker cp \"${{ env.containerName }}:/home/zap/${{ env.owaspzap-report-json }}\" \"${{ runner.temp }}/owaspzap/${{ env.owaspzap-report-json }}\"\necho \"\"\necho \"copying ${{ env.owaspzap-report-sarif }}.json to artifact directory\"\ndocker cp \"${{ env.containerName }}:/home/zap/${{ env.owaspzap-report-sarif }}.json\" \"${{ runner.temp }}/owaspzap/${{ env.owaspzap-report-sarif }}\"" | |
- name: Publish OWASP ZAP Reports | |
uses: actions/[email protected] | |
with: | |
name: CodeAnalysisLogs | |
path: "${{ runner.temp }}/owaspzap" | |
- name: Publish ZAP Report | |
if: success() || failure() | |
uses: actions/[email protected] | |
with: | |
name: owasp_zap_report | |
path: "${{ runner.temp }}/owaspzap" | |
deploy_prod-deploy_prod: | |
name: Deploy To Prod K8S Cluster | |
needs: | |
- dast-run_integration_tests | |
- dast-run_dast_scan | |
runs-on: ubuntu-latest | |
environment: | |
name: OSS_pygoat-prod | |
env: | |
appUrl: http://gh-pygoat.cad4devops.com | |
host: gh-pygoat.cad4devops.com | |
RESOURCE_GROUP: rg-k8s-pygoat-dev-001 | |
CLUSTER_NAME: aks-k8s-pygoat-dev-001 | |
NAMESPACE: pygoat | |
Pygoat_Service: pygoat-svc | |
DnsResourceGroup: rg-dns-prod | |
zoneName: cad4devops.com | |
dnsRecordSetName: gh-pygoat | |
image: ${{ vars.DOCKER_USERNAME }}.azurecr.io/devsecops-pygoat | |
steps: | |
- name: checkout | |
uses: actions/[email protected] | |
- name: download artifact | |
uses: actions/[email protected] | |
- uses: actions/[email protected] | |
- uses: cschleiden/replace-tokens@v1 | |
with: | |
tokenPrefix: "#{" | |
tokenSuffix: "}#" | |
files: '["${{ github.workspace }}/manifests/k8s-*.yaml"]' | |
env: | |
host: ${{ env.host }} | |
image: ${{ env.image }} | |
tag: ${{ env.tag }} | |
- name: See the files | |
run: |- | |
cat ${{ github.workspace }}/manifests/k8s-deployment.yaml | |
cat ${{ github.workspace }}/manifests/k8s-ingress.yaml | |
- name: Azure login | |
if: env.useAzureCliForK8s == 'true' | |
uses: azure/login@v2 | |
with: | |
client-id: ${{ secrets.AZURE_CLIENT_ID }} | |
tenant-id: ${{ secrets.AZURE_TENANT_ID }} | |
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
- name: Create k8s namespace ${{ env.NAMESPACE }} | |
if: env.useAzureCliForK8s == 'true' | |
uses: azure/cli@v2 | |
with: | |
azcliversion: latest | |
inlineScript: | | |
az account show | |
az aks get-credentials --resource-group ${{ env.RESOURCE_GROUP }} --name ${{ env.CLUSTER_NAME }} | |
az aks install-cli | |
echo "Sleeping for 10 seconds - waiting for kubectl to be ready" | |
sleep 10 | |
kubectl version --client | |
kubectl get nodes | |
kubectl get namespaces | |
# create namespace if it does not exist | |
kubectl create namespace ${{ env.NAMESPACE }} --dry-run=client -o yaml | kubectl apply -f - | |
- uses: tale/kubectl-action@v1 | |
continue-on-error: true | |
with: | |
base64-kube-config: ${{ secrets.KUBE_CONFIG }} | |
kubectl-version: v1.28.9 | |
- name: Create k8s namespace ${{ env.NAMESPACE }} | |
continue-on-error: true | |
run: |- | |
kubectl version --client | |
kubectl get nodes | |
kubectl get namespaces | |
# create namespace if it does not exist | |
kubectl create namespace ${{ env.NAMESPACE }} --dry-run=client -o yaml | kubectl apply -f - | |
- name: Install k8s manifests - namespace ${{ env.NAMESPACE }} | |
if: env.useAzureCliForK8s == 'true' | |
uses: azure/cli@v2 | |
with: | |
azcliversion: latest | |
inlineScript: | | |
az account show | |
az aks get-credentials --resource-group ${{ env.RESOURCE_GROUP }} --name ${{ env.CLUSTER_NAME }} | |
az aks install-cli | |
echo "Sleeping for 10 seconds - waiting for kubectl to be ready" | |
sleep 10 | |
kubectl version --client | |
# apply the manifests | |
kubectl apply -f ${{ github.workspace }}/manifests/k8s-deployment.yaml -n ${{ env.NAMESPACE }} | |
kubectl apply -f ${{ github.workspace }}/manifests/k8s-service.yaml -n ${{ env.NAMESPACE }} | |
kubectl apply -f ${{ github.workspace }}/manifests/k8s-ingress.yaml -n ${{ env.NAMESPACE }} | |
# wait for the service to be ready | |
kubectl wait --for=condition=available --timeout=600s deployment.apps/pygoat-app -n ${{ env.NAMESPACE }} | |
- name: Install k8s manifests - namespace ${{ env.NAMESPACE }} | |
continue-on-error: true | |
run: |- | |
kubectl version --client | |
# apply the manifests | |
kubectl apply -f ${{ github.workspace }}/manifests/k8s-deployment.yaml -n ${{ env.NAMESPACE }} | |
kubectl apply -f ${{ github.workspace }}/manifests/k8s-service.yaml -n ${{ env.NAMESPACE }} | |
kubectl apply -f ${{ github.workspace }}/manifests/k8s-ingress.yaml -n ${{ env.NAMESPACE }} | |
# wait for the service to be ready | |
kubectl wait --for=condition=available --timeout=600s deployment.apps/pygoat-app -n ${{ env.NAMESPACE }} | |
- name: Adjust DNS for environment | |
if: env.adjustDNS == 'true' | |
uses: azure/cli@v2 | |
with: | |
azcliversion: latest | |
inlineScript: | | |
az account show | |
az aks get-credentials --resource-group ${{ env.RESOURCE_GROUP }} --name ${{ env.CLUSTER_NAME }} | |
az aks install-cli | |
echo "Sleeping for 10 seconds - waiting for kubectl to be ready" | |
sleep 10 | |
kubectl version --client | |
kubectl get pods -n ${{ env.NAMESPACE }} | |
kubectl get services -n ${{ env.NAMESPACE }} | |
# get external IP from the service | |
externalIp=$(kubectl get service ${{ env.Pygoat_Service }} -n ${{ env.NAMESPACE }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') | |
echo "External IP: $externalIp" | |
az network dns record-set a add-record \ | |
-g ${{ env.DnsResourceGroup }} \ | |
-z ${{ env.zoneName }} \ | |
-n ${{ env.dnsRecordSetName }} \ | |
-a $externalIp | |
- name: Get Resources - namespace ${{ env.NAMESPACE }} | |
continue-on-error: true | |
run: |- | |
kubectl version --client | |
kubectl get pods -n ${{ env.NAMESPACE }} | |
kubectl get services -n ${{ env.NAMESPACE }} | |
# get external IP from the service | |
externalIp=$(kubectl get service ${{ env.Pygoat_Service }} -n ${{ env.NAMESPACE }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') | |
echo "External IP: $externalIp" |