Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions blueprints/secops/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ This repository provides a collection of Terraform blueprints designed to automa

<br clear="left">

## Detection as Code with Terraform for Google SecOps

<a href="./detection-as-code/" title="Detection as Code with Terraform for Google SecOps"><img src="./detection-as-code/images/diagram.png" align="left" width="280px"></a> This [blueprint](./detection-as-code/) is a sample terraform repository to implementing a Detection as code pipeline for managing Google SecOps rules based on Terraform code.

<br clear="left">

## SecOps GKE Forwarder

Expand Down
206 changes: 206 additions & 0 deletions blueprints/secops/detection-as-code/.github/workflows/secops.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
# Copyright 2025 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.

name: "SecOps Detection As Code"

on:
pull_request:
branches:
- main
types:
- closed
- opened
- synchronize

env:
SERVICE_ACCOUNT: # TODO replace with WIF SA
WIF_PROVIDER: projects/XXXXXXXXX/locations/global/workloadIdentityPools/xxxxxxxx/providers/xxxxxx #TODO replace with wif provider
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
TF_VERSION: 1.6.5

jobs:
secops-pr:
permissions:
contents: read
id-token: write
issues: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- id: checkout
name: Checkout repository
uses: actions/checkout@v3

# set up SSH key authentication to the other repositories

# - id: ssh-config
# name: Configure SSH authentication
# run: |
# ssh-agent -a "$SSH_AUTH_SOCK" > /dev/null
# ssh-add - <<< "${{ secrets.CICD_MODULES_KEY }}"

# set up step variables for plan / apply

- id: vars-plan
if: github.event.pull_request.merged != true && success()
name: Set up plan variables
run: |
echo "plan_opts=-lock=false" >> "$GITHUB_ENV"
echo "service_account=${{env.SERVICE_ACCOUNT}}" >> "$GITHUB_ENV"

- id: vars-apply
if: github.event.pull_request.merged == true && success()
name: Set up apply variables
run: |
echo "service_account=${{env.SERVICE_ACCOUNT}}" >> "$GITHUB_ENV"

# set up authentication via Workload identity Federation and gcloud

- id: gcp-auth
name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{env.WIF_PROVIDER}}
service_account: ${{env.SERVICE_ACCOUNT}}
access_token_lifetime: 900s

- id: gcp-sdk
name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v2
with:
install_components: alpha

- id: tf-setup
name: Set up Terraform
uses: hashicorp/setup-terraform@v2.0.3
with:
terraform_version: ${{env.TF_VERSION}}

# run Terraform init/validate/plan

- id: tf-init
name: Terraform init
continue-on-error: true
run: |
terraform init -no-color

- id: tf-validate
continue-on-error: true
name: Terraform validate
run: terraform validate -no-color

- id: tf-plan
name: Terraform plan
continue-on-error: true
run: |
terraform plan -input=false -out ../plan.out -no-color ${{env.plan_opts}}

- id: tf-apply
if: github.event.pull_request.merged == true && success()
name: Terraform apply
continue-on-error: true
run: |
terraform apply -input=false -auto-approve -no-color ../plan.out

# PR comment with Terraform result from previous steps
# length is checked and trimmed for length so as to stay within the limit

- id: pr-comment
name: Post comment to Pull Request
continue-on-error: true
uses: actions/github-script@v6
if: github.event_name == 'pull_request'
env:
PLAN: ${{steps.tf-plan.outputs.stdout}}\n${{steps.tf-plan.outputs.stderr}}
with:
script: |
const output = `### Terraform Initialization \`${{steps.tf-init.outcome}}\`

### Terraform Validation \`${{steps.tf-validate.outcome}}\`

<details><summary>Validation Output</summary>

\`\`\`\n
${{steps.tf-validate.outputs.stdout}}
\`\`\`

</details>

### Terraform Plan \`${{steps.tf-plan.outcome}}\`

<details><summary>Show Plan</summary>

\`\`\`\n
${process.env.PLAN.split('\n').filter(l => l.match(/^([A-Z\s].*|)$/)).join('\n')}
\`\`\`

</details>

### Terraform Apply \`${{steps.tf-apply.outcome}}\`

*Pusher: @${{github.actor}}, Action: \`${{github.event_name}}\`, Working Directory: \`${{env.tf_actions_working_dir}}\`, Workflow: \`${{github.workflow}}\`*`;

github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})

- id: pr-short-comment
name: Post comment to Pull Request (abbreviated)
uses: actions/github-script@v6
if: github.event_name == 'pull_request' && steps.pr-comment.outcome != 'success'
with:
script: |
const output = `### Terraform Initialization \`${{steps.tf-init.outcome}}\`

### Terraform Validation \`${{steps.tf-validate.outcome}}\`

### Terraform Plan \`${{steps.tf-plan.outcome}}\`

Plan output is in the action log.

### Terraform Apply \`${{steps.tf-apply.outcome}}\`

*Pusher: @${{github.actor}}, Action: \`${{github.event_name}}\`, Working Directory: \`${{env.tf_actions_working_dir}}\`, Workflow: \`${{github.workflow}}\`*`;

github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})

# exit on error from previous steps

- id: check-init
name: Check init failure
if: steps.tf-init.outcome != 'success'
run: exit 1

- id: check-validate
name: Check validate failure
if: steps.tf-validate.outcome != 'success'
run: exit 1

- id: check-plan
name: Check plan failure
if: steps.tf-plan.outcome != 'success'
run: exit 1

- id: check-apply
name: Check apply failure
if: github.event.pull_request.merged == true && steps.tf-apply.outcome != 'success'
run: exit 1
94 changes: 94 additions & 0 deletions blueprints/secops/detection-as-code/.gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Copyright 2025 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.

default:
before_script:
- echo "${CI_JOB_JWT_V2}" > token.txt
image:
name: hashicorp/terraform
entrypoint:
- "/usr/bin/env"
- "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

workflow:
rules:
# merge / apply
- if: $CI_PIPELINE_SOURCE == 'push' && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH
variables:
COMMAND: apply
# pr / plan
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
variables:
COMMAND: plan

variables:
GOOGLE_CREDENTIALS: cicd-sa-credentials.json
SERVICE_ACCOUNT: # TODO replace with WIF SA
WIF_PROVIDER: projects/XXXXXXXXXXX/locations/global/workloadIdentityPools/POOL/providers/PROVIDER
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
PLAN: plan.cache
PLAN_JSON: plan.json

stages:
- gcp-auth
- tf-plan-apply

cache:
- key: gcp-auth
paths:
- cicd-sa-credentials.json

gcp-auth:
image:
name: google/cloud-sdk:slim
stage: gcp-auth
artifacts:
paths:
- cicd-sa-credentials.json
id_tokens:
GITLAB_TOKEN:
aud:
- # TODO replace with audience
before_script:
- echo "$GITLAB_TOKEN" > token.txt
script:
- |
gcloud iam workload-identity-pools create-cred-config \
$WIF_PROVIDER --service-account=$SERVICE_ACCOUNT \
--service-account-token-lifetime-seconds=900 \
--output-file=$GOOGLE_CREDENTIALS \
--credential-source-file=token.txt
- gcloud config set auth/credential_file_override $GOOGLE_CREDENTIALS
- gcloud auth login --update-adc --cred-file=$GOOGLE_CREDENTIALS

tf-plan-apply:
stage: tf-plan-apply
dependencies:
- gcp-auth
script:
- apk --no-cache add jq
- alias convert_report="jq -r '([.resource_changes[]?.change.actions?]|flatten)|{\"create\":(map(select(.==\"create\"))|length),\"update\":(map(select(.==\"update\"))|length),\"delete\":(map(select(.==\"delete\"))|length)}'"
- echo "$GITLAB_TOKEN" > token.txt
- terraform init
- terraform validate
- "if [ $COMMAND == 'plan' ]; then terraform plan -input=false -no-color -lock=false -out=$PLAN; fi"
- "if [ $COMMAND == 'plan' ]; then terraform show --json $PLAN | convert_report > $PLAN_JSON; fi"
- "if [ $COMMAND == 'apply' ]; then terraform apply -input=false -no-color -auto-approve; fi"
artifacts:
reports:
terraform: $PLAN_JSON
id_tokens:
GITLAB_TOKEN:
aud:
- # TODO replace with audience
Loading