CI/CD Integration Example
This guide demonstrates a complete CI/CD integration with Cloudgeni, including security scanning, compliance checks, and automated remediation workflows.What You'll Build
- Security gate that blocks insecure code
- Multi-stage pipeline with scanning at each phase
- Automated PR comments with findings
- Slack notifications for security alerts
- Branch protection enforcement
Architecture
Copy
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Commit │ → │ Scan │ → │ Review │ → │ Deploy │
│ Code │ │ & Test │ │ & Approve │ │ to Cloud │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│ │ │ │
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Cloudgeni Integration │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Static │ │ Compliance│ │ PR │ │ Drift │ │
│ │ Scan │ │ Check │ │ Review │ │ Monitor │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
GitHub Actions Implementation
Complete Workflow
Create.github/workflows/security.yml:
Copy
name: Security Pipeline
on:
push:
branches: [main, develop]
paths:
- '**/*.tf'
- '**/*.bicep'
- '**/*.hcl'
pull_request:
branches: [main]
paths:
- '**/*.tf'
- '**/*.bicep'
- '**/*.hcl'
schedule:
# Run daily security scan at 2 AM UTC
- cron: '0 2 * * *'
permissions:
contents: read
security-events: write
pull-requests: write
issues: write
env:
TF_ROOT: infrastructure
jobs:
# Stage 1: Static Security Scan
security-scan:
name: Security Scan
runs-on: ubuntu-latest
outputs:
critical: ${{ steps.scan.outputs.critical }}
high: ${{ steps.scan.outputs.high }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Cloudgeni Scan
id: scan
uses: cloudgeni/github-action@v1
with:
api-key: ${{ secrets.CLOUDGENI_API_KEY }}
scan-path: ${{ env.TF_ROOT }}
output-format: sarif
fail-on-critical: true
fail-on-high: ${{ github.ref == 'refs/heads/main' }}
- name: Upload SARIF to GitHub Security
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: cloudgeni-results.sarif
- name: Upload scan results
uses: actions/upload-artifact@v4
if: always()
with:
name: security-results
path: cloudgeni-results.*
# Stage 2: Compliance Check
compliance-check:
name: Compliance Check
runs-on: ubuntu-latest
needs: security-scan
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Check SOC 2 Compliance
uses: cloudgeni/github-action@v1
with:
api-key: ${{ secrets.CLOUDGENI_API_KEY }}
scan-path: ${{ env.TF_ROOT }}
framework: soc2
fail-on-critical: true
- name: Check ISO 27001 Compliance
uses: cloudgeni/github-action@v1
with:
api-key: ${{ secrets.CLOUDGENI_API_KEY }}
scan-path: ${{ env.TF_ROOT }}
framework: iso27001
fail-on-critical: true
# Stage 3: Terraform Validation
terraform-validate:
name: Terraform Validate
runs-on: ubuntu-latest
needs: security-scan
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "1.5.0"
- name: Terraform Init
run: terraform init -backend=false
working-directory: ${{ env.TF_ROOT }}
- name: Terraform Validate
run: terraform validate
working-directory: ${{ env.TF_ROOT }}
- name: Terraform Format Check
run: terraform fmt -check -recursive
working-directory: ${{ env.TF_ROOT }}
# Stage 4: Terraform Plan (for PRs)
terraform-plan:
name: Terraform Plan
runs-on: ubuntu-latest
needs: [security-scan, compliance-check, terraform-validate]
if: github.event_name == 'pull_request'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Terraform Init
run: terraform init
working-directory: ${{ env.TF_ROOT }}
- name: Terraform Plan
id: plan
run: |
terraform plan -no-color -out=tfplan 2>&1 | tee plan.txt
working-directory: ${{ env.TF_ROOT }}
- name: Comment Plan on PR
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const plan = fs.readFileSync('${{ env.TF_ROOT }}/plan.txt', 'utf8');
const truncatedPlan = plan.length > 60000
? plan.substring(0, 60000) + '\n\n... (truncated)'
: plan;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Terraform Plan\n\n\`\`\`\n${truncatedPlan}\n\`\`\``
});
# Stage 5: Notify on Failure
notify-failure:
name: Notify on Failure
runs-on: ubuntu-latest
needs: [security-scan, compliance-check]
if: failure()
steps:
- name: Send Slack notification
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "Security scan failed!",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Security Alert* :warning:\n\nSecurity scan failed for `${{ github.repository }}`\n\n*Branch:* `${{ github.ref_name }}`\n*Commit:* `${{ github.sha }}`\n*Author:* ${{ github.actor }}"
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "View Run"
},
"url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
]
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
Branch Protection Rules
Configure in GitHub
- Go to Settings → Branches → Add rule
- Branch name pattern:
main - Enable:
- Require a pull request before merging
- Require status checks to pass
- Select required checks:
Security ScanCompliance CheckTerraform Validate
- Require branches to be up to date
- Do not allow bypassing settings
Result
PRs to main cannot be merged unless:- ✅ Security scan passes (no critical findings)
- ✅ Compliance checks pass
- ✅ Terraform validates successfully
GitLab CI Implementation
Create.gitlab-ci.yml:
Copy
stages:
- security
- validate
- plan
- apply
variables:
TF_ROOT: infrastructure
.terraform:
image: hashicorp/terraform:1.5
before_script:
- cd ${TF_ROOT}
- terraform init
# Stage 1: Security Scan
security-scan:
stage: security
image: cloudgeni/scanner:latest
script:
- cloudgeni scan
--api-key ${CLOUDGENI_API_KEY}
--path ${TF_ROOT}
--fail-on-critical
--output sarif > gl-sast-report.json
artifacts:
reports:
sast: gl-sast-report.json
paths:
- gl-sast-report.json
when: always
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Stage 2: Compliance Check
compliance-check:
stage: security
image: cloudgeni/scanner:latest
script:
- cloudgeni scan
--api-key ${CLOUDGENI_API_KEY}
--path ${TF_ROOT}
--framework soc2,iso27001
--fail-on-critical
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Stage 3: Terraform Validate
terraform-validate:
extends: .terraform
stage: validate
script:
- terraform validate
- terraform fmt -check -recursive
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Stage 4: Terraform Plan
terraform-plan:
extends: .terraform
stage: plan
script:
- terraform plan -out=plan.tfplan
artifacts:
paths:
- ${TF_ROOT}/plan.tfplan
needs:
- security-scan
- compliance-check
- terraform-validate
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# Stage 5: Terraform Apply (manual)
terraform-apply:
extends: .terraform
stage: apply
script:
- terraform apply -auto-approve plan.tfplan
dependencies:
- terraform-plan
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: manual
environment:
name: production
Azure Pipelines Implementation
Createazure-pipelines.yml:
Copy
trigger:
branches:
include:
- main
paths:
include:
- infrastructure/**
pr:
branches:
include:
- main
paths:
include:
- infrastructure/**
variables:
- group: cloudgeni-credentials
- name: TF_ROOT
value: $(System.DefaultWorkingDirectory)/infrastructure
stages:
# Stage 1: Security
- stage: Security
jobs:
- job: Scan
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
- task: Bash@3
displayName: Install Cloudgeni
inputs:
targetType: inline
script: |
curl -sSL https://get.cloudgeni.ai/install.sh | bash
- task: Bash@3
displayName: Security Scan
inputs:
targetType: inline
script: |
cloudgeni scan \
--api-key $(CLOUDGENI_API_KEY) \
--path $(TF_ROOT) \
--fail-on-critical \
--output json > $(Build.ArtifactStagingDirectory)/security-results.json
- task: PublishBuildArtifacts@1
displayName: Publish Security Results
inputs:
pathToPublish: $(Build.ArtifactStagingDirectory)/security-results.json
artifactName: security-results
condition: always()
# Stage 2: Validate
- stage: Validate
dependsOn: Security
jobs:
- job: TerraformValidate
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
- task: TerraformInstaller@1
inputs:
terraformVersion: latest
- task: TerraformTaskV4@4
displayName: Terraform Init
inputs:
provider: azurerm
command: init
workingDirectory: $(TF_ROOT)
- task: TerraformTaskV4@4
displayName: Terraform Validate
inputs:
provider: azurerm
command: validate
workingDirectory: $(TF_ROOT)
# Stage 3: Plan
- stage: Plan
dependsOn: Validate
condition: eq(variables['Build.Reason'], 'PullRequest')
jobs:
- job: TerraformPlan
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
- task: TerraformInstaller@1
inputs:
terraformVersion: latest
- task: TerraformTaskV4@4
displayName: Terraform Plan
inputs:
provider: azurerm
command: plan
workingDirectory: $(TF_ROOT)
environmentServiceNameAzureRM: azure-connection
Monitoring and Alerts
Scheduled Scans
Run nightly security scans:Copy
# GitHub Actions
on:
schedule:
- cron: '0 2 * * *' # 2 AM UTC daily
Slack Alerts
Configure alerts for:- New critical findings
- Compliance score drops
- Failed security checks
Dashboard Monitoring
Track in Cloudgeni dashboard:- Findings trend over time
- Compliance score history
- PR review statistics