From 9f7dce2f09e5f8b9bd92802b4ae9014b5df01f17 Mon Sep 17 00:00:00 2001 From: Martin Roy Date: Tue, 2 Dec 2025 16:34:31 -0500 Subject: [PATCH 1/9] BST-18006 Add the gitlab-ci scan tests --- .github/workflows/scan-test.yml | 31 +++++++++++++++++++++++++++++++ docs/setup-gitlab.md | 4 ++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/.github/workflows/scan-test.yml b/.github/workflows/scan-test.yml index 1a35e9cf..5dd26d33 100644 --- a/.github/workflows/scan-test.yml +++ b/.github/workflows/scan-test.yml @@ -67,3 +67,34 @@ jobs: } registry-repo: "${{ github.repository_owner }}/${{ github.event.repository.name }}" base-ref: "${{ github.base_ref }}" + + gitlab-ci: + name: Gitlab-CI + runs-on: ubuntu-latest + steps: + - name: Generate GitLab OAuth Token + id: gitlab-token + run: | + response=$(curl -s -X POST "https://gitlab.com/oauth/token" \ + -d "grant_type=client_credentials" \ + -d "client_id=${{ secrets.BOOST_SCAN_RUNNER_GITLAB_CLIENT_ID }}" \ + -d "client_secret=${{ secrets.BOOST_SCAN_RUNNER_GITLAB_CLIENT_SECRET }}") + + token=$(echo "$response" | jq -r '.access_token') + echo "token=$token" >> $GITHUB_OUTPUT + echo "::add-mask::$token" + - name: Checkout scanner registry + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Need full history to detect changes + - name: Run Tests + uses: boostsecurityio/scan-test-action@03526a5475206e034c62f3e2b8abafb9e8e15e85 + with: + provider: gitlab-ci + provider-config: | + { + "token": "${{ steps.github-token.outputs.token }}", + "project_id": "boostsecurityio/martin/boostsec-registry-test-runner", + } + registry-repo: "${{ github.repository_owner }}/${{ github.event.repository.name }}" + base-ref: "${{ github.head_ref }}" diff --git a/docs/setup-gitlab.md b/docs/setup-gitlab.md index ab21bfee..b563f855 100644 --- a/docs/setup-gitlab.md +++ b/docs/setup-gitlab.md @@ -52,8 +52,8 @@ Navigate to the scanner registry repository (GitHub): run: | response=$(curl -s -X POST "https://gitlab.com/oauth/token" \ -d "grant_type=client_credentials" \ - -d "client_id=${{ secrets.GITLAB_CLIENT_ID }}" \ - -d "client_secret=${{ secrets.GITLAB_CLIENT_SECRET }}") + -d "client_id=${{ secrets.BOOST_SCAN_RUNNER_GITLAB_CLIENT_ID }}" \ + -d "client_secret=${{ secrets.BOOST_SCAN_RUNNER_GITLAB_CLIENT_SECRET }}") token=$(echo "$response" | jq -r '.access_token') echo "token=$token" >> $GITHUB_OUTPUT From f93b7f5a2909a3c16ed8de9fd148082916464d4c Mon Sep 17 00:00:00 2001 From: Martin Roy Date: Tue, 2 Dec 2025 16:39:19 -0500 Subject: [PATCH 2/9] trigger ci From 1f5bc616da8829aa24a02439b0a5763c05d0ab55 Mon Sep 17 00:00:00 2001 From: Martin Roy Date: Wed, 3 Dec 2025 09:04:41 -0500 Subject: [PATCH 3/9] FIXUP change to trigger/pat tokens --- .github/workflows/scan-test.yml | 3 +- docs/authentication-strategy.md | 41 +++++++----- docs/setup-gitlab.md | 107 ++++++++++++++++++++------------ 3 files changed, 95 insertions(+), 56 deletions(-) diff --git a/.github/workflows/scan-test.yml b/.github/workflows/scan-test.yml index 5dd26d33..5c765c1a 100644 --- a/.github/workflows/scan-test.yml +++ b/.github/workflows/scan-test.yml @@ -93,7 +93,8 @@ jobs: provider: gitlab-ci provider-config: | { - "token": "${{ steps.github-token.outputs.token }}", + "trigger_token": "${{ secrets.BOOST_SCAN_RUNNER_GITLAB_TRIGGER_TOKEN }}", + "api_token": "${{ secrets.BOOST_SCAN_RUNNER_GITLAB_READ_TOKEN }}", "project_id": "boostsecurityio/martin/boostsec-registry-test-runner", } registry-repo: "${{ github.repository_owner }}/${{ github.event.repository.name }}" diff --git a/docs/authentication-strategy.md b/docs/authentication-strategy.md index 392003a9..fd0bd2c1 100644 --- a/docs/authentication-strategy.md +++ b/docs/authentication-strategy.md @@ -8,12 +8,12 @@ Migration from long-lived user tokens to short-lived OAuth2/OIDC tokens across a ## Authentication Solution -| Platform | Auth Method | Token Lifetime | User-Independent | -|------------------|---------------------------|----------------|------------------| -| **GitHub** | GitHub App | 1 hour | ✅ | -| **GitLab** | OAuth2 Application | 2 hours | ✅ | -| **Azure DevOps** | OIDC (Federated Identity) | ~1 hour | ✅ | -| **Bitbucket** | OAuth2 Consumer | 2 hours | ✅ | +| Platform | Auth Method | Token Lifetime | User-Independent | +|------------------|--------------------------------------|-----------------------|------------------| +| **GitHub** | GitHub App | 1 hour | ✅ | +| **GitLab** | Trigger Token + Read Token (2-token) | Trigger: ∞, Read: 1yr | ⚠️ Read token | +| **Azure DevOps** | OIDC (Federated Identity) | ~1 hour | ✅ | +| **Bitbucket** | OAuth2 Consumer | 2 hours | ✅ | ### Architecture Flow @@ -25,7 +25,7 @@ GitHub Actions Workflow Triggered ┌────────────────────────────────────┐ │ Token Generation (in GH Actions) │ │ - GitHub: Official Action │ -│ - GitLab: OAuth2 API call │ +│ - GitLab: Stored tokens (2-token) │ │ - Azure: OIDC (no secrets) │ │ - Bitbucket: OAuth2 API call │ └────────────────────────────────────┘ @@ -39,6 +39,17 @@ Trigger test pipelines on each platform Tokens expire automatically ``` +### GitLab Two-Token Approach + +GitLab does not support OAuth2 client credentials flow. We use two purpose-specific tokens: + +| Token | Purpose | Scope | +|----------------------------|-------------------|---------------------------------| +| **Pipeline Trigger Token** | Trigger pipelines | Can only trigger, no API access | +| **Project Access Token** | Poll status | `read_api` only, Guest role | + +This provides least-privilege access - neither token alone can both trigger and read. + ### Key Security Improvements - ✅ **Short-lived tokens** - Auto-expire in 1-2 hours (vs. indefinite) @@ -61,6 +72,8 @@ Tokens expire automatically **OIDC for GitHub API** - OIDC is for external services authenticating to GitHub, not for GitHub Actions triggering other GitHub workflows; GitHub Apps are the correct solution. +**GitLab OAuth2 Client Credentials** - GitLab does not support OAuth2 client credentials flow for machine-to-machine authentication; two-token approach (trigger + read) provides equivalent security with least-privilege separation. + --- ## Implementation Requirements @@ -68,23 +81,23 @@ Tokens expire automatically ### One-Time Setup (per platform) 1. **GitHub**: Register GitHub App with `contents: read`, `actions: write` permissions -2. **GitLab**: Create OAuth2 Application with `api` scope +2. **GitLab**: Create Pipeline Trigger Token + Project Access Token (`read_api`, Guest role) 3. **Azure DevOps**: Register Microsoft Entra ID application, add federated credential for GitHub Actions OIDC, grant Build (Read & Execute) to Azure DevOps project 4. **Bitbucket**: Create OAuth2 Consumer with `pipeline`, `pipeline:write`, `repository` scopes ### Secrets Configuration -Store client credentials in GitHub Actions repository secrets: -- `GH_APP_ID`, `GH_APP_PRIVATE_KEY` -- `GITLAB_CLIENT_ID`, `GITLAB_CLIENT_SECRET` -- `AZURE_TENANT_ID`, `AZURE_CLIENT_ID` (no client secret - uses OIDC; no subscription needed for Azure DevOps API) -- `BITBUCKET_CLIENT_ID`, `BITBUCKET_CLIENT_SECRET` +Store credentials in GitHub Actions repository secrets: +- `BOOST_SCAN_RUNNER_GITHUB_APP_ID`, `BOOST_SCAN_RUNNER_GITHUB_APP_PRIVATE_KEY` +- `BOOST_SCAN_RUNNER_GITLAB_TRIGGER_TOKEN`, `BOOST_SCAN_RUNNER_GITLAB_READ_TOKEN` +- `BOOST_SCAN_RUNNER_ADO_TENANT_ID`, `BOOST_SCAN_RUNNER_ADO_CLIENT_ID` (no client secret - uses OIDC) +- `BOOST_SCAN_RUNNER_BITBUCKET_CLIENT_ID`, `BOOST_SCAN_RUNNER_BITBUCKET_CLIENT_SECRET` ### Code Changes - **GitHub Actions workflow**: Add token generation steps (GitHub App action, OAuth API calls, azure/login with OIDC) - **GitHub Actions permissions**: Add `id-token: write` permission for Azure OIDC -- **test-action CLI**: Accept `--{platform}-token` arguments instead of client credentials +- **test-action CLI**: Accept `--{platform}-token` arguments (GitLab: `--gitlab-trigger-token`, `--gitlab-read-token`) - **Providers**: Use provided tokens directly instead of generating them --- diff --git a/docs/setup-gitlab.md b/docs/setup-gitlab.md index b563f855..4698883f 100644 --- a/docs/setup-gitlab.md +++ b/docs/setup-gitlab.md @@ -1,5 +1,14 @@ # GitLab Test Runner Setup +GitLab does not support OAuth2 client credentials flow. Instead, we use a **two-token approach** for least-privilege access: + +| Token | Purpose | Permissions | +|----------------------------|----------------------|------------------------------| +| **Pipeline Trigger Token** | Trigger pipelines | Trigger only (no API access) | +| **Project Access Token** | Poll pipeline status | `read_api` scope, Guest role | + +--- + ## 1. Create Test Runner Repository 1. Create a new project named `scan-test-runner-gitlab-ci` in your GitLab group @@ -8,75 +17,91 @@ --- -## 2. Create OAuth2 Application +## 2. Create Pipeline Trigger Token + +1. Navigate to your project: + **Project → Settings → CI/CD → Pipeline trigger tokens** -1. Navigate to your group settings: - **Group → Settings → Applications** + Or: `https://gitlab.com/{GROUP}/{PROJECT}/-/settings/ci_cd#js-pipeline-triggers` - Or for self-hosted: `https://{GITLAB_HOST}/groups/{GROUP}/-/settings/applications` +2. Click **Add new token** + +3. Enter a description: `Scanner Registry Test Orchestrator` + +4. Click **Create pipeline trigger token** + +5. Copy the **trigger token** - it's only shown once! + +--- -2. Click **Add new application** +## 3. Create Project Access Token (for polling) -3. Configure the application: +1. Navigate to your project: + **Project → Settings → Access tokens** - | Field | Value | - |------------------|------------------------------------------------------| - | **Name** | `BoostSecurity.io Scan Test Runner` | - | **Redirect URI** | `http://localhost` (not used for client credentials) | - | **Confidential** | ✅ Checked | - | **Scopes** | ✅ `api` | + Or: `https://gitlab.com/{GROUP}/{PROJECT}/-/settings/access_tokens` -4. Click **Save application** +2. Click **Add new token** -5. Note the **Application ID** and **Secret** - the secret is only shown once! +3. Configure the token: + + | Field | Value | + |---------------------|------------------------------------------------| + | **Token name** | `Scanner Registry Status Poller` | + | **Expiration date** | Set to 1 year (maximum), add rotation reminder | + | **Role** | **Guest** (minimal) | + | **Scopes** | ✅ `read_api` only | + +4. Click **Create project access token** + +5. Copy the **token** - it's only shown once! --- -## 3. Configure Secrets on Scanner Registry Repository +## 4. Configure Secrets on Scanner Registry Repository Navigate to the scanner registry repository (GitHub): **Settings → Secrets and variables → Actions → New repository secret** -| Secret Name | Value | -|------------------------------------------|----------------------------| -| `BOOST_SCAN_RUNNER_GITLAB_CLIENT_ID` | Application ID from step 2 | -| `BOOST_SCAN_RUNNER_GITLAB_CLIENT_SECRET` | Secret from step 2 | +| Secret Name | Value | +|-----------------------------------------|------------------------------------| +| `BOOST_SCAN_RUNNER_GITLAB_TRIGGER_TOKEN` | Pipeline trigger token from step 2 | +| `BOOST_SCAN_RUNNER_GITLAB_READ_TOKEN` | Project access token from step 3 | --- -## 4. Usage in GitHub Actions Workflow +## 5. Usage in GitHub Actions Workflow ```yaml -- name: Generate GitLab OAuth Token - id: gitlab-token +- name: Run test-action run: | - response=$(curl -s -X POST "https://gitlab.com/oauth/token" \ - -d "grant_type=client_credentials" \ - -d "client_id=${{ secrets.BOOST_SCAN_RUNNER_GITLAB_CLIENT_ID }}" \ - -d "client_secret=${{ secrets.BOOST_SCAN_RUNNER_GITLAB_CLIENT_SECRET }}") + use "${{ secrets.BOOST_SCAN_RUNNER_GITLAB_TRIGGER_TOKEN }}" \ + use "${{ secrets.BOOST_SCAN_RUNNER_GITLAB_READ_TOKEN }}" +``` - token=$(echo "$response" | jq -r '.access_token') - echo "token=$token" >> $GITHUB_OUTPUT - echo "::add-mask::$token" +--- -- name: Run test-action - ... -``` +## 6. Token Details + +| Token | Lifetime | Scope | Rotation | +|-------------------|---------------|------------------------|--------------------------------| +| **Trigger Token** | No expiration | Trigger pipelines only | Manual (revoke if compromised) | +| **Read Token** | 1 year max | `read_api` (read-only) | Annual rotation required | --- -## 5. Token Details +## 7. Security Notes -| Property | Value | -|--------------|--------------------------------------------------| -| **Lifetime** | 2 hours | -| **Refresh** | New token generated per workflow run | -| **Scope** | `api` (required for pipeline trigger and status) | +- **Trigger token** can only start pipelines - cannot read data or modify anything +- **Read token** has Guest role with `read_api` - cannot trigger or modify anything +- Neither token can access code, settings, or other projects +- Separation ensures compromise of one token limits blast radius --- ## References -- [GitLab OAuth2 Provider](https://docs.gitlab.com/ee/integration/oauth_provider.html) -- [GitLab OAuth2 API](https://docs.gitlab.com/ee/api/oauth2.html#client-credentials-flow) -- [GitLab Pipelines API](https://docs.gitlab.com/ee/api/pipelines.html) +- [Pipeline Trigger Tokens](https://docs.gitlab.com/ee/ci/triggers/) +- [Project Access Tokens](https://docs.gitlab.com/ee/user/project/settings/project_access_tokens.html) +- [GitLab API Authentication](https://docs.gitlab.com/ee/api/rest/#authentication) +- [Pipelines API](https://docs.gitlab.com/ee/api/pipelines.html) From dc32dd7fe315f1ae3d2ef6314d841a63a79e2b63 Mon Sep 17 00:00:00 2001 From: Martin Roy Date: Wed, 3 Dec 2025 18:49:35 -0500 Subject: [PATCH 4/9] FIXUP use new auth --- .github/workflows/scan-test.yml | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/.github/workflows/scan-test.yml b/.github/workflows/scan-test.yml index 5c765c1a..6f7790ed 100644 --- a/.github/workflows/scan-test.yml +++ b/.github/workflows/scan-test.yml @@ -72,30 +72,19 @@ jobs: name: Gitlab-CI runs-on: ubuntu-latest steps: - - name: Generate GitLab OAuth Token - id: gitlab-token - run: | - response=$(curl -s -X POST "https://gitlab.com/oauth/token" \ - -d "grant_type=client_credentials" \ - -d "client_id=${{ secrets.BOOST_SCAN_RUNNER_GITLAB_CLIENT_ID }}" \ - -d "client_secret=${{ secrets.BOOST_SCAN_RUNNER_GITLAB_CLIENT_SECRET }}") - - token=$(echo "$response" | jq -r '.access_token') - echo "token=$token" >> $GITHUB_OUTPUT - echo "::add-mask::$token" - name: Checkout scanner registry uses: actions/checkout@v4 with: fetch-depth: 0 # Need full history to detect changes - name: Run Tests - uses: boostsecurityio/scan-test-action@03526a5475206e034c62f3e2b8abafb9e8e15e85 + uses: boostsecurityio/scan-test-action@2410d5ae4661d6dbe63a744b037aae9db7bd066e with: provider: gitlab-ci provider-config: | { "trigger_token": "${{ secrets.BOOST_SCAN_RUNNER_GITLAB_TRIGGER_TOKEN }}", "api_token": "${{ secrets.BOOST_SCAN_RUNNER_GITLAB_READ_TOKEN }}", - "project_id": "boostsecurityio/martin/boostsec-registry-test-runner", + "project_id": "boostsecurityio/scan-test-runner-gitlab-ci", } registry-repo: "${{ github.repository_owner }}/${{ github.event.repository.name }}" base-ref: "${{ github.head_ref }}" From 94ae896030a59b2b0593e67c92cc1ba1f7f605ee Mon Sep 17 00:00:00 2001 From: Martin Roy Date: Wed, 3 Dec 2025 18:58:07 -0500 Subject: [PATCH 5/9] FIXUp json --- .github/workflows/scan-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scan-test.yml b/.github/workflows/scan-test.yml index 6f7790ed..82a2b44f 100644 --- a/.github/workflows/scan-test.yml +++ b/.github/workflows/scan-test.yml @@ -84,7 +84,7 @@ jobs: { "trigger_token": "${{ secrets.BOOST_SCAN_RUNNER_GITLAB_TRIGGER_TOKEN }}", "api_token": "${{ secrets.BOOST_SCAN_RUNNER_GITLAB_READ_TOKEN }}", - "project_id": "boostsecurityio/scan-test-runner-gitlab-ci", + "project_id": "boostsecurityio/scan-test-runner-gitlab-ci" } registry-repo: "${{ github.repository_owner }}/${{ github.event.repository.name }}" base-ref: "${{ github.head_ref }}" From 9f4fa69e8b7cfc08577da4da7f88719c02809869 Mon Sep 17 00:00:00 2001 From: Martin Roy Date: Thu, 4 Dec 2025 09:27:13 -0500 Subject: [PATCH 6/9] FIXUP use the right ref --- .github/workflows/scan-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scan-test.yml b/.github/workflows/scan-test.yml index 82a2b44f..80fd27a0 100644 --- a/.github/workflows/scan-test.yml +++ b/.github/workflows/scan-test.yml @@ -87,4 +87,4 @@ jobs: "project_id": "boostsecurityio/scan-test-runner-gitlab-ci" } registry-repo: "${{ github.repository_owner }}/${{ github.event.repository.name }}" - base-ref: "${{ github.head_ref }}" + base-ref: "${{ github.base_ref }}" From c6b8e883e5df7f8d98fe0fe2a987defad23637aa Mon Sep 17 00:00:00 2001 From: Martin Roy Date: Thu, 4 Dec 2025 11:33:47 -0500 Subject: [PATCH 7/9] FIXUP update doc --- docs/setup-gitlab.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/setup-gitlab.md b/docs/setup-gitlab.md index 4698883f..0648384c 100644 --- a/docs/setup-gitlab.md +++ b/docs/setup-gitlab.md @@ -32,6 +32,8 @@ GitLab does not support OAuth2 client credentials flow. Instead, we use a **two- 5. Copy the **trigger token** - it's only shown once! +6. Ensure the ** CI/CD > Variables > Minimum role to use pipeline variables ** is set to **Developer** + --- ## 3. Create Project Access Token (for polling) From f1f72e15a968ca0d4b5cf124fb557d1d4c53a875 Mon Sep 17 00:00:00 2001 From: Martin Roy Date: Thu, 4 Dec 2025 11:37:17 -0500 Subject: [PATCH 8/9] Update test action --- .github/workflows/scan-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scan-test.yml b/.github/workflows/scan-test.yml index 80fd27a0..94dfcd87 100644 --- a/.github/workflows/scan-test.yml +++ b/.github/workflows/scan-test.yml @@ -77,7 +77,7 @@ jobs: with: fetch-depth: 0 # Need full history to detect changes - name: Run Tests - uses: boostsecurityio/scan-test-action@2410d5ae4661d6dbe63a744b037aae9db7bd066e + uses: boostsecurityio/scan-test-action@53e2f687ab93ac5d150b88abd7341b72f6fbf384 with: provider: gitlab-ci provider-config: | From 447a08241e313318d940f963c06e5f31b38ab76f Mon Sep 17 00:00:00 2001 From: Martin Roy Date: Thu, 4 Dec 2025 13:41:16 -0500 Subject: [PATCH 9/9] trigger tests --- scanners/boostsecurityio/trivy-fs/tests.yaml | 10 +++++----- scanners/boostsecurityio/trivy-image/tests.yaml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scanners/boostsecurityio/trivy-fs/tests.yaml b/scanners/boostsecurityio/trivy-fs/tests.yaml index d19c65d5..239eb64e 100644 --- a/scanners/boostsecurityio/trivy-fs/tests.yaml +++ b/scanners/boostsecurityio/trivy-fs/tests.yaml @@ -1,12 +1,12 @@ version: "1.0" tests: - - name: "gitleaks" - type: "source-code" - source: - url: "git@github.com:gitleaks/gitleaks.git" - ref: "v8.15.2" - name: "osv-scanner" type: "source-code" source: url: "git@github.com:google/osv-scanner.git" ref: "main" + - name: "gitleaks" + type: "source-code" + source: + url: "git@github.com:gitleaks/gitleaks.git" + ref: "v8.15.2" diff --git a/scanners/boostsecurityio/trivy-image/tests.yaml b/scanners/boostsecurityio/trivy-image/tests.yaml index 73e729ce..68b3c391 100644 --- a/scanners/boostsecurityio/trivy-image/tests.yaml +++ b/scanners/boostsecurityio/trivy-image/tests.yaml @@ -6,5 +6,5 @@ tests: url: "https://github.com/martin-boost-dev/boost-poc-registry-testing-trivy" ref: "main" scan_paths: - - "rclone" - "osv-scanner" + - "rclone"