Use the Prisma Cloud Extension for the GitLab CI/CD Pipeline

To scan IaC templates in the build or release phase of the GitLab CI/CD pipeline, you need to use the Prisma Cloud extension. The first step is to set up a connection to the Prisma Cloud API server and configure the details—as environment variables—for that connection. Then, the IaC scanning capability becomes available as a script that you can embed as a custom task in your GitLab pipelines. You can trigger the custom task to scan on every commit (pull request) or on a schedule, and specify the build or release pipeline failure criteria based on the severity of the security issues that it identifies. The scan uses the failure thresholds you specify to pass or fail the check. When the scan is successful, the code can be merged. If the scan is unsuccessful, the security issues must be fixed in order to merge code changes.
The list of inputs and that are required for scanning IaC templates in the build or release phase of the GitLab CI/CD pipeline are:
  • Connection settings as environment variables to enable communication between the Prisma Cloud API server and your GitLab repository.
  • The .gitlab-ci.yml at the root level in your repository.
    For any commit or push to your repository, this file start jobs on GitLab Runners according to the contents of the file. You must have a shared runner or a project-specific/custom runner for the job to run successfully.
  • Scan.sh script.
    This script uses the values that you provided in the environment variables to call the Prisma Cloud API endpoint. You have the flexibility to either provide the path to where this scan.sh script resides in your repository within the .gitlab-ci.yml, or you can copy the script in to the gitlab-ci.yml file itself.
    When the script runs, if you have any missing or incorrect environment variables, an error message displays on the pipeline console.
  • config.yml file at the root-level within the project under the .prismaCloud directory.
    The path for this file must be .prismaCloud/config.yml. Prisma Cloud requires this configuration file to learn about your IaC module structure, runtime variables, and tags so that it can scan the IaC templates in your repository.
To set up the Prisma Cloud GitLab extension for CI/CD:

Configure the Prisma Cloud Extension for GitLab CI/CD

The GitLab extension is a script, and the first step for you is to include the script as a custom task in your pipeline.
  1. Set up the connection to the Prisma Cloud API.
    1. Add the connection settings as environment variables to
      Project
      Settings
      CICD
      Variables
      .
      iac-scan-gitlab-connection-settings.png
    2. Set the Prisma Cloud API URL as the value for the
      prisma_cloud_api_url
      environment variable.
      The API URL for Prisma Cloud varies depending on the region and cluster on which your tenant is deployed. If the tenant provisioned for you is, for example,
      https://app2.prismacloud.io
      or
      https://app.eu.prismacloud.io
      , replace
      app
      in the URL with
      api
      and enter it here. Refer to the Prisma Cloud REST API Reference, for more details.
    3. Add your Prisma Cloud access key as the value for the
      access_key
      environment variable.
      The access key enables programmatic access. If you do not have a key, see Create and Manage Access Keys.
    4. Add your GitLab server name as the value for the
      cicd_asset_name
      environment variable.
      On Prisma Cloud, the asset name is used to track results. Some examples names are - creditapp_server, ConsumerBU_server. etc
  2. Create the
    .prismaCloud/config.yml
    file and add it to the root directory of your repository branch. The file is required, and it must include the template type, version, and the template specific parameters and tags you use in your environment.

Set Up a Custom Job for IaC Scan

  1. Create the scan.sh custom script.
    Use the sample Prisma Cloud Custom Script— scan.sh to create the file. Then, add the file to a folder from where it can be accessed in your GitLab pipeline. This file enables you to view the scan results.
  2. Add Prisma Cloud IaC scan job to the GitLab CI configuration.
    The GitLab CI configuration is stored in the .gitlab-ci.yml file. Add the following command to your gitlab-ci.yml file
    ./prismacloud-scripts/scan.sh $CI_BUILDS_DIR/prisma_scan
    , where
    $CI_BUILDS_DIR/prisma_scan
    is the path to your IaC templates location.
    The IaC templates in this directory must not exceed the 9 MB size limit.
    Refer to the GitLab documentation to learn about the gitlab-ci.yml file. A sample file is included here.
    #variables are specific to your environment, please change accordingly. variables: GIT_STRATEGY: fetch GIT_CHECKOUT: "true" GIT_CLONE_PATH: $CI_BUILDS_DIR/prisma_scan prisma-cloud-scan: stage: build before_script: - apt-get update -qy - apt-get install -y jq - wget https://github.com/mikefarah/yq/releases/download/3.2.1/yq_linux_386 - mv yq_linux_386 /usr/local/bin/yq - chmod +x /usr/local/bin/yq - apt-get install bsdmainutils #needed for displaying file output in column format on console - apt-get -y install zip unzip script: # If you wish to pull code of your project using git clone before next steps if wish to clone at different stage than buid. # here ./prismacloud-scripts/scan.sh is the location of the script in the gitlab repo or whereever it is stored. $CI_BUILDS_DIR/prisma_scan is the argument to file which is 'full cloned repository path'. # prisma_scan is project(repository) name - ./prismacloud-scripts/scan.sh $CI_BUILDS_DIR/prisma_scan # Above same can also be done by passing absolute repo path as below #- ./prismacloud-scripts/scan.sh /build/prisma_scan artifacts: when : always paths: - report/scan_results.csv
  3. Set up the failure criteria for the Prisma Cloud IaC scan.
    Define the number of issues by severity in the
    cicd_failure_criteria
    environment variable. Set the High : x, Medium : y, Low : z, Operator: O, where, x,y,z is the number of issues of each severity, and the operator is OR, AND.
    For example:
    • To fail the pipeline for any security issue detected, cicd_failue_criteria = High : 0, Medium : 0, Low : 0, Operator: OR
    • To never fail the pipeline, cicd_failure_criteria = High : 1000, Medium : 1000, Low : 1000, Operator: AND
  4. Set up the Prisma Cloud tags.
    Prisma Cloud tags are different from GitLab tags or cloud tags that you may have included within your IaC templates. Prisma Cloud tags enable visibility on the Prisma Cloud administrator console.
    Provide the values as a comma separated list of tags in the
    cicd_tags
    environment variable. For example, cicd_tag = project=x, owner=mr.y, compliance = pci.
  5. View IaC scan results.
    The Prisma Cloud IaC scan uses the failure criteria you defined in the
    cicd_failure_criteria
    environment variable to pass or fail a scan. When it detects a security issue, it generates an artifact.
    • To download the artifact, select
      Project
      CI/CD
      Pipeline
      , select the job and
      Download
      artifacts for the job.
      IaC Scan result when the scan is successful and you have no security issues.
      iac-scan-gitlab-scan-success.png
    • IaC Scan result when the scan fails.
      iac-scan-gitlab-scan-fail.png
    • The Prisma Cloud artifact is a .csv file that lists the security issues detected. Download the artifact and open report/scan_results.csv to view the list of issues.
      iac-scan-gitlab-scan-csv.png
    • View the Prisma Cloud IaC scan results on the console of CI/CD pipeline log output (
      Project
      Setting
      Pipeline
      , select the job and view
      Log console output
      .
      iac-scan-gitlab-scan-cli.png

Prisma Cloud Custom Script— scan.sh

Scan.sh script for Prisma Cloud IaC scan GitLab CI/CD extension.
#!/bin/bash #######Perform zip functionality########### #echo "Entered full cloned repo path:" $1 if [[ "$1" != "" ]];then repo_path=$1 else echo "Please enter the full cloned repository path on build server/runner. For details refer to https://docs.paloaltonetworks.com/prisma/prisma-cloud/prisma-cloud-admin/prisma-cloud-devops-security/use-the-prisma-cloud-app-for-gitlab.html" exit 1; fi #echo "repo_path:" $repo_path #ls -al $repo_path #read ENV variables echo $prisma_cloud_api_url $access_key #$secret_key if [[ -z "$prisma_cloud_api_url" ]];then echo "Please enter a valid URL. For details refer to https://docs.paloaltonetworks.com/prisma/prisma-cloud/prisma-cloud-admin/prisma-cloud-devops-security/use-the-prisma-cloud-app-for-gitlab.html" exit 1; fi if [[ -z "$access_key" || -z "$secret_key" ]];then echo "Invalid credentials, verify that access key and secret key in environment variables are valid. For details refer to https://docs.paloaltonetworks.com/prisma/prisma-cloud/prisma-cloud-admin/prisma-cloud-devops-security/use-the-prisma-cloud-app-for-gitlab.html" exit 1; fi if [[ ! -f $repo_path/.prismaCloud/config.yml ]]; then echo "Can not find config.yml under .prismaCloud folder in repo $CI_PROJECT_TITLE. Please make sure the file is present in correct format https://docs.paloaltonetworks.com/prisma/prisma-cloud/prisma-cloud-admin/prisma-cloud-devops-security/use-the-prisma-cloud-app-for-gitlab.html at the root of your repo under .prismaCloud folder." exit 1; fi if [[ -z "$cicd_asset_name" ]]; then echo "Please enter a valid cicd asset name. For details refer to https://docs.paloaltonetworks.com/prisma/prisma-cloud/prisma-cloud-admin/prisma-cloud-devops-security/use-the-prisma-cloud-app-for-gitlab.html" exit 1; fi #####Compress the repo and check if compressed zip file size>5MB############# #echo "current path:" #pwd cd $repo_path #ls -al . zip -r $repo_path/iacscan.zip . -x \*.git\* #here cd inside repo_path and '.' as source is mandatory else while zipping copies else it will zip from root instead of files inside repo #echo "after zip content of repo_path" #ls -al $repo_path file_size="$(wc -c $repo_path/iacscan.zip | awk '{print $1}')" #echo "file_size:" $file_size file_size_limit=5242880 if [[ "$file_size" -gt "$file_size_limit" ]];then echo "Directory size $repo_path more than 8MB is not supported" exit 1; fi #$CI_PROJECT_DIR is default inbuilt dir used to upload the artifacts but you can change to any one the job has access to. #echo "view content of CI_PROJECT_DIR" #ls -al $CI_PROJECT_DIR #############Check failure criteria exists, if not default 0,0,0,or######### if [[ -z "$cicd_failure_criteria" ]];then failure_criteria_high_severity=0 failure_criteria_medium_severity=0 failure_criteria_low_severity=0 failure_criteria_operator="or" else echo "failure criteria:" $cicd_failure_criteria cicd_failure_criteria_removed_spaces=$(printf '%s' $cicd_failure_criteria) #- echo $cicd_failure_criteria_removed_spaces delimiter=, s=$cicd_failure_criteria_removed_spaces$delimiter array=(); while [[ $s ]]; do array+=( "${s%%"$delimiter"*}" ); s=${s#*"$delimiter"}; done; #- declare -p array failure_criteria_high_severity=$(awk -F':' '{print $2}' <<< "${array[0]}") failure_criteria_medium_severity=$(awk -F':' '{print $2}' <<< "${array[1]}") failure_criteria_low_severity=$(awk -F':' '{print $2}' <<< "${array[2]}") failure_criteria_operator=$(awk -F':' '{print $2}' <<< "${array[3]}") #echo "Failure Criterias:" $failure_criteria_high_severity $failure_criteria_medium_severity $failure_criteria_low_severity $failure_criteria_operator fi ################################################# # Read .prismaCloud/config.yml and form headers for scan ################################################ fileContents=$(yq read -j $repo_path/.prismaCloud/config.yml) #echo "file contents are:" $fileContents t_Type="$(echo "$fileContents" | jq -r '.template_type')" #echo "template type:" $t_Type headers="" url="" if [[ ! -z "$t_Type" ]]; then templateType=${t_Type^^} #echo $templateType else echo "No valid template-type found in config.yml file in repo $CI_PROJECT_TITLE. Please specify either of these values: TF, CFT or K8s as template-type variable in the config.yml" exit 1; fi if [[ "$templateType" == "TF" ]]; then url="$prisma_cloud_api_url/iac/tf/v1/scan" terraformVersion="$(echo "$fileContents" | jq -r '.terraform_version')" if [[ ! -z "$terraformVersion" && "$terraformVersion" == "0.12" ]];then headers+=" -H terraform-version:$terraformVersion" #read terraform 0.12 parameters isTerraform12ParamsPresent="$(echo "$fileContents" | jq -r '.terraform_012_parameters')" if [[ "$isTerraform12ParamsPresent" != null ]]; then terraformContents="$(echo "$fileContents" | jq -r '.terraform_012_parameters[] |= with_entries( .key |= gsub("root_module"; "root-module") )' | jq -r '.terraform_012_parameters[] |= with_entries( .key |= gsub("variable_files"; "variable-files") )' )" terraform012Parameters="$(echo "$terraformContents" | jq -r '.terraform_012_parameters' | tr -d '\n\t' | tr -d '[:blank:]')" if [[ "$terraform012Parameters" != null ]]; then headers+=" -H terraform-012-parameters:$terraform012Parameters" fi fi else #- headers+=" -H terraform-version:0.11" no version header needed for 0.11 #- read terraform 0.11 parameters variableFiles="$(echo "$fileContents" | jq -r '.terraform_011_parameters.variable_files')" variableValues="$(echo "$fileContents" | jq -r '.terraform_011_parameters.variable_values')" if [[ "$variableFiles" != null ]]; then headers+=" -H rl-variable-file-names:$variableFiles" fi if [[ "$variableValues" != null ]]; then headers+=" -H rl-parameters:$variableValues" fi fi elif [[ "$templateType" == "CFT" ]]; then url="$prisma_cloud_api_url/iac/cft/v1/scan" variableValues="$(echo "$fileContents" | jq -r '.cft_parameters.variable_values' | tr -d '\n\t' | tr -d '[:blank:]')" if [[ "$variableValues" != null ]]; then headers+=" -H 'rl-parameters:$variableValues'" fi elif [[ "$templateType" == "K8S" ]]; then url="$prisma_cloud_api_url/iac/k8s/v1/scan" else echo "No valid template-type found in config.yml file in repo $CI_PROJECT_TITLE. Please specify either of these values: TF, CFT or K8s as template-type variable in the config.yml" exit 1; fi ################################################### # LOGIN TO GET TOKEN ################################################## #echo "Get token using login api" result=$(curl -k -i -o -X POST $prisma_cloud_api_url/login --user-agent "GitLab PrismaCloud/DevOpsSecurity-1.0.0" -H 'Content-Type:application/json' -d "{\"username\":\"${access_key}\",\"password\":\"${secret_key}\"}") #- echo $result code=$(echo "$result" |grep HTTP | awk '{print $2}') echo $code if [[ "$code" -eq 400 || "$code" -eq 401 || "$code" -eq 403 ]]; then echo "Invalid credentials, verify that access key and secret key in environment variables are valid. For details refer to https://docs.paloaltonetworks.com/prisma/prisma-cloud/prisma-cloud-admin/prisma-cloud-devops-security/use-the-prisma-cloud-app-for-gitlab.html" exit 1; elif [[ "$code" -eq 500 || "$code" -eq 501 || "$code" -eq 503 ]];then echo "Oops! Something went wrong, please try again or refer to documentation https://docs.paloaltonetworks.com/prisma/prisma-cloud/prisma-cloud-admin/prisma-cloud-devops-security/use-the-prisma-cloud-app-for-gitlab.html" exit 1; elif [[ "$code" -ne 200 ]];then echo "Oops! Something went wrong, please try again or refer to documentation https://docs.paloaltonetworks.com/prisma/prisma-cloud/prisma-cloud-admin/prisma-cloud-devops-security/use-the-prisma-cloud-app-for-gitlab.html" exit 1; fi output_response=$(echo "$result" | grep token) token="$(echo "$output_response" | jq .token | tr -d '"')" #################################################### # Start PROCESSING PRISM CLOUD IAC SCAN ################################################### #echo url:"$url" echo header:"$headers" #form prisma-tags prisma_tags="" if [[ ! -z "$cicd_tags" ]]; then temp_str=$(printf '%s' $cicd_tags) if [[ ! -z "$temp_str" ]]; then settings_tags=\"$(sed 's/,/","/g' <<< "$temp_str")\" prisma_tags="\"settings-tags\":[$settings_tags]" fi fi #tags from config.yml repo_tags="$(echo "$fileContents" | jq -r '.tags' |tr -d '\n\t' | tr -d '[:blank:]')" if [[ $repo_tags != null ]]; then prisma_tags+=",\"repo-tags\":$repo_tags" fi ################################################################## # creating metadata structure metadata_json={"asset-name":"$cicd_asset_name","asset-type":"Gitlab","user-id":"${GITLAB_USER_LOGIN}","prisma-tags":{"$prisma_tags"},"scan-attributes":{"build-number":"${CI_JOB_ID}","project-name":"${CI_PROJECT_TITLE}"},"failure-criteria":{"high":"$failure_criteria_high_severity","medium":"$failure_criteria_medium_severity","low":"$failure_criteria_low_severity","operator":"$failure_criteria_operator"}} #echo metadata "$metadata_json" ################################################################# cd $CI_BUILDS_DIR #ls cp $repo_path/iacscan.zip . response="$(curl -k -X POST $url -H "x-redlock-auth:${token}" --user-agent "GitlabCI PrismaCloud/DevOpsSecurity-1.0.0" $headers -H "x-redlock-iac-metadata:${metadata_json}" -F templateFile=@iacscan.zip)" #echo $response result="$(echo "$response" | jq -r '.result.is_successful')" mkdir results if [[ "$result" == true ]];then matched="$(echo "$response" | jq -r '.result.rules_matched')" if [[ $matched != null ]];then stats="$(echo "$response" | jq -r '.result.severity_stats')" echo $matched | jq '["Severity","Name","Description", "Files"], (map({severity, name, description, files} ) | .[] | [.severity, .name, .description, (.files|join(";"))]) | @csv' | tr -d '\\"'> results/scan.csv awk -F'\t' -v OFS='\t' ' NR == 1 {print "Index", $0; next} {print (NR-1), $0} ' results/scan.csv > results/scan_results.csv #format console output file to display echo $matched | jq '["Severity","Name","Files"], (map({severity, name, files} ) | .[] | [.severity, .name, (.files|join(";"))]) | @csv'| column -t -s "," | tr -d '\\"' > results/formatted.csv awk -F'\t' -v OFS='\t' ' NR == 1 {print "\nIndex", $0; print "------------------------------------------------------------------------------------------------------------------------------------------------------" ; next} {print (NR-1), $0} ' results/formatted.csv > results/console_output.csv #show result on console cat results/console_output.csv #echo $CI_PROJECT_DIR mkdir $CI_PROJECT_DIR/report cp -r results/scan_results.csv $CI_PROJECT_DIR/report #ls -la $CI_PROJECT_DIR/report high="$(echo "$stats" | jq -r '.high')" med="$(echo "$stats" | jq -r '.medium')" low="$(echo "$stats" | jq -r '.low')" if [[ ( ( $failure_criteria_operator == "or" ) && ( "$high" -ge $failure_criteria_high_severity) || ( "$medium" -ge $failure_criteria_medium_severity ) || ( "$low" -ge $failure_criteria_low_severity ) ) || ( ($failure_criteria_operator == "and") && ( "$high" -ge $failure_criteria_high_severity ) && ( "$medium" -ge $failure_criteria_medium_severity ) && ( "$low" -ge $failure_criteria_low_severity ) ) ]];then echo "Prisma Cloud IaC scan failed with issues as security issues count (high:$high , medium:$med , low:$low) meets or exceeds the failure criteria (high:$failure_criteria_high_severity, medium:$failure_criteria_medium_severity, low:$failure_criteria_low_severity, operator:$failure_criteria_operator) " exit 1; else echo "Prisma Cloud IaC Scan has been successful as security issues count (high:$high, medium:$med, low:$low) does not exceed the failure criteria (high:$failure_criteria_high_severity, medium:$failure_criteria_medium_severity, low:$failure_criteria_low_severity, operator:$failure_criteria_operator)" exit 0; fi else echo "Good job! Prisma Cloud did not detect any issues." fi else error_message="$(echo "$response" | jq -r '.result.error_details')" echo "$error_message" exit 1; fi

Recommended For You