Skip to content

CI/CD Pipeline

ScamShield AI uses GitHub Actions for continuous integration and deployment. Pushing to main triggers an automated test-then-deploy pipeline. Pull requests and feature branches run tests only.

Pipeline Overview

Push to feature branch / PR to main
  └── test.yml: Run pytest suite

Push to main
  └── deploy.yml:
       ├── test job: Run pytest suite
       ├── deploy job: Deploy Cloud Functions (Firebase)
       └── deploy-dashboard job: Deploy Dashboard (Cloud Run)

Workflows

test.yml -- Test on PRs and Feature Branches

Trigger: Push to any non-main branch, or pull request targeting main.

name: Run Tests

on:
  push:
    branches-ignore: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - run: |
          pip install -r functions/requirements.txt
          pip install pytest
      - run: pytest tests/ -v

deploy.yml -- Deploy on Push to Main

Trigger: Push to main, or manual dispatch from the GitHub Actions UI.

The deploy workflow has three jobs:

  1. test -- Runs the full pytest suite
  2. deploy -- Deploys Cloud Functions via Firebase CLI (depends on test passing)
  3. deploy-dashboard -- Deploys the Streamlit dashboard to Cloud Run (depends on test passing)

Both deploy jobs use Workload Identity Federation for authentication (no stored secrets).

Workload Identity Federation (WIF)

Why WIF Over Stored Secrets

Traditional GitHub Actions deployments store a GCP service account key as a GitHub Secret. This approach has problems:

  • Key rotation burden -- JSON keys don't expire automatically
  • Blast radius -- A leaked key grants full service account access until revoked
  • Audit gaps -- Hard to trace which workflow used a key

Workload Identity Federation eliminates stored keys entirely. Instead, GitHub Actions exchanges a short-lived OIDC token for GCP credentials at runtime.

How OIDC Token Exchange Works

1. GitHub Actions generates a signed OIDC JWT
   (claims include: repository, ref, workflow, actor)

2. JWT is sent to GCP's Security Token Service (STS)
   via the google-github-actions/auth action

3. STS validates the JWT against the WIF pool/provider config
   (checks issuer, repository_owner attribute condition)

4. STS returns a federated access token

5. The federated token is exchanged for a short-lived
   service account access token (1-hour TTL)

6. All subsequent gcloud/firebase commands use this token

Running setup-wif.sh

The repository includes a one-time setup script at the project root:

chmod +x setup-wif.sh
./setup-wif.sh

Before running, edit the variables at the top of the script:

PROJECT_ID="your-gcp-project-id"
GITHUB_REPO="your-github-org/scamshield-ai"
GITHUB_OWNER="your-github-org"

The script performs these steps:

  1. Enables APIs -- iamcredentials, sts, cloudresourcemanager
  2. Creates service account -- firebase-github-deploy@your-gcp-project-id.iam.gserviceaccount.com
  3. Grants IAM roles -- See required roles below
  4. Creates WIF pool -- github-actions-pool
  5. Creates OIDC provider -- github-actions-provider (issuer: token.actions.githubusercontent.com)
  6. Binds WIF to service account -- Restricted to your GitHub organization's repositories

After running, the script outputs the WIF_PROVIDER and SERVICE_ACCOUNT values to use in deploy.yml.

Service Account IAM Roles

The deploy service account needs these roles:

Role Purpose
roles/cloudfunctions.developer Deploy Cloud Functions
roles/run.developer Deploy Cloud Run services
roles/iam.serviceAccountUser Act as the service account
roles/firebaserules.admin Deploy Firestore security rules
roles/firebasehosting.admin Deploy Firebase Hosting (if used)
roles/secretmanager.viewer Read secret metadata for deployment
roles/storage.objectAdmin Upload function source to Cloud Storage
roles/firebase.admin General Firebase admin operations
roles/artifactregistry.writer Push Docker images for Cloud Run
roles/datastore.indexAdmin Manage Firestore indexes
roles/serviceusage.serviceUsageConsumer Enable/check API usage

Workflow Authentication Config

In deploy.yml, the auth step references your WIF provider and service account:

permissions:
  contents: read
  id-token: write  # Required for OIDC token generation

steps:
  - uses: google-github-actions/auth@v2
    with:
      workload_identity_provider: "projects/YOUR_PROJECT_NUMBER/locations/global/workloadIdentityPools/github-actions-pool/providers/github-actions-provider"
      service_account: "firebase-deploy@your-gcp-project-id.iam.gserviceaccount.com"

The id-token: write permission is required

Without id-token: write in the workflow's permissions block, the GitHub Actions runner cannot generate the OIDC token, and authentication will fail silently.

Manual Deploy Trigger

You can trigger a deployment manually from the GitHub Actions UI:

  1. Go to Actions > Deploy to Firebase
  2. Click Run workflow
  3. Select the main branch
  4. Click Run workflow

This is configured via workflow_dispatch in deploy.yml:

on:
  push:
    branches: [main]
  workflow_dispatch:  # Enables manual trigger

Troubleshooting

"Unable to detect a Firebase project"

The Firebase CLI needs a project reference. Ensure deploy.yml includes --project your-gcp-project-id:

firebase deploy --project your-gcp-project-id --force

"Error: PERMISSION_DENIED"

Check that:

  1. The service account has all required IAM roles
  2. The WIF binding is correct (check attribute.repository matches your repo)
  3. The id-token: write permission is set in the workflow

Verify IAM roles:

gcloud projects get-iam-policy your-gcp-project-id \
  --flatten="bindings[].members" \
  --filter="bindings.members:firebase-github-deploy@" \
  --format="table(bindings.role)"

"venv not found" During Deploy

The deploy job must create functions/venv before running firebase deploy:

- name: Create venv and install dependencies
  run: |
    python -m venv functions/venv
    functions/venv/bin/pip install -r functions/requirements.txt

This step is already in deploy.yml but can fail if the Python version does not match (must be 3.11).

Tests Pass Locally but Fail in CI

Common causes:

  • Missing test dependencies -- Ensure pytest is installed in the CI job
  • Python version mismatch -- CI uses the version specified in setup-python. Ensure it matches 3.11
  • Import path issues -- Tests run from the repository root. Ensure PYTHONPATH includes functions/ or that test imports are correctly configured via conftest.py

WIF Provider Not Found

If the auth step fails with "provider not found":

# Verify the provider exists
gcloud iam workload-identity-pools providers describe github-actions-provider \
  --workload-identity-pool=github-actions-pool \
  --location=global \
  --project=your-gcp-project-id

If it does not exist, re-run setup-wif.sh.