diff --git a/examples/guardrails/folder-factory/Jenkinsfile b/examples/guardrails/folder-factory/Jenkinsfile new file mode 100644 index 0000000..f69e778 --- /dev/null +++ b/examples/guardrails/folder-factory/Jenkinsfile @@ -0,0 +1,81 @@ +pipeline { + agent any + environment { + BUCKET_PATH = credentials('backend-path') + REPO_FULL_NAME = credentials('bucket-repo') + PROJECT_NAME = credentials('project-name') + GCP_PROJECT_NUMBER = credentials('project-id') + workload_identity_pool_id = credentials('wif_pool_id') + workload_identity_pool_provider_id = credentials('wif_pool_provider_id') + SERVICE_ACCOUNT_NAME = credentials('sa-name') + policy_file_path = credentials('policy_file_path') + } + options { + skipDefaultCheckout(true) + } + stages { + stage('clean workspace') { + steps { + cleanWs() + } + } + stage('WIF') { + steps { + withCredentials([file(variable: 'ID_TOKEN_FILE', credentialsId: 'gcp')]) { + writeFile file: "$WORKSPACE_TMP/creds.json", text: """ + { + "type": "external_account", + "audience": "//iam.googleapis.com/projects/${GCP_PROJECT_NUMBER}/locations/global/workloadIdentityPools/${workload_identity_pool_id}/providers/${workload_identity_pool_provider_id}", + "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", + "token_url": "https://sts.googleapis.com/v1/token", + "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${SERVICE_ACCOUNT_NAME}@${PROJECT_NAME}.iam.gserviceaccount.com:generateAccessToken", + "credential_source": { + "file": "$ID_TOKEN_FILE", + "format": { + "type": "text" + } + } + } + """ + sh ''' + gcloud auth login --brief --cred-file=$WORKSPACE_TMP/creds.json + ''' +} + } + } + stage('checkout') { + steps { + checkout scm + } + } + stage('Terraform Plan') { + steps { + + sh ''' + cd guardrails/folder-factory + terraform init -backend-config="bucket=${BUCKET_PATH}" -backend-config="prefix=${REPO_FULL_NAME}" + terraform plan -input=false -out ffjenkins.tfplan + ''' + } + } + stage('Terraform Validate') { + steps { + sh ''' + cd guardrails/folder-factory + terraform show -json "ffjenkins.tfplan" > "ffjenkins.json" + gcloud source repos clone gcp-policies "${policy_file_path}" --project="${GCP_PROJECT_NUMBER}" + gcloud beta terraform vet "ffjenkins.json" --policy-library="${policy_file_path}" --project="${GCP_PROJECT_NUMBER}" + ''' + } + } + stage('Terraform Apply') { + steps { + sh ''' + cd guardrails/folder-factory + terraform apply -auto-approve + ''' + } + } + } +} + diff --git a/examples/guardrails/folder-factory/README.md b/examples/guardrails/folder-factory/README.md index a139948..43dd0b0 100644 --- a/examples/guardrails/folder-factory/README.md +++ b/examples/guardrails/folder-factory/README.md @@ -57,3 +57,44 @@ iam: ``` Every folder is defined with its own yaml file located in the following [Folder](data/folder). + +## How to run this stage +### Prerequisites + +Workload Identity setup between the folder factory gitlab repositories and the GCP Identity provider configured with a service account containing required permissions to create folders and their organizational policies. There is a sample code provided in “folder.yaml.sample” to create a folder and for terraform to create a folder minimum below permissions are required. +“Folder Creator” or “Folder Admin” at org level +“Organization Policy Admin” at org level + + +### Installation Steps + +Step 1: Create a bucket for terraform backend on the GCP environment. +Step 2: Create Jenkins Pipeline on Jenkins. +Step 3: Configure the below variables on Jenkins Credentials. + +### Terraform config validator +The pipeline has an option to utilise the integrated config validator (gcloud terraform vet) to impose constraints on your terraform configuration. You have to provide the policy-library repo URL to $POLICY_LIBRARY_REPO variable. See the below for details on the Variables to be set on the CI/CD pipeline. + + +| Variable | | Example Value | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------- | +| PROJECT_NAME | The project containing the service account that has permission to communicate with the WIF provider. Should be created as part of Project Factory. | jenkins-connect-prj | +| GCP_PROJECT_NUMBER | Project number for the project that hosts the WIF provider | 107999111999 | +| SERVICE_ACCOUNT_NAME | The name of the service account that will be used to deploy. Must be hosted in PROJECT_NAME. | jenkins-sa | +| BUCKET_PATH | A state bucket that will hold the terraform state. This bucket must previously exist and the service account must have permission to read/write to it. | jenkins-gcs-state-bucket-name | +| policy_file_path | https://github.com/GoogleCloudPlatform/policy-library | The public repo where the policies are hosted | +| workload_identity_pool_id | | jenkins-test-pool | +| workload_identity_provider_id | | jenkins-test-provider | | + +* Once the prerequisites are set up, any commit to the remote main branch with changes to *.tf, *.tfvars, data/*, modules/* files should trigger the pipeline. + + +### Pipeline Workflow Overview +The complete workflow comprises of 6 stages and 2 before-script jobs + * Stages: + * Clean workspace : This step cleans the previous packages from Jenkins workspace + * WIF : Execute the Workload Identity Federation script and generate credential file. + * Terraform plan: Runs terraform plan and saves the plan and json version of the plan as artifacts, this depends on the branch + * Terraform validate: Runs gcloud terraform vet against the terraform code with the constraints in the specified repository. + * apply: This is executed for specified list of branches, currently main/master + diff --git a/examples/guardrails/project-factory/Jenkinsfile b/examples/guardrails/project-factory/Jenkinsfile new file mode 100644 index 0000000..5543a60 --- /dev/null +++ b/examples/guardrails/project-factory/Jenkinsfile @@ -0,0 +1,49 @@ +pipeline { + agent any + environment { + BUCKET_PATH = credentials('backend-path') + policy_file_path = credentials('policy_file_path') + } + options { + skipDefaultCheckout(true) + } + stages { + stage('clean workspace') { + steps { + cleanWs() + } + } + stage('checkout') { + steps { + checkout scm + } + } + stage('Terraform Plan') { + steps { + sh ''' + cd guardrails/project-factory + terraform init -backend-config="bucket=${BUCKET_PATH}" -backend-config="prefix=project-factory-staging-tfstate" -var-file="staging.tfvars" + terraform plan -var-file="staging.tfvars" -out pfjenkins.tfplan + ''' + } + } + stage('Terraform Validate') { + steps { + sh ''' + cd guardrails/project-factory + terraform show -json "pfjenkins.tfplan" > "ffjenkins.json" + gcloud source repos clone gcp-policies "${policy_file_path}" --project="${GCP_PROJECT_NUMBER}" + gcloud beta terraform vet "pfjenkins.json" --policy-library="${policy_file_path}" --project="${GCP_PROJECT_NUMBER}" + ''' + } + } + stage('Terraform Apply') { + steps { + sh ''' + cd guardrails/project-factory + terraform apply -var-file="staging.tfvars" -auto-approve + ''' + } + } + } +} diff --git a/examples/guardrails/project-factory/README.md b/examples/guardrails/project-factory/README.md index f901e7f..bd5b101 100644 --- a/examples/guardrails/project-factory/README.md +++ b/examples/guardrails/project-factory/README.md @@ -73,3 +73,45 @@ repo_branch: dev ``` Every project is defined with its own file located in the [Project Folder](data/projects). + +## How to run this stage +### Prerequisites + +Workload Identity setup between the folder factory gitlab repositories and the GCP Identity provider configured with a service account containing required permissions to create folders and their organizational policies. There is a sample code provided in “folder.yaml.sample” to create a folder and for terraform to create a folder minimum below permissions are required. +“Folder Creator” or “Folder Admin” at org level +“Organization Policy Admin” at org level + + +### Installation Steps + +Step 1: Create a bucket for terraform backend on the GCP environment. +Step 2: Create Jenkins Pipeline on Jenkins. +Step 3: Configure the below variables on Jenkins Credentials. + +### Terraform config validator +The pipeline has an option to utilise the integrated config validator (gcloud terraform vet) to impose constraints on your terraform configuration. You have to provide the policy-library repo URL to $POLICY_LIBRARY_REPO variable. See the below for details on the Variables to be set on the CI/CD pipeline. + + +| Variable | | Example Value | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------- | +| PROJECT_NAME | The project containing the service account that has permission to communicate with the WIF provider. Should be created as part of Project Factory. | jenkins-connect-prj | +| GCP_PROJECT_NUMBER | Project number for the project that hosts the WIF provider | 107999111999 | +| SERVICE_ACCOUNT_NAME | The name of the service account that will be used to deploy. Must be hosted in PROJECT_NAME. | jenkins-sa | +| BUCKET_PATH | A state bucket that will hold the terraform state. This bucket must previously exist and the service account must have permission to read/write to it. | jenkins-gcs-state-bucket-name | +| policy_file_path | https://github.com/GoogleCloudPlatform/policy-library | The public repo where the policies are hosted | +| workload_identity_pool_id | | jenkins-test-pool | +| workload_identity_provider_id | | jenkins-test-provider | | + +* Once the prerequisites are set up, any commit to the remote main branch with changes to *.tf, *.tfvars, data/*, modules/* files should trigger the pipeline. + + +### Pipeline Workflow Overview +The complete workflow comprises of 6 stages and 2 before-script jobs + * Stages: + * Clean workspace : This step cleans the previous packages from Jenkins workspace + * WIF : Execute the Workload Identity Federation script and generate credential file. + * Terraform plan: Runs terraform plan and saves the plan and json version of the plan as artifacts, this depends on the branch + * Terraform validate: Runs gcloud terraform vet against the terraform code with the constraints in the specified repository. + * apply: This is executed for specified list of branches, currently main/master + + diff --git a/examples/guardrails/project-factory/main.tf b/examples/guardrails/project-factory/main.tf index 3d589d4..6c7d4d7 100644 --- a/examples/guardrails/project-factory/main.tf +++ b/examples/guardrails/project-factory/main.tf @@ -25,11 +25,12 @@ module "project" { source = "./modules/project_plus" for_each = local.projects team = each.key - repo_sub = "${each.value.repo_provider == "gitlab" ? "project_path:${each.value.repo_name}:ref_type:branch:ref:${each.value.repo_branch}" : "repo:${each.value.repo_name}:ref:refs/heads/${each.value.repo_branch}"}" + repo_sub = each.value.repo_branch repo_provider = each.value.repo_provider billing_account = each.value.billing_account_id folder = var.folder roles = try(each.value.roles, []) - wif-pool = "${each.value.repo_provider == "gitlab" ? google_iam_workload_identity_pool.wif-pool-gitlab.name : google_iam_workload_identity_pool.wif-pool-github.name}" - depends_on = [google_iam_workload_identity_pool.wif-pool-github,google_iam_workload_identity_pool.wif-pool-gitlab] + wif-pool = google_iam_workload_identity_pool.wif-pool-jenkins.name + depends_on = [google_iam_workload_identity_pool.wif-pool-jenkins] } + diff --git a/examples/guardrails/project-factory/modules/project_plus/main.tf b/examples/guardrails/project-factory/modules/project_plus/main.tf index 7330dd0..2332f16 100644 --- a/examples/guardrails/project-factory/modules/project_plus/main.tf +++ b/examples/guardrails/project-factory/modules/project_plus/main.tf @@ -34,7 +34,7 @@ resource "google_service_account" "sa" { resource "google_service_account_iam_member" "sa-iam" { service_account_id = google_service_account.sa.name role = "roles/iam.workloadIdentityUser" - member = "principalSet://iam.googleapis.com/${var.wif-pool}/attribute.sub/${var.repo_sub}" + member = "principalSet://iam.googleapis.com/${var.wif-pool}/attribute.branch_name/${var.repo_sub}" } resource "google_project_iam_member" "sa-project" { diff --git a/examples/guardrails/project-factory/variables.tf b/examples/guardrails/project-factory/variables.tf index df6e79d..c2c9ea4 100644 --- a/examples/guardrails/project-factory/variables.tf +++ b/examples/guardrails/project-factory/variables.tf @@ -17,3 +17,11 @@ variable "folder" { } +variable "billing_account" { +} +variable "issuer_uri" { +} + +variable "allowed_audiences" { + type = list +} diff --git a/examples/guardrails/project-factory/wif.tf b/examples/guardrails/project-factory/wif.tf index c54c67f..d3568e4 100644 --- a/examples/guardrails/project-factory/wif.tf +++ b/examples/guardrails/project-factory/wif.tf @@ -22,46 +22,30 @@ module "wif-project" { source = "./modules/project" name = "wif-prj-${random_id.rand.hex}" parent = var.folder - billing_account = "01B3B2-962224-4EEC67" + billing_account = var.billing_account } -resource "google_iam_workload_identity_pool" "wif-pool-gitlab" { +resource "google_iam_workload_identity_pool" "wif-pool-jenkins" { provider = google-beta - workload_identity_pool_id = "gitlab-pool-${random_id.rand.hex}" + workload_identity_pool_id = "jenkins-pool1-${random_id.rand.hex}" project = module.wif-project.project_id } -resource "google_iam_workload_identity_pool_provider" "wif-provider-gitlab" { +resource "google_iam_workload_identity_pool_provider" "wif-provider-jenkins" { provider = google-beta - workload_identity_pool_id = google_iam_workload_identity_pool.wif-pool-gitlab.workload_identity_pool_id - workload_identity_pool_provider_id = "gitlab-provider-${random_id.rand.hex}" + workload_identity_pool_id = google_iam_workload_identity_pool.wif-pool-jenkins.workload_identity_pool_id + workload_identity_pool_provider_id = "jenkins-provider-${random_id.rand.hex}" project = module.wif-project.project_id attribute_mapping = { "google.subject" = "assertion.sub" "attribute.sub" = "assertion.sub" + "attribute.branch_name" = "assertion.branchName" } oidc { - issuer_uri = "https://gitlab.com" - } -} - -resource "google_iam_workload_identity_pool" "wif-pool-github" { - provider = google-beta - workload_identity_pool_id = "github-pool-${random_id.rand.hex}" - project = module.wif-project.project_id -} - -resource "google_iam_workload_identity_pool_provider" "wif-provider-github" { - provider = google-beta - workload_identity_pool_id = google_iam_workload_identity_pool.wif-pool-github.workload_identity_pool_id - workload_identity_pool_provider_id = "github-provider-${random_id.rand.hex}" - project = module.wif-project.project_id - attribute_mapping = { - "google.subject" = "assertion.sub" - "attribute.sub" = "assertion.sub" - "attribute.actor" = "assertion.actor" - } - oidc { - issuer_uri = "https://token.actions.githubusercontent.com" + issuer_uri = var.issuer_uri + allowed_audiences = var.allowed_audiences } + depends_on = [ + google_iam_workload_identity_pool.wif-pool-jenkins + ] } diff --git a/examples/guardrails/skunkworks/Jenkinsfile b/examples/guardrails/skunkworks/Jenkinsfile new file mode 100644 index 0000000..e2856a0 --- /dev/null +++ b/examples/guardrails/skunkworks/Jenkinsfile @@ -0,0 +1,78 @@ +pipeline { + agent any + environment { + BUCKET_PATH = credentials('backend-path') + SKUNKWORK_PROJECT = credentials('staging-project') + SW_PROJECT_NUMBER = credentials('sw-project-number') + SW_SERVICE_ACCOUNT = credentials('sw-sa') + SW_workload_identity_pool_id = credentials('sw_wif_pool_id') + SW_workload_identity_pool_provider_id = credentials('sw_wif_pool_provider_id') + policy_file_path = credentials('policy_file_path') + } + options { + skipDefaultCheckout(true) + } + stages { + stage('clean workspace') { + steps { + cleanWs() + } + } + stage('WIF') { + steps { + withCredentials([file(variable: 'ID_TOKEN_STAGE', credentialsId: 'staging')]) { + writeFile file: "$WORKSPACE_TMP/sts_creds.json", text: """ + { + "type": "external_account", + "audience": "//iam.googleapis.com/projects/${SW_PROJECT_NUMBER}/locations/global/workloadIdentityPools/${SW_workload_identity_pool_id}/providers/${SW_workload_identity_pool_provider_id}", + "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", + "token_url": "https://sts.googleapis.com/v1/token", + "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${SW_SERVICE_ACCOUNT}@${SKUNKWORK_PROJECT}.iam.gserviceaccount.com:generateAccessToken", + "credential_source": { + "file": "$ID_TOKEN_STAGE", + "format": { + "type": "text" + } + } + } + """ + sh ''' + gcloud auth login --brief --cred-file=$WORKSPACE_TMP/sts_creds.json + ''' +} + } + } + stage('checkout') { + steps { + checkout scm + } + } + stage('Terraform Plan') { + steps { + sh ''' + cd guardrails/skunkworks + terraform init -backend-config="bucket=${BUCKET_PATH}" -backend-config="prefix=prod-skunkworks" + terraform plan -var="project=${SKUNKWORK_PROJECT}" -out swjenkins.tfplan + ''' + } + } + stage('Terraform Validate') { + steps { + sh ''' + cd guardrails/skunkworks + terraform show -json "swjenkins.tfplan" > "swjenkins.json" + gcloud source repos clone gcp-policies "${policy_file_path}" --project="${SW_PROJECT_NUMBER}" + gcloud beta terraform vet "swjenkins.json" --policy-library="${policy_file_path}" --project="${SW_PROJECT_NUMBER}" + ''' + } + } + stage('Terraform Apply') { + steps { + sh ''' + cd guardrails/skunkworks + terraform apply -var="project=${SKUNKWORK_PROJECT}" -auto-approve + ''' + } + } + } +} diff --git a/examples/guardrails/skunkworks/README.md b/examples/guardrails/skunkworks/README.md index 0b89bb2..7602c43 100644 --- a/examples/guardrails/skunkworks/README.md +++ b/examples/guardrails/skunkworks/README.md @@ -27,3 +27,43 @@ env: SERVICE_ACCOUNT: 'XXXX@XXXX' # The service account that should be used for this repository. ``` +## How to run this stage +### Prerequisites + +Workload Identity setup between the folder factory gitlab repositories and the GCP Identity provider configured with a service account containing required permissions to create folders and their organizational policies. There is a sample code provided in “folder.yaml.sample” to create a folder and for terraform to create a folder minimum below permissions are required. +“Folder Creator” or “Folder Admin” at org level +“Organization Policy Admin” at org level + + +### Installation Steps + +Step 1: Create a bucket for terraform backend on the GCP environment. +Step 2: Create Jenkins Pipeline on Jenkins. +Step 3: Configure the below variables on Jenkins Credentials. + +### Terraform config validator +The pipeline has an option to utilise the integrated config validator (gcloud terraform vet) to impose constraints on your terraform configuration. You have to provide the policy-library repo URL to $POLICY_LIBRARY_REPO variable. See the below for details on the Variables to be set on the CI/CD pipeline. + + +| Variable | | Example Value | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------- | +| PROJECT_NAME | The project containing the service account that has permission to communicate with the WIF provider. Should be created as part of Project Factory. | jenkins-connect-prj | +| GCP_PROJECT_NUMBER | Project number for the project that hosts the WIF provider | 107999111999 | +| SERVICE_ACCOUNT_NAME | The name of the service account that will be used to deploy. Must be hosted in PROJECT_NAME. | jenkins-sa | +| BUCKET_PATH | A state bucket that will hold the terraform state. This bucket must previously exist and the service account must have permission to read/write to it. | jenkins-gcs-state-bucket-name | +| policy_file_path | https://github.com/GoogleCloudPlatform/policy-library | The public repo where the policies are hosted | +| workload_identity_pool_id | | jenkins-test-pool | +| workload_identity_provider_id | | jenkins-test-provider | | + +* Once the prerequisites are set up, any commit to the remote main branch with changes to *.tf, *.tfvars, data/*, modules/* files should trigger the pipeline. + + +### Pipeline Workflow Overview +The complete workflow comprises of 6 stages and 2 before-script jobs + * Stages: + * Clean workspace : This step cleans the previous packages from Jenkins workspace + * WIF : Execute the Workload Identity Federation script and generate credential file. + * Terraform plan: Runs terraform plan and saves the plan and json version of the plan as artifacts, this depends on the branch + * Terraform validate: Runs gcloud terraform vet against the terraform code with the constraints in the specified repository. + * apply: This is executed for specified list of branches, currently main/master + diff --git a/examples/guardrails/skunkworks/variables.tf b/examples/guardrails/skunkworks/variables.tf index 6af98ff..2709299 100644 --- a/examples/guardrails/skunkworks/variables.tf +++ b/examples/guardrails/skunkworks/variables.tf @@ -16,5 +16,5 @@ variable "project" { type = string - default = "project-id" + }