-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #36 from appvia/feat_add_drift
feat: adding the ability to detect drift and send slack notifications
- Loading branch information
Showing
4 changed files
with
271 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
--- | ||
name: Terraform Drift Detection | ||
|
||
on: | ||
workflow_call: | ||
secrets: | ||
slack-webhook-url: | ||
description: "The Slack webhook URL" | ||
required: false | ||
|
||
inputs: | ||
aws-account-id: | ||
description: "The AWS account ID to deploy to" | ||
required: true | ||
type: string | ||
|
||
aws-region: | ||
default: "eu-west-2" | ||
description: "The AWS region to deploy to" | ||
required: false | ||
type: string | ||
|
||
aws-role: | ||
default: "${{ github.event.repository.name }}" | ||
description: "The AWS role to assume" | ||
required: false | ||
type: string | ||
|
||
aws-read-role-name: | ||
description: "Overrides the default behavior, and uses a custom role name for read-only access" | ||
required: false | ||
type: string | ||
|
||
aws-write-role-name: | ||
description: "Overrides the default behavior, and uses a custom role name for read-write access" | ||
required: false | ||
type: string | ||
|
||
environment: | ||
default: "production" | ||
description: "The environment to deploy to" | ||
required: false | ||
type: string | ||
|
||
use-env-as-suffix: | ||
default: false | ||
description: "Whether to use the environment as a suffix for the state file and iam roles" | ||
required: false | ||
type: boolean | ||
|
||
runs-on: | ||
default: "ubuntu-latest" | ||
description: "Single label value for the GitHub runner to use (custom value only applies to Terraform Plan and Apply steps)" | ||
required: false | ||
type: string | ||
|
||
terraform-dir: | ||
default: "." | ||
description: "The directory to validate" | ||
required: false | ||
type: string | ||
|
||
terraform-lock-timeout: | ||
default: "30s" | ||
description: The time to wait for a lock | ||
required: false | ||
type: string | ||
|
||
terraform-state-key: | ||
default: "" | ||
description: "The key of the terraform state (default: <repo-name>.tfstate)" | ||
required: false | ||
type: string | ||
|
||
terraform-values-file: | ||
default: "" | ||
description: "The values file to use (default: <environment>.tfvars)" | ||
required: false | ||
type: string | ||
|
||
terraform-version: | ||
default: "1.7.1" | ||
description: "The version of terraform to use" | ||
required: false | ||
type: string | ||
|
||
working-directory: | ||
default: "." | ||
description: "The working directory to run terraform commands in" | ||
required: false | ||
type: string | ||
|
||
env: | ||
AWS_ROLE: ${{ inputs.aws-role }} | ||
AWS_READONLY_OVERRIDE_ROLE: ${{ inputs.aws-read-role-name }} | ||
AWS_READWRITE_OVERRIDE_ROLE: ${{ inputs.aws-write-role-name }} | ||
AWS_WEB_IDENTITY_TOKEN_FILE: /tmp/web_identity_token_file | ||
|
||
permissions: | ||
id-token: write | ||
contents: read | ||
|
||
jobs: | ||
terraform-plan: | ||
name: "Terraform Plan" | ||
runs-on: ${{ inputs.runs-on }} | ||
defaults: | ||
run: | ||
working-directory: ${{ inputs.working-directory }} | ||
outputs: | ||
result-auth: ${{ steps.auth.outcome }} | ||
result-init: ${{ steps.init.outcome }} | ||
result-validate: ${{ steps.validate.outcome }} | ||
result-s3-backend-check: ${{ steps.s3-backend-check.outcome }} | ||
steps: | ||
- name: Checkout Repository | ||
uses: actions/checkout@v4 | ||
- name: Setup node | ||
uses: actions/setup-node@v4 | ||
with: | ||
node-version: 16 | ||
- name: Setup Terraform | ||
uses: hashicorp/setup-terraform@v3 | ||
with: | ||
terraform_version: ${{ inputs.terraform-version }} | ||
- name: Retrieve Web Identity Token for AWS Authentication | ||
run: | | ||
curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sts.amazonaws.com" | jq -r '.value' > $AWS_WEB_IDENTITY_TOKEN_FILE | ||
- name: Determine AWS Role | ||
id: role | ||
run: | | ||
if [ "${{ inputs.use-env-as-suffix }}" == "true" ]; then | ||
role_suffix="-${{ inputs.environment }}" | ||
else | ||
role_suffix="" | ||
fi | ||
if [[ "${GITHUB_REF##*/}" == "main" ]]; then | ||
echo "name=${AWS_READWRITE_OVERRIDE_ROLE:-${AWS_ROLE}${role_suffix}}" >> $GITHUB_OUTPUT | ||
else | ||
echo "name=${AWS_READONLY_OVERRIDE_ROLE:-${AWS_ROLE}${role_suffix}-ro}" >> $GITHUB_OUTPUT | ||
fi | ||
- name: Authenticate with AWS | ||
id: auth | ||
uses: aws-actions/configure-aws-credentials@v4 | ||
with: | ||
aws-region: ${{ inputs.aws-region }} | ||
role-session-name: ${{ github.event.repository.name }} | ||
role-to-assume: arn:aws:iam::${{ inputs.aws-account-id }}:role/${{ steps.role.outputs.name }} | ||
mask-aws-account-id: "no" | ||
- name: Set terraform-state-key variable | ||
id: state-key | ||
run: | | ||
if [ -n "${{ inputs.terraform-state-key }}" ]; then | ||
echo "name=${{ inputs.terraform-state-key }}" >> $GITHUB_OUTPUT | ||
else | ||
if [ "${{ inputs.use-env-as-suffix }}" == "true" ]; then | ||
echo "name=${{ github.event.repository.name }}-${{ inputs.environment }}.tfstate" >> $GITHUB_OUTPUT | ||
else | ||
echo "name=${{ github.event.repository.name }}.tfstate" >> $GITHUB_OUTPUT | ||
fi | ||
fi | ||
- name: Terraform Init | ||
id: init | ||
run: terraform -chdir=${{ inputs.terraform-dir }} init -backend-config="bucket=${{ inputs.aws-account-id }}-${{ inputs.aws-region }}-tfstate" -backend-config="key=${{ steps.state-key.outputs.name }}" -backend-config="encrypt=true" -backend-config="dynamodb_table=${{ inputs.aws-account-id }}-${{ inputs.aws-region }}-tflock" -backend-config="region=${{ inputs.aws-region }}" | ||
- name: Terraform Validate | ||
id: validate | ||
run: terraform -chdir=${{ inputs.terraform-dir }} validate -no-color | ||
- name: Terraform S3 Backend Check | ||
id: s3-backend-check | ||
run: | | ||
if grep -E '^[^#]*backend\s+"s3"' terraform.tf; then | ||
echo "Terraform configuration references an S3 backend." | ||
else | ||
echo "Terraform configuration does not reference an S3 backend." | ||
exit 1 | ||
fi | ||
- name: Set terraform-values-file variable | ||
run: | | ||
if [ -n "${{ inputs.terraform-values-file }}" ]; then | ||
echo "TF_VAR_FILE=${{ inputs.terraform-values-file }}" >> $GITHUB_ENV | ||
else | ||
echo "TF_VAR_FILE=values/${{ inputs.environment }}.tfvars" >> $GITHUB_ENV | ||
fi | ||
- name: Check for drift and set status | ||
id: check-drift | ||
run: | | ||
if grep -q 'No changes' <(terraform -chdir=${{ inputs.terraform-dir }} plan -var-file=$TF_VAR_FILE -no-color -input=false -out=tfplan -lock-timeout=${{ inputs.terraform-lock-timeout }}); then | ||
echo "No drift detected." | ||
echo "DRIFT_STATUS=no-drift" >> "$GITHUB_OUTPUT" | ||
else | ||
echo "Drift detected!" | ||
echo "DRIFT_STATUS=drift" >> "$GITHUB_OUTPUT" | ||
fi | ||
- name: Send Slack notification if drift is detected | ||
if: steps.check-drift.outputs.DRIFT_STATUS == 'drift' | ||
uses: slackapi/[email protected] | ||
with: | ||
webhook: ${{ secrets.slack-webhook-url }} | ||
webhook-type: incoming-webhook | ||
payload: | | ||
{ | ||
"username": "GitHub Actions", | ||
"text": "🚨 Drift Detected (${{ github.repository }})", | ||
"icon_emoji": ":warning:" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,3 +5,5 @@ | |
.idea | ||
|
||
*.orig | ||
|
||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
# Terraform Draft Workflow for AWS Infrastructure | ||
|
||
This workflow is used to run an scheduled or manually triggered drift detection on AWS infrastructure and alert in Slack if a change is detected, using GitHub Actions workflow template ([terraform-drift.yml](../.github/workflows/terraform-drift.yml)) | ||
In order to trigger the workflow, firstly the workflow must be referenced from the calling workflow flow, see below. | ||
|
||
## Workflow Steps | ||
|
||
1. **Setup Terraform:** Terraform is fetched at the specified version (overridable via inputs). | ||
2. **AWS Authentication:** The workflow uses Web Identity Federation to authenticate with AWS. The required AWS Role ARN must be provided as an input for successful authentication. | ||
- A Web Identity Token File is also generated and stored in `/tmp/web_identity_token_file`, which can be referenced in Terraform Provider configuration blocks if required. | ||
3. **Terraform Init:** The Terraform backend is initialised and any necessary provider plugins are downloaded. The required inputs for AWS S3 bucket name and DynamoDB table name must be provided for storing the Terraform state. | ||
4. **Terraform Plan:** A Terraform plan is generated with a specified values file (overridable via inputs) using the terraform plan command. | ||
5. **Change Detection Status:** Plan is checked for any changes and status is set as to drift status, "No drift detected." or "Drift detected!" | ||
6. **Alerting:** If drift is detected from the Terraform Plan, an alert is sent to a configured Slack Channel alerting users. Otherwise where there is no change, this step is skipped. | ||
|
||
## Usage | ||
|
||
Create a workflow file in your Terraform repository (e.g. `.github/workflows/terraform-drift.yml`) with the below contents: | ||
|
||
```yml | ||
--- | ||
name: Terraform Drift | ||
on: | ||
workflow_dispatch: | ||
schedule: | ||
- cron: "56 10 * * *" | ||
|
||
permissions: | ||
contents: read | ||
id-token: write | ||
|
||
jobs: | ||
terraform-drift: | ||
uses: appvia/appvia-cicd-workflows/.github/workflows/terraform-drift.yml@main | ||
name: Drift Detection | ||
secrets: | ||
slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} | ||
with: | ||
aws-account-id: <aws-account-id-value> | ||
``` | ||
REQUIRED INPUTS: | ||
- `slack-webhook-url` - Slack Webhook to a channel/app, stored as a secret in your Github Actions Secrets | ||
- `aws-account-id` - AWS account number where the infrastructure is deployed, and consequently planned against | ||
|
||
OPTIONAL INPUTS: | ||
- `aws-region` - Default: "eu-west-2" | ||
- `aws-role` - Default: Repository Name | ||
- `aws-read-role-name` - Extra role for read only access | ||
- `aws-write-role-name` - Extra role for read-write access | ||
- `environment` - Default: "production" | ||
- `use-env-as-suffix` - Default: false, Whether to use the environment as a suffix for the state file and iam roles | ||
- `runs-on` - Default: "ubuntu-latest" | ||
- `terraform-dir` - Default: ".", | ||
- `terraform-lock-timeout` - Default: "30s" | ||
- `terraform-state-key` - Default: <repo-name>.tfstate | ||
- `terraform-values-file` - Default: <environment>.tfvars | ||
- `terraform-version` - Default: "1.7.1" | ||
- `working-directory` - Default: "." | ||
|
||
**Note:** This template may change over time, so it is recommended that you point to a tagged version rather than the main branch. |