18F/identity-dashboard

View on GitHub
.gitlab-ci.yml

Summary

Maintainability
Test Coverage
# Jobs defined here use the idp/ci docker image from ECR by default. To find
# other available images:
#   aws ecr describe-repositories | jq '.repositories[].repositoryUri'
# Images are built via the identity-devops GitLab pipeline.

variables:
  ECR_REGISTRY: '${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com'
  IDP_WORKER_IMAGE_TAG: 'main'
  IDP_IMAGE_TAG: 'main'
  PKI_IMAGE_TAG: 'main'
  DASHBOARD_CI_SHA: 'sha256:b95a19d9b71d12604982572bad611cec59e87153c325cd54892ea82c13f70d8b'
  GITLAB_CI: 'true'

default:
  image: '${ECR_REGISTRY}/dashboard/ci:latest'

.bundle_install: &bundle_install
  - bundle check || bundle install --deployment --jobs=4 --retry=3 --without deploy development doc production --path vendor/ruby

.yarn_install: &yarn_install
  - yarn install --frozen-lockfile --ignore-engines --cache-folder .yarn-cache

.yarn_production_install: &yarn_production_install
  - yarn install --production --frozen-lockfile --ignore-engines --cache-folder .yarn-cache

.build_cache:
  - &ruby_cache
    key:
      files:
        - Gemfile.lock
    paths:
      - vendor/ruby
    policy: pull

  - &yarn_cache
    key:
      files:
        - yarn.lock
    paths:
      - .yarn-cache/
    policy: pull

  - &yarn_production_cache
    key:
      files:
        - yarn.lock
    paths:
      - .yarn-cache/
    policy: pull

  - &assets_cache
    key: $CI_COMMIT_SHA
    paths:
      - tmp/cache/assets
      - public/assets
      - public/packs
    policy: pull

stages:
  - build
  - test
  - review
  - scan

workflow:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "schedule"'
      when: never
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event" || $CI_PIPELINE_SOURCE == "external_pull_request_event"'
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event" || $CI_PIPELINE_SOURCE == "external_pull_request_event" || $CI_PIPELINE_SOURCE == "web"'
    - if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "stages/prod"'
    - if: '$CI_MERGE_REQUEST_IID || $CI_EXTERNAL_PULL_REQUEST_IID'

install:
  stage: build
  variables:
    RAILS_ENV: test
    SKIP_YARN_INSTALL: 'true'
  cache:
    - <<: *ruby_cache
      policy: pull-push
    - <<: *yarn_cache
      policy: pull-push
    - <<: *assets_cache
      policy: push
  script:
    - *bundle_install
    - *yarn_install
    - bundle exec rake assets:precompile

# Build a container image async, and don't block CI tests
# Cache intermediate images for 1 week (168 hours)
build-review-image:
  stage: review
  needs: []
  interruptible: true
  variables:
    BRANCH_TAGGING_STRING: ''
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      variables:
        BRANCH_TAGGING_STRING: "--destination ${ECR_REGISTRY}/identity-dashboard/review:main"
    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
    - if: $CI_PIPELINE_SOURCE != "merge_request_event"
      when: never
  tags:
    - build-pool
  image:
    name: gcr.io/kaniko-project/executor:debug
    entrypoint: ['']
  script:
    - mkdir -p /kaniko/.docker
    - |-
      KANIKOCFG="\"credsStore\":\"ecr-login\""
      if [ "x${http_proxy}" != "x" -o "x${https_proxy}" != "x" ]; then
        KANIKOCFG="${KANIKOCFG}, \"proxies\": { \"default\": { \"httpProxy\": \"${http_proxy}\", \"httpsProxy\": \"${https_proxy}\", \"noProxy\": \"${no_proxy}\"}}"
      fi
      KANIKOCFG="{ ${KANIKOCFG} }"
      echo "${KANIKOCFG}" > /kaniko/.docker/config.json
    - >-
      /kaniko/executor
      --context "${CI_PROJECT_DIR}"
      --dockerfile "${CI_PROJECT_DIR}/dockerfiles/dashboard_review_app.Dockerfile"
      --destination "${ECR_REGISTRY}/identity-dashboard/review:${CI_COMMIT_SHA}"
      ${BRANCH_TAGGING_STRING}
      --cache-repo="${ECR_REGISTRY}/identity-dashboard/review/cache"
      --cache-ttl=168h
      --cache=true
      --compressed-caching=false
      --build-arg "http_proxy=${http_proxy}" --build-arg "https_proxy=${https_proxy}" --build-arg "no_proxy=${no_proxy}"

build-ci-image:
  stage: build
  interruptible: true
  needs: []
  tags:
    - build-pool
  image:
    name: gcr.io/kaniko-project/executor:debug
    entrypoint: ['']
  rules:
    # Build when there are changes to the Dockerfile
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_PIPELINE_SOURCE == "merge_request_event" || $CI_PIPELINE_SOURCE == "web"'
      changes:
        compare_to: 'refs/heads/main'
        paths:
          - dockerfiles/dashboard_ci.Dockerfile
  script:
    - mkdir -p /kaniko/.docker
    - |-
      KANIKOCFG="\"credsStore\":\"ecr-login\""
      if [ "x${http_proxy}" != "x" -o "x${https_proxy}" != "x" ]; then
        KANIKOCFG="${KANIKOCFG}, \"proxies\": { \"default\": { \"httpProxy\": \"${http_proxy}\", \"httpsProxy\": \"${https_proxy}\", \"noProxy\": \"${no_proxy}\"}}"
      fi
      KANIKOCFG="{ ${KANIKOCFG} }"
      echo "${KANIKOCFG}" > /kaniko/.docker/config.json
    - >-
      /kaniko/executor
      --context "${CI_PROJECT_DIR}"
      --dockerfile "${CI_PROJECT_DIR}/dockerfiles/dashboard_ci.Dockerfile"
      --destination "${ECR_REGISTRY}/dashboard/ci:latest"
      --destination "${ECR_REGISTRY}/dashboard/ci:${CI_COMMIT_SHA}"
      --build-arg "http_proxy=${http_proxy}" --build-arg "https_proxy=${https_proxy}" --build-arg "no_proxy=${no_proxy}"

lint:
  stage: test
  needs:
    - job: install
  cache:
    - <<: *ruby_cache
    - <<: *yarn_cache
    - <<: *assets_cache
  variables:
    JUNIT_OUTPUT: 'true'
  script:
    - *bundle_install
    - *yarn_install
    - make lint
  artifacts:
    expire_in: 31d
    when: always
    paths:
      - rubocop.xml
    reports:
      junit: rubocop.xml

specs:
  stage: test
  needs:
    - job: install
  cache:
    - <<: *ruby_cache
    - <<: *yarn_cache
    - <<: *assets_cache
  variables:
    AWS_ACCESS_KEY_ID: test
    AWS_DEFAULT_REGION: us-west-2
    AWS_REGION: us-west-2
    AWS_SECRET_ACCESS_KEY: test
    CAPYBARA_WAIT_TIME_SECONDS: 5
    COVERAGE: 'true'
    COBERTURA_FORMATTER_ENABLED: 'true'
    DOCKER_DB_HOST: db-postgres
    POSTGRES_DB: identity-dashboard_test
    POSTGRES_USER: postgres_user
    POSTGRES_PASSWORD: postgres_password
    POSTGRES_HOST_AUTH_METHOD: trust
    RAILS_ENV: test
  services:
    - name: postgres:13.9
      alias: db-postgres
      command: ['--fsync=false', '--synchronous_commit=false', '--full_page_writes=false']
  script:
    - mkdir coverage
    - *bundle_install
    - *yarn_install
    - cp config/application.yml.default config/application.yml
    - bundle exec rake db:setup
    - bundle exec rspec --format documentation --format RspecJunitFormatter --out rspec.xml
  coverage: '/\(\d+.\d+\%\) covered/'
  artifacts:
    when: always
    reports:
      junit: rspec.xml
      coverage_report:
        coverage_format: cobertura
        path: coverage/coverage.xml
    expire_in: 31d
    paths:
      - coverage/coverage.xml
      - rspec.xml

review-app:
  stage: review
  allow_failure: true
  needs:
    - job: build-review-image
  resource_group: $CI_ENVIRONMENT_SLUG-dashboard.reviewapp.identitysandbox.gov
  image:
    name: dtzar/helm-kubectl:latest
  script:
    - kubectl config get-contexts
    - export CONTEXT=$(kubectl config get-contexts | grep reviewapp | awk '{print $1}' | head -1)
    - kubectl config use-context "$CONTEXT"
    - |-
      export IDP_CONFIG=$(cat <<EOF
      {
        "kubernetesReviewApp": "true",
        "postgres": {
          "sslmode": "prefer",
          "name": "idp",
          "host": "$CI_ENVIRONMENT_SLUG-login-chart-pg.review-apps"
        },
        "postgresWorker": {
          "sslmode": "prefer",
          "name": "idp",
          "host": "$CI_ENVIRONMENT_SLUG-login-chart-pg.review-apps"
        },
        "railsOffline": "true",
        "redis": {
          "irsAttemptsApiUrl": "redis://$CI_ENVIRONMENT_SLUG-login-chart-redis.review-apps:6379/2",
          "throttleUrl": "redis://$CI_ENVIRONMENT_SLUG-login-chart-redis.review-apps:6379/1",
          "url": "redis://$CI_ENVIRONMENT_SLUG-login-chart-redis.review-apps:6379"
        },
        "assetHost": "https://$CI_ENVIRONMENT_SLUG.reviewapp.identitysandbox.gov",
        "domainName": "$CI_ENVIRONMENT_SLUG.reviewapp.identitysandbox.gov",
        "loginDatacenter": "true",
        "loginDomain": "identitysandbox.gov",
        "loginEnv": "$CI_ENVIRONMENT_SLUG",
        "loginHostRole": "idp",
        "loginSkipRemoteConfig": "true",
        "pivcacServiceUrl": "https://$CI_ENVIRONMENT_SLUG.pivcac.reviewapp.identitysandbox.gov/",
        "pivcacVerifyTokenUrl": "https://$CI_ENVIRONMENT_SLUG.pivcac.reviewapp.identitysandbox.gov/",
        "dashboardUrl": "https://$CI_ENVIRONMENT_SLUG-dashboard.reviewapp.identitysandbox.gov"
      }
      EOF
      )
    - |-
      export WORKER_CONFIG=$(cat <<EOF
      {
        "kubernetesReviewApp": "true",
        "postgres": {
          "sslmode": "prefer",
          "name": "idp",
          "host": "$CI_ENVIRONMENT_SLUG-login-chart-pg.review-apps"
        },
        "postgresWorker": {
          "sslmode": "prefer",
          "name": "idp",
          "host": "$CI_ENVIRONMENT_SLUG-login-chart-pg.review-apps"
        },
        "railsOffline": "true",
        "redis": {
          "irsAttemptsApiUrl": "redis://$CI_ENVIRONMENT_SLUG-login-chart-redis.review-apps:6379/2",
          "throttleUrl": "redis://$CI_ENVIRONMENT_SLUG-login-chart-redis.review-apps:6379/1",
          "url": "redis://$CI_ENVIRONMENT_SLUG-login-chart-redis.review-apps:6379"
        },
        "assetHost": "https://$CI_ENVIRONMENT_SLUG.reviewapp.identitysandbox.gov",
        "domainName": "$CI_ENVIRONMENT_SLUG.reviewapp.identitysandbox.gov",
        "loginDatacenter": "true",
        "loginDomain": "identitysandbox.gov",
        "loginEnv": "$CI_ENVIRONMENT_SLUG",
        "loginHostRole": "worker",
        "loginSkipRemoteConfig": "true",
        "pivcacServiceUrl": "https://$CI_ENVIRONMENT_SLUG.pivcac.reviewapp.identitysandbox.gov/",
        "pivcacVerifyTokenUrl": "https://$CI_ENVIRONMENT_SLUG.pivcac.reviewapp.identitysandbox.gov/"
      }
      EOF
      )
    - |-
      export PIVCAC_CONFIG=$(cat <<EOF
      {
        "kubernetesReviewApp": "true",
        "clientCertS3Bucket": "login-gov-pivcac-public-cert-reviewapps.894947205914-us-west-2",
        "postgres": {
          "sslmode": "prefer",
          "name": "idp",
          "host": "$CI_ENVIRONMENT_SLUG-login-chart-pivcac-pg.review-apps"
        },
        "idpHost": "$CI_ENVIRONMENT_SLUG.reviewapp.identitysandbox.gov",
        "domainName": "$CI_ENVIRONMENT_SLUG.pivcac.reviewapp.identitysandbox.gov"
      }
      EOF
      )
    - |-
      export DASHBOARD_CONFIG=$(cat <<EOF
      {
        "kubernetesReviewApp": "true",
        "postgres": {
          "sslmode": "prefer",
          "name": "dashboard",
          "host": "$CI_ENVIRONMENT_SLUG-login-chart-dashboard-pg.review-apps"
        },
        "newrelic": {
          "enabled": "false"
        },
        "samlSpIssuer": "https://$CI_ENVIRONMENT_SLUG-dashboard.reviewapp.identitysandbox.gov",
        "idpUrl": "https://$CI_ENVIRONMENT_SLUG.reviewapp.identitysandbox.gov",
        "idpSpUrl": "https://$CI_ENVIRONMENT_SLUG.reviewapp.identitysandbox.gov",
        "postLogoutUrl": "https://$CI_ENVIRONMENT_SLUG-dashboard.reviewapp.identitysandbox.gov",
        "domainName": "$CI_ENVIRONMENT_SLUG-dashboard.reviewapp.identitysandbox.gov"
      }
      EOF
      )
    - git clone -b main --single-branch https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.login.gov/lg-public/identity-idp-helm-chart.git
    - >-
      helm upgrade --install --namespace review-apps
      --debug
      --set env="reviewapps-$CI_ENVIRONMENT_SLUG"
      --set idp.image.repository="${ECR_REGISTRY}/identity-idp/review"
      --set idp.image.tag="${IDP_IMAGE_TAG}"
      --set worker.image.repository="${ECR_REGISTRY}/identity-idp/review"
      --set worker.image.tag="${IDP_IMAGE_TAG}"
      --set pivcac.image.repository="${ECR_REGISTRY}/identity-pivcac/review"
      --set pivcac.image.tag="${PKI_IMAGE_TAG}"
      --set pivcac.image.pullPolicy="Always"
      --set dashboard.image.repository="${ECR_REGISTRY}/identity-dashboard/review"
      --set dashboard.image.tag="${CI_COMMIT_SHA}"
      --set dashboard.image.pullPolicy="Always"
      --set-json dashboard.config="$DASHBOARD_CONFIG"
      --set-json dashboard.enabled=true
      --set-json idp.config="$IDP_CONFIG"
      --set-json worker.config="$WORKER_CONFIG"
      --set-json pivcac.config="$PIVCAC_CONFIG"
      --set-json idp.ingress.hosts="[{\"host\": \"$CI_ENVIRONMENT_SLUG.reviewapp.identitysandbox.gov\", \"paths\": [{\"path\": \"/\", \"pathType\": \"Prefix\"}]}]"
      --set-json pivcac.ingress.hosts="[{\"host\": \"$CI_ENVIRONMENT_SLUG.pivcac.reviewapp.identitysandbox.gov\", \"paths\": [{\"path\": \"/\", \"pathType\": \"Prefix\"}]}]"
      --set-json dashboard.ingress.hosts="[{\"host\": \"$CI_ENVIRONMENT_SLUG-dashboard.reviewapp.identitysandbox.gov\", \"paths\": [{\"path\": \"/\", \"pathType\": \"Prefix\"}]}]"
      $CI_ENVIRONMENT_SLUG ./identity-idp-helm-chart
    - echo "DNS may take a while to propagate, so be patient if it doesn't show up right away"
    - echo "To access the rails console, first run 'aws-vault exec sandbox-power -- aws eks update-kubeconfig --name reviewapp'"
    - echo "Then run aws-vault exec sandbox-power -- kubectl exec -it service/$CI_ENVIRONMENT_SLUG-login-chart-idp -n review-apps -- /app/bin/rails console"
    - echo "Address of IDP review app:"
    - echo https://$CI_ENVIRONMENT_SLUG.reviewapp.identitysandbox.gov
    - echo "Address of PIVCAC review app:"
    - echo https://$CI_ENVIRONMENT_SLUG.pivcac.reviewapp.identitysandbox.gov
    - echo "Address of Dashboard review app:"
    - echo https://$CI_ENVIRONMENT_SLUG-dashboard.reviewapp.identitysandbox.gov
  environment:
    name: review/$CI_COMMIT_REF_NAME
    url: https://$CI_ENVIRONMENT_SLUG-dashboard.reviewapp.identitysandbox.gov
    on_stop: stop-review-app
    auto_stop_in: 2 days
  rules:
    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
    - if: $CI_PIPELINE_SOURCE != "merge_request_event"
      when: never

stop-review-app:
  resource_group: $CI_ENVIRONMENT_SLUG-dashboard.reviewapp.identitysandbox.gov
  script:
    - export CONTEXT=$(kubectl config get-contexts | grep reviewapp | awk '{print $1}' | head -1)
    - kubectl config use-context "$CONTEXT"
    - helm uninstall --namespace review-apps $CI_ENVIRONMENT_SLUG
  stage: review
  image:
    name: dtzar/helm-kubectl:latest
  needs:
    - job: review-app
  environment:
    name: review/$CI_COMMIT_REF_NAME
    action: stop
  when: manual
  rules:
    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
    - if: $CI_PIPELINE_SOURCE != "merge_request_event"
      when: never

include:
  - template: Jobs/SAST.gitlab-ci.yml
  - template: Jobs/Dependency-Scanning.gitlab-ci.yml
  - template: Security/Secret-Detection.gitlab-ci.yml

secret_detection:
  stage: scan
  allow_failure: false
  variables:
    SECRET_DETECTION_EXCLUDED_PATHS: 'keys.example,config/artifacts.example,public/acuant/*/opencv.min.js,tmp/0.0.0.0-3000.key'
    SECRET_DETECTION_REPORT_FILE: 'gl-secret-detection-report.json'
  rules:
    - if: $SECRET_DETECTION_DISABLED
      when: never
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_TAG'
      variables:
        SECRET_DETECTION_SHOULD_RUN: "true"
    - if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main"
      variables:
        SECRET_DETECTION_SHOULD_RUN: "true"
        SECRET_DETECTION_LOG_OPTIONS: origin/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}..HEAD
    - if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME != "main" && $CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_NAME == "main"
      variables:
        SECRET_DETECTION_SHOULD_RUN: "true"
        SECRET_DETECTION_LOG_OPTIONS: origin/${CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_NAME}..HEAD
  before_script:
    - apk add --no-cache jq
    - git fetch origin --quiet
  script:
    - |
      if [ -n "$SECRET_DETECTION_SHOULD_RUN" ]; then
        /analyzer run
        if [ -f "$SECRET_DETECTION_REPORT_FILE" ]; then
          # check if '{ "vulnerabilities": [], ..' is empty in the report file if it exists
          if [ "$(jq ".vulnerabilities | length" $SECRET_DETECTION_REPORT_FILE)" -gt 0 ]; then
            echo "Vulnerabilities detected. Please analyze the artifact $SECRET_DETECTION_REPORT_FILE produced by the 'secret-detection' job."
            exit 80
          fi
        else
          echo "Artifact $SECRET_DETECTION_REPORT_FILE does not exist. The 'secret-detection' job likely didn't create one. Hence, no evaluation can be performed."
        fi
      else
        echo "Skipping because this is not a PR or is not targeting main"
        exit 0
      fi

# Export the automated ECR scan results into a format Gitlab can use
# Report schema https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/container-scanning-report-format.json
ecr-scan-review-app:
  extends: .container_scan_template
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
    - if: $CI_PIPELINE_SOURCE != "merge_request_event"
      when: never
  needs:
    - job: build-review-image
  stage: scan
  variables:
    ecr_repo: identity-dashboard/review

ecr-scan-ci:
  extends: .container_scan_template
  rules:
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_PIPELINE_SOURCE == "merge_request_event" || $CI_PIPELINE_SOURCE == "web"'
      changes:
        compare_to: 'refs/heads/main'
        paths:
          - dockerfiles/dashboard_ci.Dockerfile
  needs:
    - job: build-ci-image
  stage: scan
  variables:
    ecr_repo: dashboard/ci

.container_scan_template:
  interruptible: true
  allow_failure: true
  tags:
    - build-pool
  image:
    name: amazon/aws-cli
    entrypoint: [""]
  before_script:
    - curl -LO https://github.com/jqlang/jq/releases/download/jq-1.6/jq-linux64
    - chmod +x jq-linux64
    - mv jq-linux64 /usr/local/bin/jq
  script:
    - >
      while true; do
        SCAN_STATUS=$(aws ecr describe-image-scan-findings --repository-name ${ecr_repo} --image-id imageTag=$CI_COMMIT_SHA --query 'imageScanStatus.status' --output text || true)
        if echo "$SCAN_STATUS" | grep -q "ACTIVE"; then
          echo "Scan Complete"
          break
        elif echo "$SCAN_STATUS" | grep -q "FAILED"; then
          echo "ECR scan failed"
          exit 1
        else
          echo "Waiting for ECR scan to complete"
          sleep 15
        fi
      done
    - SCAN_FINDINGS=$(aws ecr describe-image-scan-findings --repository-name ${ecr_repo} --image-id imageTag=$CI_COMMIT_SHA)
    - echo $SCAN_FINDINGS
    - >
      echo $SCAN_FINDINGS |
      jq -r 'if (.imageScanFindings.enhancedFindings | length > 0) then
      {
        "version": "15.0.4",
        "scan": {
          "start_time": (.imageScanFindings.imageScanCompletedAt | sub("\\.[0-9]+"; "") | strptime("%Y-%m-%dT%H:%M:%S%z") | strftime("%Y-%m-%dT%H:%M:%S")),
          "end_time": (.imageScanFindings.imageScanCompletedAt | sub("\\.[0-9]+"; "") | strptime("%Y-%m-%dT%H:%M:%S%z") | strftime("%Y-%m-%dT%H:%M:%S")),
          "scanner": {
            "id": "clair",
            "name": "Amazon ECR Image Scan",
            "version": "1.0.0",
            "vendor": {
              "name": "Amazon Web Services"
            }
          },
          "analyzer": {
            "id": "clair",
            "name": "Amazon ECR Image Scan",
            "version": "1.0.0",
            "vendor": {
              "name": "Amazon Web Services"
            }
          },
          "status": "success",
          "type": "container_scanning"
        },
        "vulnerabilities": [
          .imageScanFindings.enhancedFindings[] |
          {
            "id": .packageVulnerabilityDetails.vulnerabilityId,
            "name": .title,
            "description": .description,
            "severity": (if .severity == "HIGH" then "High"
                        elif .severity == "MEDIUM" then "Medium"
                        elif .severity == "LOW" then "Low"
                        elif .severity == "CRITICAL" then "Critical"
                        elif .severity == "INFORMATIONAL" then "Info"
                        elif .severity == "UNTRIAGED" then "Info"
                        else "Unknown" end),
            "solution": .remediation.recommendation.text,
            "identifiers": [
              {
                "type": "cve",
                "name": .packageVulnerabilityDetails.vulnerabilityId,
                "url": .packageVulnerabilityDetails.sourceUrl,
                "value": .packageVulnerabilityDetails.vulnerabilityId
              }
            ],
            "links": [
              {
                "name": .packageVulnerabilityDetails.vulnerabilityId,
                "url": .packageVulnerabilityDetails.sourceUrl
              }
            ],
            "location": {
              "dependency": {
                "package": {
                  "name": .packageVulnerabilityDetails.vulnerablePackages[0].name
                },
                "version": .packageVulnerabilityDetails.vulnerablePackages[0].version
              },
              "operating_system": .resources[0].details.awsEcrContainerImage.platform,
              "image": .resources[0].id
            }
          }
        ]
      }
      else
      {
        "version": "15.0.4",
        "scan": {
          "start_time": (now | strftime("%Y-%m-%dT%H:%M:%S")),
          "end_time": (now | strftime("%Y-%m-%dT%H:%M:%S")),
          "scanner": {
            "id": "clair",
            "name": "Amazon ECR Image Scan",
            "version": "1.0.0",
            "vendor": {
              "name": "Amazon Web Services"
            }
          },
          "analyzer": {
            "id": "clair",
            "name": "Amazon ECR Image Scan",
            "version": "1.0.0",
            "vendor": {
              "name": "Amazon Web Services"
            }
          },
          "status": "success",
          "type": "container_scanning"
        },
        "vulnerabilities": []
      }
      end' > gl-container-scanning-report.json
  artifacts:
    paths:
      - gl-container-scanning-report.json
    reports:
      container_scanning: gl-container-scanning-report.json