GitLab
Connect GitLab to Team Server, place the Runtime Sensor on the runner, and instrument each protected CI job with Endura commands.
Object Mapping
| GitLab | Team Server |
|---|---|
| Group | Discovery scope |
| Project | Pipeline |
| CI job | Build |
Discover the project before instrumenting it. Each CI job becomes a build; all jobs share the project’s policy.
Project identity uses canonical path_with_namespace, matching the Sensor’s CI_PROJECT_PATH. Nested namespaces remain intact, and shared projects retain their owning namespace.
Prerequisites
- Operator or Administrator access to Team Server
- GitLab group access that can create a read token
- Permission to edit
.gitlab-ci.yml - Root access to self-managed runner hosts
- For GitLab-hosted runners, an Endura license and Runtime Sensor token stored as protected CI/CD variables
Configure the Integration
Credentials and Permissions
Choose the narrowest token that covers the groups Team Server must discover. Use a group access token for one group and its subgroups. To discover groups outside that hierarchy, use a personal access token created for a dedicated service account.
Grant the token read_api scope and give its identity at least the Reporter role for every private group or project Team Server must discover. The broader api scope also works, but is not required. Set an expiry and rotation owner; store the token in a secrets manager.
GitLab 19.2 and later also support fine-grained personal access tokens. Instead of read_api, configure these fine-grained REST permissions:
| Resource | Permission | Access boundary | Team Server use |
|---|---|---|---|
| User | Read | User | Test the credential with GET /user. |
| Group | Read | User | List visible groups with GET /groups. |
| Project | Read | Each required group | List group projects with GET /groups/:id/projects. |
| Job | Read | Each required project | Read CI job history with GET /projects/:id/jobs. |
The permission boundaries and the token owner’s GitLab membership both limit access. Grant every required boundary, but no write permissions.
The token must discover at least one group
Team Server validates group discovery before saving the integration. If the token discovers no groups, confirm the token owner belongs to a selected group and has read_api or the required fine-grained permissions. Successful authentication alone is not enough.
Add GitLab to Team Server
- Open Integrations, then GitLab under Discover.
- Select Get Started or Create.
- Enter
https://gitlab.comor the base URL of a self-managed instance. - Paste the token and select Test.
- Select one or more groups. The choices show each full group path, including parent namespaces.
- Select Save.
Projects owned by or shared with those groups appear under Pipelines. Shared projects retain their canonical owning namespace. Use separate integrations for separate GitLab instances.
For a private self-managed address or internal CA, configure Team Server outbound HTTP policy.
Schedule Discovery
Add these jobs to scheduler.jobs:
gitlab_pipelines:
run: "gitlab_sync mode:pipelines trigger:scheduled"
schedule: "0 4 * * * * *"
gitlab_builds:
run: "gitlab_sync mode:builds trigger:scheduled"
schedule: "0 6,21,36,51 * * * * *"Keep statistics_calculate and status_reconcile enabled. Restart Team Server after changing configuration; see Scheduler.
Manage Sync and Deletion
Use Start Sync for an immediate run. See sync controls and deletion.
Choose the Runner Pattern
Choose the pattern that matches the executor. The examples in this guide support Linux x86-64 runners.
| Runner and executor | Runtime Sensor location | Endura lifecycle | Checkout coverage |
|---|---|---|---|
| GitLab-hosted Linux, Docker Machine | Inside the job container | Start the Sensor and job, then stop the job and Sensor in the main shell | No, the helper container retrieves source first |
| Self-managed Shell or Instance | Runner host or instance | Start and stop the Endura job | Yes, with pre_get_sources_script |
| Self-managed Docker, Docker Autoscaler, or Docker Machine | Runner host or autoscaled VM | Mount the host socket, then start and stop the Endura job | No, the helper container retrieves source first |
| Self-managed Kubernetes | DaemonSet on every eligible node | Mount the node socket, then start and stop the Endura job | No, the helper container retrieves source first |
Windows, macOS, non-x86-64 runners, and executors without the required Linux kernel access are outside these examples.
GitLab-hosted Linux Runners
GitLab-hosted Linux runners use the Docker Machine executor and run each job in a privileged container. Create masked and protected ENDURA_LICENSE_KEY and ENDURA_SENSOR_TOKEN variables under Settings > CI/CD > Variables. Protected variables are available only to pipelines running on protected branches or tags.
Use the following complete pattern:
variables:
ENDURA_LOG_FILE: "${CI_PROJECT_DIR}/endura-sensor.log"
ENDURA_TEAM_SERVER: "https://team-server.example.com"
default:
image: ubuntu:26.04
tags:
- saas-linux-small-amd64
before_script:
# Install Runtime Sensor prerequisites.
- apt-get update -qq
- DEBIAN_FRONTEND=noninteractive apt-get install -y -qq curl ca-certificates
# Install Endura.
- curl -sSf https://repo.endurasecurity.com/install/endura-sensor/testing.sh | sh
# Mount the kernel filesystems required by the Runtime Sensor.
- |
mkdir -p /sys/fs/bpf
if ! mountpoint -q /sys/fs/bpf; then
mount -t bpf bpf /sys/fs/bpf
fi
mkdir -p /sys/kernel/security
if ! mountpoint -q /sys/kernel/security; then
mount -t securityfs securityfs /sys/kernel/security
fi
# Start the Runtime Sensor and protected job in the main shell.
- |
cleanup_endura() {
build_rc=$?
trap - EXIT
set +e
endura job stop
job_stop_rc=$?
endura sensor stop
sensor_stop_rc=$?
if [ "${build_rc}" -ne 0 ]; then
exit "${build_rc}"
fi
if [ "${job_stop_rc}" -ne 0 ]; then
exit "${job_stop_rc}"
fi
exit "${sensor_stop_rc}"
}
trap cleanup_endura EXIT
endura sensor start -d
unset ENDURA_LICENSE_KEY ENDURA_SENSOR_TOKEN
endura sensor status
endura sensor hooks
ENDURA_JOB_ID=$(endura job start --workspace "${CI_PROJECT_DIR}")
echo "started endura job with id ${ENDURA_JOB_ID}"
stages:
- build
build:
stage: build
script:
- apt-get update -qq
- DEBIAN_FRONTEND=noninteractive apt-get install -y -qq make nodejs npm
- make build
artifacts:
when: always
paths:
- endura-sensor.logbefore_script and script share one shell. The EXIT trap stops both the job and Sensor before that shell exits; the later after_script has separate variables, functions, and process lifetime. Cleanup preserves build failures and fails otherwise successful work if either stop fails. Forced termination can prevent cleanup and artifact collection.
The container uses the host kernel and runs as root, so no sudo is needed. The mounts expose bpffs and securityfs. A missing CAP_SYS_NICE causes a scheduling warning but does not prevent startup; check sensor status for connectivity and root-only sensor hooks for coverage. Credentials are removed before project-controlled work; artifacts: when: always retains the project-directory log after ordinary success or failure.
The Docker helper retrieves source first, so checkout is unprotected. This pattern was validated on docker+machine with GitLab Runner 19.5.0~pre, an observed version rather than a minimum requirement.
Self-managed Shell and Instance Executors
Install the Runtime Sensor on every Linux x86-64 host behind the runner tags used by protected jobs. Bake it into images or provisioning for autoscaled Instance runners.
For the Shell executor, use top-level defaults to instrument every job:
variables:
AFTER_SCRIPT_IGNORE_ERRORS: "false"
default:
tags:
- endura
hooks:
pre_get_sources_script:
- mkdir -p "${CI_PROJECT_DIR}"
- ENDURA_JOB_ID=$(endura job start)
- echo "started endura job with id ${ENDURA_JOB_ID}"
after_script:
- endura job stop
build:
script:
- make buildpre_get_sources_script protects checkout and submodules. after_script runs after success, script failure, and normal cancellation. Keep AFTER_SCRIPT_IGNORE_ERRORS=false to make cleanup failures fail otherwise successful jobs.
Force cancellation, source-preparation failure, and timeout without cleanup time can skip after_script. Job-level hooks/after_script replace defaults; restore Endura commands when overriding. Keep cancellation retirement enabled as fallback.
The Runtime Sensor service reads its license, token, and Team Server URL from the root-only host configuration. Do not copy those values into .gitlab-ci.yml for a persistent runner. after_script does not share shell variables with the main script, but this pattern does not need them because the active job is identified by its workspace.
Self-managed Docker-family Executors
For Docker, Docker Autoscaler, and Docker Machine executors, run the Runtime Sensor on the runner host or autoscaled VM. Mount its socket into build containers in config.toml:
[[runners]]
[runners.docker]
volumes = ["/run/endura/sensor.sock:/run/endura/sensor.sock"]Restart the runner after editing its configuration. Install the CLI before job start:
variables:
AFTER_SCRIPT_IGNORE_ERRORS: "false"
default:
tags:
- endura-docker
before_script:
- curl -sSf https://repo.endurasecurity.com/install/endura-sensor/testing.sh | sh
- ENDURA_JOB_ID=$(endura job start)
- echo "started endura job with id ${ENDURA_JOB_ID}"
after_script:
- endura job stopUse endura-docker (or your fleet tag) only for runners with the Sensor and socket. Bake root-only Sensor configuration into every autoscaled VM. The helper retrieves source before job start, leaving checkout unprotected.
Self-managed Kubernetes Executor
Deploy the Runtime Sensor as a DaemonSet on every Linux x86-64 node eligible to run GitLab build Pods. Do not start a second Sensor inside the build container.
Mount the node’s Runtime Sensor socket directory through the runner’s config.toml:
[[runners]]
[runners.kubernetes]
[[runners.kubernetes.volumes.host_path]]
name = "endura-run"
mount_path = "/run/endura"
host_path = "/run/endura"
read_only = falseUse the Docker example’s CLI installation, job start, and after_script stop. The build Pod needs a writable socket mount from its own node’s Sensor. Helper checkout precedes before_script and remains unprotected.
Child and Multi-stage Pipelines
Defaults apply unless jobs replace hooks, before_script, or after_script. Restore Endura commands in overrides and include them in separately configured child pipelines. Each job is a build; the project shares one policy. Derive across the full parent/child flow and keep cancellation retirement enabled.
Standardize Instrumentation
Store the Endura defaults in a central project and adopt them with include:
include:
- project: platform/ci-templates
ref: <commit-sha>
file: /endura.yml
build:
script:
- make buildUse extends when jobs need to merge custom setup with the shared wrapper. Pin the include to an immutable revision where possible.
Compliance pipelines are deprecated. Execution policies inject separate jobs; they do not wrap arbitrary project jobs with Endura.
Verify
- Run the complete instrumented pipeline.
- Open the project under Pipelines in Team Server.
- Confirm each protected GitLab job appears under Builds.
- Confirm
endura sensor statusreports the expected Team Server connection andendura sensor hooksreports the expected hook coverage on the runner. - If a job is missing, check project discovery, runner tags, overridden defaults, and job logs.
Troubleshooting
| Symptom | Check |
|---|---|
| No groups or projects appear | Confirm the token has read_api (or api) or the required fine-grained User, Group, Project, and Job Read permissions. Confirm the token owner belongs to the selected full group path, then verify access to each owned or shared project under its canonical namespace and check both GitLab discovery jobs. |
| A CI job does not appear | Confirm the project was discovered first, the Runtime Sensor was online on the selected runner, and the applicable pre_get_sources_script or before_script start commands ran. |
| A GitLab-hosted runner cannot start the Runtime Sensor | Review the installer and mount output. Confirm /sys/kernel/btf/vmlinux, bpffs, securityfs, and the required eBPF capabilities are available. |
| Sensor startup warns about real-time scheduling | Check whether CAP_SYS_NICE is available. The warning does not prevent startup; use endura sensor status and endura sensor hooks to evaluate health and hook coverage. |
| A Docker-executor job cannot reach the Runtime Sensor | Confirm the socket mount is present in the runner’s config.toml, then restart the runner. |
| A Kubernetes-executor job cannot reach the Runtime Sensor | Confirm the DaemonSet runs on the build node and /run/endura is mounted from that node into the build Pod. |
| A self-managed job remains active | Confirm no job overrides the default after_script without restoring endura job stop. Check for force cancellation or a timeout that left no cleanup time. |
| A hosted job has no final Sensor log | Confirm the log path is under $CI_PROJECT_DIR and the artifact uses when: always. Forced runner termination can prevent both the trap and artifact collection. |
Create and Assign a Policy
Derive across representative parent and child jobs and capture the policy printed by endura job stop. Refine and test it in observe mode, then assign it to the GitLab project pipeline before enforcement.
Configure notifications after violations reach Team Server.