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'