GitLab IAM Service
This service provides Identity and Access Management (IAM) capabilities
specifically tailored for GitLab. Designed with modularity in mind, it offers a
unified codebase that can be compiled to serve distinct purposes, ensuring
flexibility and optimized deployments.
It is the main codebase to implement GitLab Adaptive Trust Environment.
Capabilities
- Auth Provider: Manages OAuth 2.0 and OIDC flows, enabling secure authentication and authorization for applications integrating with GitLab.
- Lookup API: Facilitates efficient retrieval of identity and access-related data stored within the database.
- Update API: Provides a robust API for securely updating identity and access information in the database.
- IAM Transform: Transforms GitLab permissions into a format optimized for rapid lookup, stored in the database.
- Envoy Control Plane: xDS APIs to integrate the IAM service with Envoy as a control plane.
- Secure Token Service: Token exchange and URT signing capabilities.
Design
The GitLab IAM service is designed for flexibility and can be deployed in
several distinct modes to suit different environments, from local development
to production.
Single Binary, Single-Process Mode
This is the default and simplest mode for development and testing. A single
iam binary runs all microservices (lookup, sts, update, auth) concurrently as
goroutines within one process.
- How it works: Run
iam --serve all.
- Use Case: Ideal for local development, debugging, and simple
deployments where ease of use is paramount.
- Communication: Services communicate over the network (
localhost) via
their standard gRPC interfaces, ensuring consistency with distributed
deployments.
Single Binary, Multi-Process Mode
The same iam binary can be used to launch a single, specific microservice.
- How it works: Use the
--serve flag to specify the service (e.g., iam --serve lookup).
- Use Case: Useful for deployments where services are managed
individually by an external orchestrator (e.g., Kubernetes, systemd) but
sourced from a single container image.
Multiple Binaries, Multiple Processes Mode
For maximum separation and traditional microservice deployments, each service
can be compiled into its own standalone binary.
- How it works: Each service has a dedicated
main package (e.g.,
cmd/lookup/main.go) that can be built into a separate binary (e.g.,
lookup, sts). The Makefile provides targets for this (make lookup,
make sts, etc.).
- Use Case: Production deployments where services need to be scaled,
monitored, and managed independently. This provides the highest level of
isolation.
Development
Run scripts/prepare-dev-env.sh to install build tools first.
Building
The Makefile provides targets for all build scenarios:
make: Build all targets.
make iam: Builds the primary multi-service binary.
make lookup, make sts, make update, make auth: Build the individual service binaries.
make up, make down: Start and stop dependent services like the database.
Linting
While we catch linter and formatting errors in CI, it's best to catch these pre-CI.
Run linters once:
make lint
Storage backends
We support multiple storage backends:
Before running the service or the tests, the dependent services need to be started using the command:
make up
Testing
make test: Runs the entire test suite using the development-l2 environment (Postgres).
make test-l1: Runs the entire test suite using the development-l1 environment (YugabyteDB).
Continuous Integration (.gitlab-ci.yml)
The pipeline is defined with three stages (lint, verify, test) and uses
pinned, compatible versions of Go (golang:1.24-alpine) and golangci-lint to
ensure stable, reproducible builds. This specific Go version is required by the
go.mod file.
Invoking RPCs
To invoke RPCs from any of the gRPC servers locally, use grpcurl:
grpcurl -plaintext -proto proto/lookup/lookup.proto localhost:5001 lookup.Lookup/Health
{
"status": "ok"
}
Test with grpcurl
# Get all users
grpcurl -plaintext localhost:5001 lookup.Lookup/GetUsers
# Get a specific user by ID
grpcurl -plaintext -d '{"user_id": "456"}' localhost:5001 lookup.Lookup/GetUser
Configuration
The IAM service uses a layered TOML configuration system. On startup, three files are merged in order — each layer overrides values from the previous one:
base.toml — shared defaults for all environments (HTTP timeouts, CORS defaults, service addresses)
- Environment Config file — environment-specific overrides (database host/port, CORS origins, service URLs)
- Secrets file — credentials that must never be committed (database password, HMAC secrets, OAuth client secrets)
These three files use a shared structure — platform-wide settings live under [platform] and per-service settings under [services.<name>],
so all services draw from the same merged config, all resolved from configs/ at the repo root with environment-specific files under configs/environments/.
Environment variables
| Variable |
Description |
Default |
ENV |
Selects the environment config file as ./configs/environments/$ENV.toml |
development-l2 |
CONFIG_FILE_PATH |
Absolute path to the environment config file. Overrides the default ./configs/environments/$ENV.toml when set. |
— |
SECRETS_FILE_PATH |
Absolute path to the secrets file. Overrides the default ./configs/environments/secrets.toml when set. |
— |
Deployed Environments
- Set the
ENV variable for the environment that is being deployed. Possible values are: sandbox-l1, sandbox-l2, staging-l1, staging-l2, production-l1, production-l2
- Mount the env config file with the name
ENV.toml and the secrets file with the name secrets.toml under /app/configs/environments.
e.g., if ENV is staging-l1, the config file should be named staging-l1.toml
File Resolution
/app/configs/
├── base.toml # always loaded first
└── environments/
├── staging-l1.toml # ENV=staging-l1
└── secrets.toml # loaded last
Special Case - Runway:
Runway currently does not support mounting multiple files to the same directory without overwriting them.
As a workaround, CONFIG_FILE_PATH and SECRETS_FILE_PATH can be set to explicitly specify where to locate the env config file and the secrets file. These are temporary and will be removed soon.
Example: custom paths
CONFIG_FILE_PATH=/etc/iam/config.toml \
SECRETS_FILE_PATH=/run/secrets/iam-secrets.toml \
./iam --serve all
Local Development and CI
- Set
ENV to the environment you want to work with (development-l1 or development-l2, default is development-l2): e.g. ENV=development-l1 make run-all
- The repo ships these config files ready to use:
./configs/
├── base.toml # always loaded first
└── environments/
├── development-l1.toml # ENV=development-l1
├── development-l2.toml # ENV=development-l2 (default)
├── ci-l1.toml # ENV=ci-l1
├── ci-l2.toml # ENV=ci-l2
└── secrets.toml # shipped with placeholder values for local development; not baked into docker images
Tests that load config call testhelper.ChdirRepoRoot(t) in their setup, which changes the working directory to the repo root for the duration of the test.
This lets config.Load resolve ./configs correctly regardless of which package directory the test runs from.
Commit messages
This project uses Conventional Commits to drive automated releases via semantic-release. Releases produce a git tag and a GitLab Release, which downstream tooling (e.g., Runway) uses to deploy.
Since we generally squash on merge, the MR title is the message that gets analyzed.
<type>(<optional scope>): <description>
Common types
| Type |
Triggers release |
Example |
feat |
minor (1.2.0) |
feat: add OAuth scope validation |
fix |
patch (1.2.3) |
fix: handle expired tokens correctly |
perf |
patch (1.2.3) |
perf: cache JWKS responses |
chore |
none |
chore: bump go-jose to v4 |
docs |
none |
docs: clarify token rotation |
refactor |
none |
refactor: extract token helper |
test |
none |
test: add coverage for STS endpoint |
ci |
none |
ci: pin runner image version |
Breaking changes
Append ! after the type, or include a BREAKING CHANGE: footer:
feat!: drop support for v1 tokens
feat: redesign token refresh flow
BREAKING CHANGE: clients must re-authenticate after upgrade
Notes
- Non-conventional MR titles won't break the build, they're silently skipped by the analyzer and no release happens.
- Use
fix: for any change that should produce a deployable artifact, even if "fix" feels semantically loose. chore:, docs:, etc. will not produce a release.
Deployment
Sandbox environment
IAM services are deployed to a sandbox environment. The sandbox environment is a Kubernetes cluster under a GCP project maintained by the team. Much of this config lives in this sandbox-config repo. Please see that repo's README for details.
Deployments to the sandbox environment are automatically triggered on a green main pipeline.
Staging environment
We're deploying just the iam-auth service via Runway GKE. We have two workloads: one serving HTTP, and one serving gRPC.
Runway
- HTTP workload
- Provisioned in this MR
- Runway service ID:
iam-auth-gke-ext
- Runway project ID: 74293132
- Access control: group-level (
gitlab-org/software-supply-chain-security/authentication/authentication-runway-access)
- gRPC workload
- Access control: group-level (
gitlab-org/software-supply-chain-security/authentication/authentication-runway-access)
- Provisioning TBD
Project configuration and Vault
To support Runway deploys, the iam GitLab project was onboarded to GitLab's infra-mgmt repository. infra-mgmt is a central location to manage project configs via Terraform. The iam project was onboarded in this MR.
Communication between Runway infra and the iam project's build pipeline is enabled by an access token stored in Vault. We also have a Vault path configured for the iam-auth-gke-ext workload where we store application secrets:
Links