Skip to content

Connect GitLab to Team Server, place the Runtime Sensor on the runner, and instrument each protected CI job with Endura commands.

Object Mapping

GitLabTeam Server
GroupDiscovery scope
ProjectPipeline
CI jobBuild

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:

ResourcePermissionAccess boundaryTeam Server use
UserReadUserTest the credential with GET /user.
GroupReadUserList visible groups with GET /groups.
ProjectReadEach required groupList group projects with GET /groups/:id/projects.
JobReadEach required projectRead 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

  1. Open Integrations, then GitLab under Discover.
  2. Select Get Started or Create.
  3. Enter https://gitlab.com or the base URL of a self-managed instance.
  4. Paste the token and select Test.
  5. Select one or more groups. The choices show each full group path, including parent namespaces.
  6. 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 executorRuntime Sensor locationEndura lifecycleCheckout coverage
GitLab-hosted Linux, Docker MachineInside the job containerStart the Sensor and job, then stop the job and Sensor in the main shellNo, the helper container retrieves source first
Self-managed Shell or InstanceRunner host or instanceStart and stop the Endura jobYes, with pre_get_sources_script
Self-managed Docker, Docker Autoscaler, or Docker MachineRunner host or autoscaled VMMount the host socket, then start and stop the Endura jobNo, the helper container retrieves source first
Self-managed KubernetesDaemonSet on every eligible nodeMount the node socket, then start and stop the Endura jobNo, 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.log

before_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 build

pre_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 stop

Use 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 = false

Use 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 build

Use 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

  1. Run the complete instrumented pipeline.
  2. Open the project under Pipelines in Team Server.
  3. Confirm each protected GitLab job appears under Builds.
  4. Confirm endura sensor status reports the expected Team Server connection and endura sensor hooks reports the expected hook coverage on the runner.
  5. If a job is missing, check project discovery, runner tags, overridden defaults, and job logs.

Troubleshooting

SymptomCheck
No groups or projects appearConfirm 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 appearConfirm 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 SensorReview 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 schedulingCheck 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 SensorConfirm the socket mount is present in the runner’s config.toml, then restart the runner.
A Kubernetes-executor job cannot reach the Runtime SensorConfirm the DaemonSet runs on the build node and /run/endura is mounted from that node into the build Pod.
A self-managed job remains activeConfirm 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 logConfirm 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.