build/cloudbuild-ci.yaml (128 lines of code) (raw):

# Copyright 2021 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. timeout: "3600s" # 1 hour tags: - "secure-cicd-ci" substitutions: _ATTESTOR_FULL_NAME: "projects/${PROJECT_ID}/attestors/${_ATTESTOR_NAME}" _GAR_REPO_URI: "${_DEFAULT_REGION}-docker.pkg.dev/${PROJECT_ID}/${_GAR_REPOSITORY}" options: substitution_option: 'ALLOW_LOOSE' pool: name: $_CLOUDBUILD_PRIVATE_POOL artifacts: objects: location: gs://$_CACHE_BUCKET_NAME/artifacts paths: - 'build-artifacts.json' - 'build-artifacts-notag.json' logsBucket: gs://$_CACHE_BUCKET_NAME/build_logs steps: ############################### Build Containers ########################### # Create build-installation-image - name: $_DEFAULT_REGION-docker.pkg.dev/$PROJECT_ID/$_GAR_REPOSITORY/skaffold-builder id: "build-images" entrypoint: "/bin/bash" args: - '-xe' - -c - | ./mvnw clean install skaffold config set --global local-cluster false skaffold build --default-repo=${_GAR_REPO_URI} --tag=$SHORT_SHA --cache-file='/.skaffold/cache' --file-output=/artifacts/build-artifacts.json sed -i "s|:latest||g" /artifacts/build-artifacts.json # remove the "latest" tag (complicates things later) cp /artifacts/build-artifacts.json ./build-artifacts.json # allow artifact copy mechanism to capture file (see 'artifacts' above) while read p; do echo "$p" done < /artifacts/build-artifacts.json volumes: - path: '/artifacts' name: 'artifacts' ################ Securing Artifacts Before Deployment #################### ### Container Struture Test - name: 'gcr.io/cloud-builders/docker' id: "container-structure" entrypoint: "/bin/bash" args: - '-xe' - '-c' - | # install jq and container-structure-test apt-get -y install jq curl -LO https://storage.googleapis.com/container-structure-test/latest/container-structure-test-linux-amd64 \ && chmod +x container-structure-test-linux-amd64 \ && mv container-structure-test-linux-amd64 /usr/bin/container-structure-test # remove commit hash from string (need either tag or digest, not both) sed "s|:$SHORT_SHA||g" /artifacts/build-artifacts.json > /artifacts/build-artifacts-notag.json cp /artifacts/build-artifacts-notag.json ./build-artifacts-notag.json # allow artifact copy mechanism to capture file (see 'artifacts' above) IMAGES=( $$(jq -r '.builds[].tag' /artifacts/build-artifacts-notag.json)) for IMAGE in "$${IMAGES[@]}"; do docker pull "$${IMAGE}" # pull the remote image from GAR w/ digest SHA container-structure-test test --image "$${IMAGE}" --config policies/container-structure-policy.yaml done volumes: - path: '/artifacts' name: 'artifacts' waitFor: ['build-images'] ### Container image analysis scanning (CVE Check) - name: $_DEFAULT_REGION-docker.pkg.dev/$PROJECT_ID/$_GAR_REPOSITORY/skaffold-builder id: "container-scanner" entrypoint: "/bin/bash" args: - '-xe' - -c - | gcloud config set project ${PROJECT_ID} # remove commit hash from string (need either tag or digest, not both) sed "s|:$SHORT_SHA||g" /artifacts/build-artifacts.json > /artifacts/build-artifacts-notag.json IMAGES=( $$(jq -r '.builds[].tag' /artifacts/build-artifacts-notag.json)) for IMAGE in "$${IMAGES[@]}"; do # Check CVEs against policy /signer \ -v=10 \ -alsologtostderr \ -image="$${IMAGE}" \ -policy=policies/container-analysis-policy.yaml \ -vulnz_timeout=15m \ -mode=check-only || error=true if [[ $error == true ]]; then echo "Container Analysis failed due to CVE thresholds being triggered"; exit 1; fi done volumes: - path: '/artifacts' name: 'artifacts' waitFor: - 'build-images' ### Binary Authorization - name: $_DEFAULT_REGION-docker.pkg.dev/$PROJECT_ID/$_GAR_REPOSITORY/skaffold-builder id: "binary-authorization-checkpoint" entrypoint: "/bin/bash" args: - '-xe' - -c - | gcloud config set project ${PROJECT_ID} sed "s|:$SHORT_SHA||g" /artifacts/build-artifacts.json > /artifacts/build-artifacts-notag.json IMAGES=( $$(jq -r '.builds[].tag' /artifacts/build-artifacts-notag.json)) # Public Key for attestor PUBLIC_KEY_ID=$(gcloud container binauthz attestors describe ${_ATTESTOR_FULL_NAME} \ --format='value(userOwnedGrafeasNote.publicKeys[0].id)') for IMAGE in "$${IMAGES[@]}"; do HAS_ATTESTATION=$(gcloud container binauthz attestations list \ --project="${PROJECT_ID}" \ --attestor="${_ATTESTOR_FULL_NAME}" \ --artifact-url="$${IMAGE}" \ --format="value(name)") if [ -z $${HAS_ATTESTATION} ]; then gcloud beta container binauthz attestations sign-and-create \ --artifact-url="$${IMAGE}" \ --attestor="${_ATTESTOR_FULL_NAME}" \ --keyversion=$( echo "$${PUBLIC_KEY_ID}" | sed "s|//cloudkms.googleapis.com/v1/||" ) fi done volumes: - path: '/artifacts' name: 'artifacts' waitFor: - 'container-structure' - 'container-scanner' ## Create Cloud Deploy Release to trigger render and deploy to first env - name: $_DEFAULT_REGION-docker.pkg.dev/$PROJECT_ID/$_GAR_REPOSITORY/skaffold-builder id: "cloud-deploy-release" entrypoint: "/bin/bash" args: - '-xe' - -c - | RELEASE_NAME='release-$SHORT_SHA' gcloud deploy releases create $${RELEASE_NAME} --delivery-pipeline=${_CLOUDDEPLOY_PIPELINE_NAME} --build-artifacts=/artifacts/build-artifacts.json --region=${_DEFAULT_REGION} volumes: - path: '/artifacts' name: 'artifacts' waitFor: - 'binary-authorization-checkpoint'