Skip to main content

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

┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   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:
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

  1. Go to SettingsBranchesAdd rule
  2. Branch name pattern: main
  3. Enable:
    • Require a pull request before merging
    • Require status checks to pass
    • Select required checks:
      • Security Scan
      • Compliance Check
      • Terraform 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:
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

Create azure-pipelines.yml:
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:
# 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