Configuring Workload Identity Federation for GitLab CI

This guide explains how to configure workload identity federation for GitLab CI, allowing your GitLab pipelines to authenticate with Thalassa Cloud without storing long-lived credentials.

Prerequisites

Before configuring workload identity federation for GitLab CI, ensure you have:

  • A GitLab project with CI/CD enabled
  • Access to your Thalassa Cloud organisation
  • A service account created in Thalassa Cloud with appropriate IAM roles
  • Permissions to create identity providers and federated identities
  • GitLab 15.7 or later (OIDC support)

Step 1: Create Identity Provider for GitLab

Step 1: Navigate to Identity Providers

Navigate to IAMIdentity Providers in your Thalassa Cloud Console.

Step 2: Create GitLab Identity Provider

Click “Create Identity Provider” and configure:

Basic Information:

  • Name: gitlab (or a descriptive name)
  • Description: Optional description, e.g., “GitLab CI OIDC provider”

OIDC Configuration:

  • OIDC Issuer URL: Your GitLab instance’s issuer URL
    • For GitLab.com: https://gitlab.com
    • For self-hosted GitLab: https://your-gitlab-instance.com
    • The issuer URL is the base URL of your GitLab instance

JWKS Configuration:

  • Use Automatic Discovery (recommended)
    • Leave the JWKS endpoint blank
    • Thalassa Cloud will automatically discover the JWKS endpoint from GitLab’s well-known configuration at https://your-gitlab-instance.com/.well-known/openid-configuration
    • For GitLab.com: https://gitlab.com/.well-known/openid-configuration

Alternative: Custom JWKS Endpoint

  • If automatic discovery doesn’t work, you can specify:
    • For GitLab.com: https://gitlab.com/-/jwks
    • For self-hosted GitLab: https://your-gitlab-instance.com/-/jwks

Step 3: Save Identity Provider

Click “Create Identity Provider” to save the configuration.

Step 2: Create Federated Identity

Step 1: Navigate to Service Accounts

Navigate to IAMService Accounts in your Thalassa Cloud Console.

Step 2: Select Service Account

Select the service account you want to configure for GitLab CI federation.

Step 3: Create Federated Identity

Click “Add Federated Identity” or “Workload Identity Federation”.

Step 4: Configure Token Matching

Configure how tokens from GitLab CI are matched:

Identity Provider:

  • Select the GitLab identity provider you created in Step 1

Token Matching:

GitLab CI OIDC tokens include several claims that you can use for matching:

  • Subject Claim (sub): The most important claim for matching. Format varies:

    • Project path: project_path:NAMESPACE/PROJECT:ref_type:BRANCH_TYPE:ref:BRANCH_NAME
    • Example for main branch: project_path:mygroup/myproject:ref_type:branch:ref:main
    • Example for tags: project_path:mygroup/myproject:ref_type:tag:ref:v1.0.0
    • Example for merge requests: project_path:mygroup/myproject:ref_type:merge_request:ref:123
  • Additional Claims (optional):

    • project_path: The full project path (e.g., mygroup/myproject)
    • namespace_path: The namespace/group path (e.g., mygroup)
    • project_id: The numeric project ID
    • ref: The git reference (e.g., main, refs/heads/main)
    • ref_type: The reference type (branch, tag, merge_request)
    • pipeline_id: The pipeline ID
    • pipeline_source: The pipeline source (e.g., push, merge_request_event)
    • environment: The environment name if using GitLab Environments

Recommended Matching Patterns:

For maximum security, use specific subject claims:

  • Match specific project and branch: project_path:mygroup/myproject:ref_type:branch:ref:main
  • Match production environment: Use environment-specific claims
  • Match merge requests: project_path:mygroup/myproject:ref_type:merge_request:ref:*

You can use wildcards for more flexibility:

  • Match all branches in a project: project_path:mygroup/myproject:ref_type:branch:ref:*
  • Match all projects in a group: project_path:mygroup/*:ref_type:branch:ref:main

API Scopes:

  • Select the API scopes your pipeline needs (e.g., read, write)
  • Follow the principle of least privilege—only grant necessary scopes

Expiry (Optional):

  • Set an expiry date if this is a temporary federation

Step 5: Save Federated Identity

Click “Create Federated Identity” to complete the setup.

Step 3: Configure GitLab CI Pipeline

Now configure your GitLab CI pipeline to use workload identity federation.

Basic Pipeline Example

stages:
  - deploy

deploy:
  stage: deploy
  image: alpine:latest
  id_tokens:
    THALASSA_ID_TOKEN:
      aud: https://api.thalassa.cloud
  before_script:
    - apk add --no-cache curl jq
  script:
    # Exchange GitLab OIDC token for Thalassa Cloud bearer token
    - |
      BEARER_TOKEN=$(curl -X POST https://api.thalassa.cloud/oidc/token \
        -H "Content-Type: application/x-www-form-urlencoded" \
        -d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
        -d "subject_token=${THALASSA_ID_TOKEN}" \
        -d "subject_token_type=urn:ietf:params:oauth:token-type:id_token" \
        -d "organisation_id=${THALASSA_ORGANISATION_ID}" \
        -d "service_account_id=${THALASSA_SERVICE_ACCOUNT_ID}" \
        | jq -r '.access_token')
    
    # Export bearer token for use in subsequent commands
    - export THALASSA_TOKEN="${BEARER_TOKEN}"
    
    # Use the token with Terraform or other tools
    - terraform init
    - terraform plan
    - terraform apply -auto-approve

Using GitLab CI/CD Variables

Store your organisation ID and service account ID as GitLab CI/CD variables:

  1. Navigate to your project or group settings
  2. Go to SettingsCI/CD
  3. Expand Variables
  4. Add variables:
    • THALASSA_ORGANISATION_ID: Your Thalassa Cloud organisation ID (masked)
    • THALASSA_SERVICE_ACCOUNT_ID: Your service account ID (masked)

Using GitLab Environments

For better security and control, you can use GitLab Environments:

deploy:production:
  stage: deploy
  environment:
    name: production
  image: alpine:latest
  id_tokens:
    THALASSA_ID_TOKEN:
      aud: https://api.thalassa.cloud
  script:
    # ... same as above

When using environments, you can match tokens based on environment-specific claims.

Advanced Example with Multiple Stages

stages:
  - plan
  - apply

variables:
  THALASSA_API: "https://api.thalassa.cloud"

plan:
  stage: plan
  image: hashicorp/terraform:latest
  id_tokens:
    THALASSA_ID_TOKEN:
      aud: ${THALASSA_API}
  before_script:
    # Exchange token and export
    - |
      export THALASSA_TOKEN=$(curl -X POST ${THALASSA_API}/oidc/token \
        -H "Content-Type: application/x-www-form-urlencoded" \
        -d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
        -d "subject_token=${THALASSA_ID_TOKEN}" \
        -d "subject_token_type=urn:ietf:params:oauth:token-type:id_token" \
        -d "organisation_id=${THALASSA_ORGANISATION_ID}" \
        -d "service_account_id=${THALASSA_SERVICE_ACCOUNT_ID}" \
        | jq -r '.access_token')
  script:
    - terraform init
    - terraform plan -out=tfplan

apply:
  stage: apply
  image: hashicorp/terraform:latest
  id_tokens:
    THALASSA_ID_TOKEN:
      aud: ${THALASSA_API}
  when: manual
  only:
    - main
  before_script:
    - |
      export THALASSA_TOKEN=$(curl -X POST ${THALASSA_API}/oidc/token \
        -H "Content-Type: application/x-www-form-urlencoded" \
        -d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
        -d "subject_token=${THALASSA_ID_TOKEN}" \
        -d "subject_token_type=urn:ietf:params:oauth:token-type:id_token" \
        -d "organisation_id=${THALASSA_ORGANISATION_ID}" \
        -d "service_account_id=${THALASSA_SERVICE_ACCOUNT_ID}" \
        | jq -r '.access_token')
  script:
    - terraform apply tfplan

Testing the Configuration

  1. Trigger a pipeline by pushing to your repository or manually running a pipeline
  2. Monitor the pipeline to verify the OIDC token exchange succeeds
  3. Check logs to ensure the bearer token is being used correctly
  4. Verify permissions by testing API calls or Terraform operations

Troubleshooting

OIDC Token Exchange Fails

  • Verify the subject claim in your federated identity matches the actual token claims
  • Check that the identity provider issuer URL matches your GitLab instance
  • Ensure the service account ID and organisation ID are correct
  • Verify the aud (audience) claim matches your API endpoint

Token Matching Fails

  • Decode your OIDC token to inspect the actual claims:
    # In GitLab CI, add a debug job
    debug:
      image: alpine:latest
      script:
        - apk add --no-cache jq
        - echo "${THALASSA_ID_TOKEN}" | cut -d. -f2 | base64 -d | jq
  • Compare the actual claims with your federated identity matching rules
  • Adjust your subject claim or additional claims to match the token

Permission Denied

  • Verify the service account has the required IAM roles
  • Check that the API scopes configured in the federated identity match your needs
  • Review the service account’s role bindings

Self-Hosted GitLab Issues

If using self-hosted GitLab, ensure:

  • The issuer URL matches your GitLab instance URL exactly
  • JWKS endpoint is accessible (Thalassa Cloud must be able to reach it)
  • TLS/SSL certificates are valid

Best Practices

  • Use Specific Subject Claims: Match specific projects and branches rather than using broad wildcards
  • Use GitLab Environments: Leverage GitLab Environments for better security and pipeline control
  • Least Privilege: Grant only the minimum required API scopes
  • Separate Jobs: Use different service accounts for different stages (plan vs apply)
  • Monitor Usage: Regularly review federated identity usage through audit logs
  • Rotate Regularly: Set expiry dates and rotate federated identities periodically
  • Use Protected Branches: Combine with GitLab protected branches for additional security

Related Documentation