diff --git a/README.md b/README.md index 995a6b9..d82032c 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Here are the projects/services we make use of in this Blueprint: * [Diagrams](docs/diagrams.md) * [Kubernetes RBAC via Google Groups membership demonstration](docs/Google-Groups-and-RBAC.md) * [Development](/docs/development.md) +* [Continuous Integration with Cloud Build](/docs/cicd.md) * [Known Issues and Limitations](#known-issues-and-limitations) * [Helpful Links](#helpful-links) diff --git a/_helpers/admin_project_setup.sh b/_helpers/admin_project_setup.sh index 540bd8d..73cebb2 100755 --- a/_helpers/admin_project_setup.sh +++ b/_helpers/admin_project_setup.sh @@ -31,8 +31,12 @@ echo "Continuing in 10 seconds. Ctrl+C to cancel" sleep 10 echo "=> Creating project inside the folder ${TF_VAR_folder_id}" -gcloud projects create "${TF_ADMIN_PROJECT}" \ - --folder "${TF_VAR_folder_id}" +project_exists=`gcloud projects list --filter "${TF_ADMIN_PROJECT}" | grep "${TF_ADMIN_PROJECT}" | wc -l | tr -d ' '` +if [ "$project_exists" = "0" ];then + gcloud projects create "${TF_ADMIN_PROJECT}" --folder "${TF_VAR_folder_id}" +else + echo "Project already exists. Skipping" +fi echo "=> Linking ${TF_VAR_billing_account} Billing Account to your project" gcloud beta billing projects link "${TF_ADMIN_PROJECT}" \ diff --git a/_helpers/build-infra.sh b/_helpers/build-infra.sh index 590fdf1..380a1d1 100755 --- a/_helpers/build-infra.sh +++ b/_helpers/build-infra.sh @@ -14,17 +14,50 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Set up the admin resources run -echo 'Setting up the Terraform Admin Project' +usage() { + echo "Usage: build-infra [-ach] " + echo + echo "Builds infrastructure of pci-gke-blueprint" + echo + echo " -c (optional) Running script in continuous integration and skipping service account creation" + echo " -a (optional) Admin project setup will be skipped" + echo " -h (optional) Print this help menu" +} + +unset run_type skip_admin_project +skip_admin_project=false + +while getopts 'ach' c +do + case $c in + c) run_type="cicd";; + a) skip_admin_project=true;; + h|?) + usage + exit 2 + ;; + esac +done # Source the environment setup file you created previously source ./workstation.env -# Create the Admin project -./_helpers/admin_project_setup.sh +if [ "$skip_admin_project" = "false" ];then + # Set up the admin resources run + echo 'Setting up the Terraform Admin Project' + + # Create the Admin project + ./_helpers/admin_project_setup.sh +fi -# Create the Terraform service account -./_helpers/setup_service_account.sh +if [ "$run_type" = "cicd" ];then + # Prepare CloudBuild service account + cloud_build_service_account=`gcloud config get-value account` + ./_helpers/setup_cloud_build_service_account.sh $cloud_build_service_account +else + # Create the Terraform service account + ./_helpers/setup_service_account.sh +fi # run terraform sed "s//${TF_ADMIN_BUCKET}/" terraform/infrastructure/backend.tf.example > terraform/infrastructure/backend.tf @@ -32,6 +65,10 @@ pushd terraform/infrastructure terraform init terraform plan -out terraform.out terraform apply terraform.out +if [ $? -ne 0 ];then + echo "Terraform apply failed. Aborting..." + exit 1 +fi popd # DNS diff --git a/_helpers/setup_cloud_build_service_account.sh b/_helpers/setup_cloud_build_service_account.sh new file mode 100755 index 0000000..af9f96b --- /dev/null +++ b/_helpers/setup_cloud_build_service_account.sh @@ -0,0 +1,123 @@ +#!/bin/bash +# Copyright 2020 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 +# +# http://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. + +cloud_build_service_account=$1 + +# Fail fast when a command fails or a variable is undefined +set -eu + +echo "" +echo "Preparing to execute with the following values:" +echo "===================================================" +echo "Admin Project: ${TF_ADMIN_PROJECT:?}" +echo "Organization: ${TF_VAR_org_id:?}" +echo "Billing Account: ${TF_VAR_billing_account:?}" +echo "Folder: ${TF_VAR_folder_id:?}" +echo "State Bucket: ${TF_ADMIN_BUCKET:?}" +echo "Cloud Build Service Account: ${cloud_build_service_account:?}" +echo "===================================================" +echo "" +echo "Continuing in 10 seconds. Ctrl+C to cancel" +sleep 10 + + +echo "=> Binding IAM roles to service account" + +# Add Viewer permissions for the Terraform Admin project +gcloud projects add-iam-policy-binding "${TF_ADMIN_PROJECT}" \ + --member "serviceAccount:$cloud_build_service_account" \ + --role roles/viewer + +# Enable Access Context Manager API for the Terraform Admin project +gcloud services --project ${TF_ADMIN_PROJECT} enable accesscontextmanager.googleapis.com + +# Add Storage Admin permissions for the Terraform Admin project +gcloud projects add-iam-policy-binding "${TF_ADMIN_PROJECT}" \ + --member "serviceAccount:$cloud_build_service_account" \ + --role roles/storage.admin + +# Add accesscontextmanager.policyAdmin +gcloud organizations add-iam-policy-binding "${TF_VAR_org_id}" \ + --member "serviceAccount:$cloud_build_service_account" \ + --role="roles/accesscontextmanager.policyAdmin" + +# Add resourcemanager.organizationAdmin +gcloud organizations add-iam-policy-binding "${TF_VAR_org_id}" \ + --member "serviceAccount:$cloud_build_service_account" \ + --role="roles/resourcemanager.organizationAdmin" + +# Add orgpolicy.policyAdmin +gcloud organizations add-iam-policy-binding "${TF_VAR_org_id}" \ + --member "serviceAccount:$cloud_build_service_account" \ + --role="roles/orgpolicy.policyAdmin" + +# Add billing admin +gcloud organizations add-iam-policy-binding "${TF_VAR_org_id}" \ + --member "serviceAccount:$cloud_build_service_account" \ + --role="roles/billing.admin" + +# Add Storage Admin permissions to entire Folder +gcloud resource-manager folders add-iam-policy-binding "${TF_VAR_folder_id}" \ + --member "serviceAccount:$cloud_build_service_account" \ + --role roles/storage.admin + +# Add Container cluster admin permissions to entire Folder +gcloud resource-manager folders add-iam-policy-binding "${TF_VAR_folder_id}" \ + --member "serviceAccount:$cloud_build_service_account" \ + --role roles/container.admin + +# Add serviceusage.serviceUsageAdmin +gcloud resource-manager folders add-iam-policy-binding "${TF_VAR_folder_id}" \ + --member "serviceAccount:$cloud_build_service_account" \ + --role roles/serviceusage.serviceUsageAdmin + +# Add IAM serviceAccountUser permissions to entire Folder +gcloud resource-manager folders add-iam-policy-binding "${TF_VAR_folder_id}" \ + --member "serviceAccount:$cloud_build_service_account" \ + --role roles/iam.serviceAccountUser + +# Add Project Creator permissions to entire Folder +gcloud resource-manager folders add-iam-policy-binding "${TF_VAR_folder_id}" \ + --member "serviceAccount:$cloud_build_service_account" \ + --role roles/resourcemanager.projectCreator + +gcloud resource-manager folders add-iam-policy-binding "${TF_VAR_folder_id}" \ + --member "serviceAccount:$cloud_build_service_account" \ + --role roles/resourcemanager.folderIamAdmin + +# Add Billing Project Manager permissions to all projects in Folder +gcloud resource-manager folders add-iam-policy-binding "${TF_VAR_folder_id}" \ + --member "serviceAccount:$cloud_build_service_account" \ + --role roles/billing.projectManager + +# Add Compute Admin permissions to all projects in Folder +gcloud resource-manager folders add-iam-policy-binding "${TF_VAR_folder_id}" \ + --member "serviceAccount:$cloud_build_service_account" \ + --role roles/compute.admin + +# Add Shared VPC Admin permissions to all projects in Folder +gcloud resource-manager folders add-iam-policy-binding "${TF_VAR_folder_id}" \ + --member "serviceAccount:$cloud_build_service_account" \ + --role roles/compute.xpnAdmin + +echo "=> Setting up IAM roles for StackDriver Logging" + +gcloud resource-manager folders add-iam-policy-binding "${TF_VAR_folder_id}" \ + --member "serviceAccount:$cloud_build_service_account" \ + --role roles/logging.configWriter + +echo "" +echo "Service Account set up successfully" +echo "" \ No newline at end of file diff --git a/cicd/cloudbuild.yml b/cicd/cloudbuild.yml new file mode 100644 index 0000000..f7e950f --- /dev/null +++ b/cicd/cloudbuild.yml @@ -0,0 +1,158 @@ +# Copyright 2020 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. + +substitutions: + _GOOGLE_GROUPS_DOMAIN: '' + _TF_ADMIN_BUCKET: '' + _TF_ADMIN_PROJECT: '' + _TF_VAR_BILLING_ACCOUNT: '' + _TF_VAR_FOLDER_ID: '' + _TF_VAR_FRONTEND_ZONE_DNS_NAME: '' + _TF_VAR_GSUITE_ID: '' + _TF_VAR_ORG_ID: '' + _TF_VAR_PROJECT_PREFIX: '' + _GCR_PROJECT_ID: '' + _REPORTS_BUCKET: '' + _DESTROY_INFRA_AFTER_CREATE: '' + + +timeout: 3000s +steps: +- id: 'Build Infra' + name: 'gcr.io/cloud-foundation-cicd/cft/developer-tools:0' + waitFor: ['-'] + entrypoint: 'sh' + env: + - GOOGLE_GROUPS_DOMAIN=${_GOOGLE_GROUPS_DOMAIN} + - TF_ADMIN_BUCKET=${_TF_ADMIN_BUCKET} + - TF_ADMIN_PROJECT=${_TF_ADMIN_PROJECT} + - TF_VAR_billing_account=${_TF_VAR_BILLING_ACCOUNT} + - TF_VAR_folder_id=${_TF_VAR_FOLDER_ID} + - TF_VAR_frontend_zone_dns_name=${_TF_VAR_FRONTEND_ZONE_DNS_NAME} + - TF_VAR_gsuite_id=${_TF_VAR_GSUITE_ID} + - TF_VAR_org_id=${_TF_VAR_ORG_ID} + - TF_VAR_project_prefix=${_TF_VAR_PROJECT_PREFIX} + args: + - '-c' + - | + cloud_build_service_account=`gcloud config get-value account` + cp workstation.env.example workstation.env + sed -i "s/YOUR_ORG_ID/${_TF_VAR_ORG_ID}/g" workstation.env + sed -i "s/YOUR_GSUITE_ID/${_TF_VAR_GSUITE_ID}/g" workstation.env + sed -i "s/YOUR_BILLING_ACCOUNT_ID/${_TF_VAR_BILLING_ACCOUNT}/g" workstation.env + sed -i "s/YOUR_PROJECT_FOLDER/${_TF_VAR_FOLDER_ID}/g" workstation.env + sed -i "s/demo-pci/${_TF_VAR_PROJECT_PREFIX}/g" workstation.env + sed -i "/export TF_ADMIN_PROJECT/c\export TF_ADMIN_PROJECT=${_TF_ADMIN_PROJECT}" workstation.env + sed -i "s/terraform-admin-/${_TF_ADMIN_BUCKET}/g" workstation.env + sed -i "/TF_VAR_frontend_zone_dns_name=/c\export TF_VAR_frontend_zone_dns_name=\"${_TF_VAR_FRONTEND_ZONE_DNS_NAME}\"" workstation.env + sed -i "/GOOGLE_GROUPS_DOMAIN=/c\GOOGLE_GROUPS_DOMAIN=\"${_GOOGLE_GROUPS_DOMAIN}\"" workstation.env + sed -i '/GOOGLE_APPLICATION_CREDENTIALS/d' workstation.env + sed -i "/TF_VAR_terraform_service_account/c\export TF_VAR_terraform_service_account=\"serviceAccount:$cloud_build_service_account\"" workstation.env + cat workstation.env + source workstation.env + ./_helpers/build-infra.sh -c + +- id: 'Write input file' + waitFor: ['Build Infra'] + name: gcr.io/cloud-foundation-cicd/cft/developer-tools:0 + entrypoint: '/bin/sh' + args: + - '-c' + - | + cloud_build_service_account=`gcloud config get-value account` + + cat < /workspace/inputs.yml + gcp_project_id: "${_TF_VAR_PROJECT_PREFIX}-in-scope" + gcp_gke_locations: + - 'us-central1' + gce_zones: + - 'us-central1' + - 'us-central1-a' + - 'us-central1-b' + - 'us-central1-c' + - 'us-central1-d' + - 'us-central1-e' + - 'us-central1-f' + cis_version: "" + cis_url: "" + fw_change_control_id_regex: 'CID:' + fw_override_control_id_regex: 'CID:' + logging_viewer_list: [] + logging_admin_list: [] + project_owners_list: ["serviceAccount:$cloud_build_service_account"] + gcs_logging_buckets: [] + cai_inventory_bucket_name: "" + cai_inventory_file_path: "" + cai_inventory_age_seconds: 60 + gcs_pii_buckets: [] + kms_regions_list: + - "us-central1" + kms_admins_list: [] + kms_encrypters_list: [] + kms_decrypters_list: [] + kms_encrypterdecrypters_list: [] + kms_rotation_period_seconds: 7776000 + environment_label: 'env' + memorystore_admins_list: [] + cloudsql_admins_list: [] + cloudsql_clients_list: [] + bq_admins_list: [] + spanner_admins_list: [] + environment_label: "goog-gke-node" + allow_all_tcp_ports: [] + allow_all_udp_ports: [] + EOF + cat /workspace/inputs.yml + +- id: 'Run PCI Profile on in-scope project' + waitFor: ['Write input file'] + name: gcr.io/${_GCR_PROJECT_ID}/inspec-gcp-pci-profile:v3.2.1-3 + entrypoint: '/bin/sh' + args: + - '-c' + - | + inspec exec /share/. -t gcp:// \ + --input-file /workspace/inputs.yml \ + --reporter cli json:/workspace/pci_report.json html:/workspace/pci_report.html | tee out.json + +- id: 'Store json Report' + waitFor: ['Run PCI Profile on in-scope project'] + name: gcr.io/cloud-builders/gsutil + args: + - cp + - /workspace/pci_report.json + - gs://${_REPORTS_BUCKET}/pci_report-${BUILD_ID}.json + +- id: 'Store HTML Report' + waitFor: ['Run PCI Profile on in-scope project'] + name: gcr.io/cloud-builders/gsutil + args: + - cp + - /workspace/pci_report.html + - gs://${_REPORTS_BUCKET}/pci_report-${BUILD_ID}.html + +- id: 'Destroy Infra' + waitFor: ['Store HTML Report'] + name: gcr.io/cloud-foundation-cicd/cft/developer-tools:0 + entrypoint: '/bin/sh' + args: + - '-c' + - | + if [ "${_DESTROY_INFRA_AFTER_CREATE}" = "true" ];then + cat workstation.env + source workstation.env + cd terraform/infrastructure + terraform destroy -auto-approve + fi + \ No newline at end of file diff --git a/docs/cicd.md b/docs/cicd.md new file mode 100644 index 0000000..722796a --- /dev/null +++ b/docs/cicd.md @@ -0,0 +1,66 @@ +# CICD for PCI-GKE Blueprint + +The PCI-GKE Blueprint can be deployed in a CICD pipeline using Cloud Build to continuously +deploy and validate the infrastructure on GCP. In addition to deploying the infrastructure +using Terraform, Chef InSpec is used to run a profile of compliance controls to validate +the in-scope project against the PCI-DSS benchmark (https://github.com/GoogleCloudPlatform/inspec-gcp-pci-profile). + +After running the InSpec profile against the in-scope project Cloud Build will store the +report in json and html format inside a Cloud Storage bucket for review. + +## Setup steps + +### Inspec-gcp-pci-benchmark Container +Before setting up the pipeline in Cloud Build a container needs to be created that contains +the Inspec profile. Clone the PCI benchmark repo into a directory outside the pci-gke +blueprint: + +`git clone https://github.com/GoogleCloudPlatform/inspec-gcp-pci-profile.git` + +A container needs to be created which runs the InSpec profile in the pipeline. +Build a container by running: + +`docker build . -t gcr.io//inspec-gcp-pci-profile:` + +Push the Docker container to the Google Container Registry: + +`docker push gcr.io//inspec-gcp-pci-profile:` + +### Prerequisites +* The Cloud Build service account requires the organization admin and folder admin on +organization level. +* The Cloud Build service account needs to have the Cloud Billing User role on the Billing account. +* The Cloud Build service account needs to have the Project Creator role on folder level. +* The Cloud Build service account needs to be a domain owner of the front-end domain that you +specify in the workstation.env file. +* Enable the Context Manager and Cloud Billing API in the project in which you create the Cloud Build +pipeline. +* If you are specifying an existing admin project in the pipeline setup (parameter `_TF_ADMIN_PROJECT`), +make sure the Cloud Build service account has the Project Owner role on the admin project. + +### Cloud Build Pipeline setup +Navigate to Cloud Build in the GCP console and wait until the API is enabled (if not already done). +Connect to the repository that contains the code for the PCI-GKE-blueprint (e.g. a fork of the +upstream repository or a separate clone in Google Cloud Source Repositories). + +Define a trigger for Cloud Build. For the Build configuration enter `cicd/cloudbuild.yml`. +Create the following Substitution variables and enter values according to the workstation.env file: +* _GCR_PROJECT_ID +* _GOOGLE_GROUPS_DOMAIN +* _REPORTS_BUCKET +* _TF_ADMIN_BUCKET +* _TF_ADMIN_PROJECT +* _TF_VAR_BILLING_ACCOUNT +* _TF_VAR_FOLDER_ID +* _TF_VAR_FRONTEND_ZONE_DNS_NAME +* _TF_VAR_GSUITE_ID +* _TF_VAR_ORG_ID +* _TF_VAR_PROJECT_PREFIX +* _DESTROY_INFRA_AFTER_CREATE + +The variable _REPORTS_BUCKET is the GCS bucket which will contain the InSpec report files in json +and html format. Make sure that the Cloud Build service account has the Cloud Storage Admin role +on the bucket that you specify. + +The variable _DESTROY_INFRA_AFTER_CREATE is boolean (`false` or `true`) and determines whether the +infrastructure should be destroyed as final step of the pipeline execution. \ No newline at end of file diff --git a/workstation.env.example b/workstation.env.example index e15419b..06ba4b5 100644 --- a/workstation.env.example +++ b/workstation.env.example @@ -13,7 +13,7 @@ export TF_VAR_billing_account=YOUR_BILLING_ACCOUNT_ID # continuing export TF_VAR_folder_id=YOUR_PROJECT_FOLDER -# Override the following project prefix if desired +# Override the following project prefix if desired (max 17 characters) export TF_VAR_project_prefix=demo-pci # project names