Skip to content

Commit

Permalink
feat(ci-cd): enable automation for multiple environments (#27)
Browse files Browse the repository at this point in the history
* chore(deps): update terraform providers
* fix(terraform): backend type azurerm must be set. -backend-config is for key/val pairs only
* docs(terraform): update AAD permissions requirements
* feat(pipeline): config depending on branch name aka environment
* pipeline: rename comment stage
* pipeline: also need to set env for pull requests
* pipeline-vars: branch names start with refs/heads
* pipeline: more debugging
* pipeline: fix typo
* pipelines: debug built in variables too
* pipeline: try again after re-configuring git branch protection
* pipelines: completely refactor since PR target branch var not available for GitHub Repos
* stage(detect-draft): remove debugging conditionals
* drift: fix pipeline output filename
* pipelines: pr-only for PR pipelines
* pipeline(ci): ignore docs changes
* pipelines: errors if state file locked
* docs(pipelines): update
  • Loading branch information
julie-ng authored Jun 18, 2021
1 parent a1357ad commit 54fb1ce
Show file tree
Hide file tree
Showing 14 changed files with 239 additions and 173 deletions.
88 changes: 44 additions & 44 deletions .terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 6 additions & 4 deletions DEPLOY.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Make sure you read the full document because the pre-configuration of permission
```bash
export AZDO_ORG_SERVICE_URL="https://dev.azure.com/<your-demo-org-name>"
export AZDO_PERSONAL_ACCESS_TOKEN=""
terraform init
terraform init -backend=false
terraform plan -out demo.tfplan
terraform apply demo.tfplan
```
Expand Down Expand Up @@ -70,10 +70,12 @@ For details, please read [Azure Security Principals](https://docs.microsoft.com/
- **Required Permissions on AD**
- Directory Role: [Azure AD Provider on Terraform](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/guides/service_principal_configuration#method-1-directory-roles-recommended) recommends assigning the `Global Administrator` role to the security principal (user or service) in order to manage users, groups and applications.

- API Permissions: if instead of using a role, you can also add the grant access to the following permissions:
- API Permissions: if instead of using a role, you can also add the grant access to the following permissions for **_Azure Active Directory Graph API_**, _not_ the Microsoft Graph API:
- `Application.ReadWrite.All`
- `Directory.ReadWrite.All`

See [Terraform documentation](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/guides/service_principal_configuration#method-1-directory-roles-recommended) on configuring service principals for full instructions.

### 4) Azure DevOps Organization

- **DevOps Organization**
Expand Down Expand Up @@ -116,7 +118,7 @@ source ./.env
Assuming you are logged in with `az login`, just run

```
terraform init
terraform init -backend=false
```

Then continue to [Create Deployment Plan &rarr;](##create-deployment-plan)
Expand All @@ -139,7 +141,7 @@ sas_token="?sv=2019-12-12…"
Finally run `init` with our new backend config.

```
terraform init -backend=true -backend-config=./backend.hcl
terraform init -backend-config=./backend.hcl
```


Expand Down
37 changes: 26 additions & 11 deletions azure-pipelines/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,33 @@

Brief notes and considerations when automating infrastructure with [Terraform](https://terraform.io) and [Azure DevOps](https://azure.microsoft.com/en-us/services/devops/).

### Pipeline Overview
## Overview

| Pipeline | Triggers | Terraform Backend | Detect Drift | Deploys |
### Branch Commit Triggered

| Pipeline | Branch | Terraform Backend | Detects Drift | Deploys |
|:--|:--|:--|:--|:--|
| [`ci.yaml`](./ci.yaml) | &bull; `main` branch<br>&bull; `dev` branch<br>&bull; `feat/*` branch<br>&bull; `fix/*` branch<br>&bull; Pull Requests to `main` | local/none | No | No |
| [`detect-drift.yaml`](./detect-drift.yaml) | &bull; `main` branch<br>&bull; Pull Requests to `release`<br>&bull; Scheduled nightly | Azure Storage | Yes | No |
| [`cd.yaml`](./cd.yaml) | &bull; `release` branch | Azure Storage | No* | Yes |
| [`ci.yaml`](./ci.yaml) | &bull; `main`<br>&bull; `feat/*`<br>&bull; `fix/*` | local | No | No |
| [`cd.yaml`](./cd.yaml) | &bull; `main` <br>&bull; `production` | Azure Storage | No* | Yes |

_*The CD pipeline does not check for drift because this is checked at the pull request._

### Pull Request Triggered

The following pipelines do CI check and configuration drift detection, and posts the results back to the Pull Request. [See example &rarr;](https://github.com/Azure/devops-governance/pull/27)

*The CD pipeline does not check for drift because this is checked at the pull request.
| Pipeline | PR Trigger | Detects Drift | Deploys |
|:--|:--|:--|:--|
| [`pr-main.yaml`](./pr-main.yaml) | `main` | Yes | No |
| [`pr-production.yaml`](./pr-production.yaml) | `production` | Yes | No |

### Scheduled Triggeed

Checks regularly for any manual non Infrastructure as Code changes.

| Pipeline | Trigger | Detects Drift | Deploys |
|:--|:--|:--|:--|
| [`schedule-drift.yaml`](./schedule-drift.yaml) | Scheduled nightly | Yes | No |

## Security Considerations

Expand Down Expand Up @@ -42,8 +60,7 @@ variables:

steps:
- bash: |
terraform init \
-backend=true \
terraform init \
-backend-config="storage_account_name=$TF_STATE_BLOB_ACCOUNT_NAME" \
-backend-config="container_name=$TF_STATE_BLOB_CONTAINER_NAME" \
-backend-config="key=$TF_STATE_BLOB_FILE" \
Expand All @@ -62,6 +79,4 @@ In this example "superadmins" refers to privileged accounts at the organization
This project creates Azure Key Vaults, which require access policies for the Data Plane. This means you want to create the Key Vault _and_ give yourself access.
If our CI/CD agent creates the Key Vault, it can assign itself access. But what if we as an infrastructure administrator also want access? If you give yourself access _outside of Terraform_, it can create **configuration drift** that conflicts with Terraform state. Using [Access Policies](https://docs.microsoft.com/en-us/azure/key-vault/general/secure-your-key-vault#data-plane-and-access-policies), you can [assign the Access Policy to an Azure AD group](https://docs.microsoft.com/en-us/azure/key-vault/general/overview-security#identity-and-access-management) instead of an account (service principal or user principal).
N.B. This is not relevant if you use the [Azure RBAC model for Azure Key Vault](https://docs.microsoft.com/en-us/azure/key-vault/general/rbac-guide). At time of this writing (November 2020), this feature is still in preview, which is why this project uses access policies.
In order to interact with the Key Vaults' data plane, the we need to [assign ourselves the proper roles](https://docs.microsoft.com/azure/key-vault/general/rbac-guide?tabs=azure-cli#azure-built-in-roles-for-key-vault-data-plane-operations), e.g. `Key Vault Secrets User` to gain access.
71 changes: 11 additions & 60 deletions azure-pipelines/cd.yaml
Original file line number Diff line number Diff line change
@@ -1,78 +1,29 @@
# Build numbering format
name: $(BuildID)

trigger:
branches:
include:
- deploy
- main
- production
paths:
exclude:
- '*.md'
- 'backends/*'
- 'images/*'
- '.github/*'

pr: none

pool:
vmImage: 'ubuntu-18.04'

variables:
- group: e2e-gov-demo-kv
- template: vars/global.yaml
- ${{ if eq(variables.isMain, 'True') }}:
- group: e2e-gov-demo-dev-kv
- ${{ if eq(variables.isProduction, 'True') }}:
- group: e2e-gov-demo-kv

stages:
- stage: ci_stage
displayName: CI Stage
jobs:
- job: ci_job
displayName: Terraform Validate and Lint
steps:
- bash: terraform version
displayName: terraform version

- bash: terraform init
displayName: terraform init

- bash: |
terraform validate
terraform fmt -check
displayName: terraform validate and Lint
- stage: cd_stage
displayName: CD Stage
jobs:
- job: deploy
displayName: Terraform Plan and Apply
steps:
- bash: |
terraform init \
-backend=true \
-backend-config="storage_account_name=$TF_STATE_BLOB_ACCOUNT_NAME" \
-backend-config="container_name=$TF_STATE_BLOB_CONTAINER_NAME" \
-backend-config="key=$TF_STATE_BLOB_FILE" \
-backend-config="sas_token=$TF_STATE_BLOB_SAS_TOKEN"
displayName: Terraform Init
env:
TF_STATE_BLOB_ACCOUNT_NAME: $(kv-tf-state-blob-account)
TF_STATE_BLOB_CONTAINER_NAME: $(kv-tf-state-blob-container)
TF_STATE_BLOB_FILE: $(kv-tf-state-blob-file)
TF_STATE_BLOB_SAS_TOKEN: $(kv-tf-state-sas-token)
- bash: terraform plan -out=deployment.tfplan -var superadmins_aad_object_id=$AAD_SUPERADMINS_GROUP_ID
displayName: Terraform Plan (ignores drift)
env:
ARM_SUBSCRIPTION_ID: $(kv-arm-subscription-id)
ARM_CLIENT_ID: $(kv-arm-client-id)
ARM_CLIENT_SECRET: $(kv-arm-client-secret)
ARM_TENANT_ID: $(kv-arm-tenant-id)
AZDO_ORG_SERVICE_URL: $(kv-azure-devops-org-url)
AZDO_PERSONAL_ACCESS_TOKEN: $(kv-azure-devops-pat)
AAD_SUPERADMINS_GROUP_ID: $(kv-aad-superadmins-group-id)

- bash: terraform apply -auto-approve deployment.tfplan
displayName: Terraform Apply
env:
ARM_SUBSCRIPTION_ID: $(kv-arm-subscription-id)
ARM_CLIENT_ID: $(kv-arm-client-id)
ARM_CLIENT_SECRET: $(kv-arm-client-secret)
ARM_TENANT_ID: $(kv-arm-tenant-id)
AZDO_ORG_SERVICE_URL: $(kv-azure-devops-org-url)
AZDO_PERSONAL_ACCESS_TOKEN: $(kv-azure-devops-pat)
- template: templates/ci.yaml
- template: templates/deploy.yaml
22 changes: 8 additions & 14 deletions azure-pipelines/ci.yaml
Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
# Build numbering format
name: $(BuildID)

trigger:
branches:
include:
- main
- dev
- feat/*
- fix/*

pr:
- main
paths:
exclude:
- '*.md'
- 'backends/*'
- 'images/*'
- '.github/*'

pool:
vmImage: 'ubuntu-18.04'

steps:
- bash: terraform init
displayName: init

- bash: terraform validate
displayName: validate

- bash: terraform fmt -check
displayName: check format / lint
stages:
- template: stages/ci.yaml
17 changes: 17 additions & 0 deletions azure-pipelines/pr-main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: $(BuildID)

pool:
vmImage: 'ubuntu-18.04'

trigger: none # PR only

pr:
- main

variables:
- template: vars/global.yaml
- group: e2e-gov-demo-dev-kv # DEV

stages:
- template: stages/ci.yaml
- template: stages/detect-drift.yaml
17 changes: 17 additions & 0 deletions azure-pipelines/pr-production.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: $(BuildID)

pool:
vmImage: 'ubuntu-18.04'

trigger: none # PR only

pr:
- production

variables:
- template: vars/global.yaml
- group: e2e-gov-demo-kv # PROD

stages:
- template: stages/ci.yaml
- template: stages/detect-drift.yaml
Loading

0 comments on commit 54fb1ce

Please sign in to comment.