Skip to content

The GitLab target

Configuring the GitLab target

gitlab_settings_target carries connection and authentication details for every entry point. It is validated by meta/argument_specs.yml, so a malformed target fails before any task runs.

discover requires url plus auth for capability reads and snapshot capture. REST and GraphQL apply operations also require url plus auth. Rails-only apply can omit both when version and edition provide the compatibility profile. Public task modules always require an API URL and authentication.

API authentication does not constrain Rails execution. Rails runs inside GitLab with the configured SSH or Kubernetes identity, so Rails execution is not available when auth declares a bound_to boundary or a project/group access token. Use a separate instance-authoritative invocation for Rails-only settings.

SSH configuration belongs to inventory

For the ssh_omnibus Rails strategy, SSH connectivity (user, keys, jump hosts, host keys) is not configured here — the Omnibus host is a normal inventory host selected with gitlab_settings_rails_host. See Execution Controls.

Example target definition

gitlab_settings_target:
  url: https://gitlab.example.com
  auth:
    type: personal_access_token
    token: "{{ vault_gitlab_admin_token }}"
  validate_certs: true
  ca_bundle: /etc/ssl/certs/internal-ca.pem
  rails_strategy: kubernetes_toolbox
  kubernetes:
    context: gitlab-lab
    namespace: gitlab
    toolbox_selector: app=toolbox,release=gitlab

Declaring authentication

Declare the credential once; its authentication type determines the request header shown below. Credentials are never placed in a URL.

auth is the only way to declare a credential. Its type is required because every authority, capability, and diagnostic decision downstream depends on knowing which kind of token it is.

auth.type Required fields Request authentication Notes
anonymous none no credential Only for operations GitLab documents as publicly readable. Never a fallback after a failure.
oauth2 access_token Authorization: Bearer Add refresh for managed rotation.
personal_access_token token PRIVATE-TOKEN Covers granular (fine-grained) tokens too; see below.
project_access_token token, bound_to (project) PRIVATE-TOKEN bound_to is a caller-declared maximum authority.
group_access_token token, bound_to (group) PRIVATE-TOKEN Covers the group and everything beneath it.
impersonation_token token PRIVATE-TOKEN Administrator-created; distinct diagnostics.
session_cookie session_cookie Cookie: _gitlab_session= Read-only; sessions are neither created nor refreshed.

Use credentials supported by the configuration endpoint

CI/CD job tokens and deploy tokens are not accepted. They authenticate GitLab's data plane -- package and container registries, and git over HTTP -- and GitLab accepts neither for a configuration operation. Configuring job-token policy and deploy keys remains fully supported as managed resources.

Granular personal access tokens

GitLab's fine-grained tokens (beta, behind the granular_personal_access_tokens feature flag) authenticate as personal_access_token with no extra declaration. Their permissions pair a boundary with actions -- an instance-level read_metadata beside membership-level project permissions, for example -- and a token missing one fails with GitLab's own sentence naming it: "This operation requires a fine-grained personal access token with the following project permissions: [Project: Read]". The role passes that diagnosis through verbatim instead of flattening it into generic authorization guidance.

Administrator delegation is an explicit modifier on a compatible OAuth or personal access token, and only where the operation documents it:

gitlab_settings_target:
  url: https://gitlab.example.com
  auth:
    type: personal_access_token
    token: "{{ vault_gitlab_admin_token }}"
    sudo:
      username: automation-user

Managed OAuth refresh

A caller-managed access token needs nothing else:

gitlab_settings_target:
  url: https://gitlab.example.com
  auth:
    type: oauth2
    access_token: "{{ vault_gitlab_oauth_access_token }}"

Enabling refresh is a deliberate decision because GitLab rotates and invalidates the previous token pair on every exchange. It requires the OAuth application ID, the redirect URI from the original authorization request, and a durable store:

gitlab_settings_target:
  url: https://gitlab.example.com
  auth:
    type: oauth2
    access_token: "{{ vault_gitlab_oauth_access_token }}"
    refresh_token: "{{ vault_gitlab_oauth_refresh_token }}"
    client_id: "{{ vault_gitlab_oauth_client_id }}"
    client_secret: "{{ vault_gitlab_oauth_client_secret }}"
    redirect_uri: https://automation.example.com/oauth/callback
    refresh:
      enabled: true
      before_expiry: 300
      store:
        type: controller_file
        path: /secure/ansible/oauth/gitlab-primary.json

Omit client_secret for a public PKCE client. An empty value is a missing-secret error, not an implicit downgrade from a confidential client.

The inline tokens seed the store on first use. After that the store is the source of truth, so later tasks and later plays use the current pair rather than the now-invalid inline values. Ordinary discovery and reconciliation tasks refresh transparently; no session task is required in a normal run.

controller_file is plaintext at rest

The store is protected by filesystem permissions (0600 files below a 0700 directory), not encryption. Use an encrypted filesystem where your threat model requires encryption at rest. It coordinates concurrent Ansible forks on one controller only: independent controllers must use separate OAuth credentials or prevent overlap.

Verifying a credential once

Registry validation runs inside every task already: an unsupported credential, an out-of-boundary token, or an unproven authority fails before a request is built, at no cost. What that cannot see is the credential's own state — GitLab alone knows whether it is active, what scopes it carries, and when it expires.

gitlab_credential_check compares that metadata with what the work ahead requires. Run it once, at the top of a play:

- name: Verify the credential can reconcile everything this play manages
  gitlab_credential_check:
    target: "{{ gitlab_settings_target }}"
    resources:
      - group
      - project
      - member
    write: true
    # Optional: backend (default api) selects which transports the
    # registry check considers; mode mirrors gitlab_settings_mode.

List every resource the play will touch. Their requirements are resolved locally from the registry, so the whole set is compared against a single metadata response — one request, not one per resource. An entry may be a mapping when its lifecycle or backend differs:

    resources:
      - resource: group
        write: true
      - branch          # read-only, so read scopes are enough

Do not loop this task instead: each iteration is a separate module invocation and therefore a separate request.

Every verdict this task can report, and every state the managed OAuth session can leave behind, is documented with its recovery step in Credential operations.

It reports an inactive or revoked credential, a token GitLab says is a different kind than you declared, an OAuth token issued to a different application, a reported expiry already past, or scopes that satisfy none of the operation's recorded alternatives — each as one clear failure naming the resource responsible, rather than an opaque 401 or 403 partway through a loop and possibly after a partial change.

It is a separate task rather than a target option because it costs one read-only request. Running it once keeps that cost at one request no matter how many resources the play then reconciles, and keeps the decision visible in the playbook. Omit resources to verify only the credential-level facts that need no operation context.

A resource the registry rejects for the declared credential fails before any request is made, because that decision is local.

The check is evidence-only. A metadata endpoint the credential cannot reach, or a response that omits a field, is reported as skipped rather than treated as a failure; absence of evidence is never evidence of insufficiency. Anonymous and session-cookie credentials expose no metadata, so they always report skipped.

Check mode

Discovery and reconciliation never initialize a credential store or rotate a token in check mode; they fail with a message telling you to establish the session first. Use the focused task with Ansible's standard check_mode keyword when you intend to permit maintenance during a --check run:

- name: Ensure the OAuth session is valid before check-mode API tasks
  gitlab_oauth_session:
    target: "{{ gitlab_settings_target }}"
  check_mode: false

Credential maintenance during ordinary tasks is reported as authentication.changed, never as the task's own changed, so a refresh does not notify handlers for a GitLab resource that did not change.

GitLab Target Spec

urlstringRequired for API operations

Base URL of the GitLab instance, without a trailing /api/v4. Required by discover and API-backed apply operations; optional for Rails-only apply.

Example:
gitlab_settings_target:
  url: https://gitlab.example.com
...

authobjectRequired for API operations

Structured API authentication. Required for discovery, snapshot capture, and API-backed apply operations; optional only for Rails-only apply. Use the least-privileged credential that can perform the declared operations. Credential fields are marked no_log.

Properties of auth

typestringRequired

Can be one of: anonymous, oauth2, personal_access_token, project_access_token, group_access_token, impersonation_token, session_cookie.


tokenstring

Personal, project, group, or impersonation token.


session_cookiestring

Existing caller-provided GitLab session cookie.


access_tokenstring

Caller-provided OAuth access token.


refresh_tokenstring

OAuth refresh token used to seed a managed refresh store.


client_idstring

OAuth application id from the original authorization flow.


client_secretstring

OAuth application secret for a confidential client. Omit for a public PKCE client.


redirect_uristring

Redirect URI from the original authorization request.


expires_atstring (date-time)

Optional OAuth access-token expiry as an RFC 3339 timestamp with a UTC offset.


refreshobject

Managed OAuth refresh policy.

Properties of auth.refresh

enabledboolean

Refresh and durably rotate the OAuth token pair. Default: false.


before_expiryinteger

Clock-skew window in seconds for proactive refresh. Default: 300.


storeobject

Durable credential store that owns the current token pair.

Properties of auth.refresh.store

typestringRequired

 Must be controller_file. It coordinates concurrent forks on one controller only.


pathstring (path)Required

 Absolute controller-side path of the session file.


bound_toobject

Required maximum authority for project and group access tokens.

Properties of auth.bound_to

resourcestringRequired

Can be one of: group, project.


identifierstringRequired

Full path of the group or project authority boundary.


sudoobject

Explicit administrator delegation. Declare exactly one child.

Properties of auth.sudo

idinteger

Numeric GitLab user ID to impersonate.


usernamestring

GitLab username to impersonate.

Example:
gitlab_settings_target:
  auth:
    type: personal_access_token
    token: "{{ vault_gitlab_admin_token }}"
...

validate_certsboolean

Whether to validate TLS certificates.

Default: true

Example:
gitlab_settings_target:
  validate_certs: false   # local lab over http/self-signed only
...

use_proxyboolean

Whether API requests may use proxy settings from the execution environment.

Default: true

Example:
gitlab_settings_target:
  use_proxy: false
...

ca_bundlestring (path)

CA bundle used to validate the target certificate, for instances behind a private CA.

Example:
gitlab_settings_target:
  ca_bundle: /etc/ssl/certs/internal-ca.pem
...

versionstring

Expected GitLab version, for example 19.2.0-ee. Version and edition are normally discovered through /api/v4/metadata; this value is an assertion, not an override, and must match discovery when both are available. Supply it for Rails-only, offline, or non-API targets. A version outside the verified set produces a warning rather than a failure; a 404 on a known endpoint reports version skew as a likely cause. See GitLab version support.

Example:
gitlab_settings_target:
  version: 19.2.0-ee
...

editionce or ee

Edition when version has no -ce or -ee suffix. Omit it when the version includes the suffix.

Example:
gitlab_settings_target:
  version: 19.2.0
  edition: ee
...

rails_strategystring

Transport used by the Rails backend.

Can be one of: kubernetes_toolbox, ssh_omnibus

Default: kubernetes_toolbox

Example:
gitlab_settings_target:
  rails_strategy: ssh_omnibus
...

kubernetesobject

Kubernetes access details for the kubernetes_toolbox strategy. Exactly one Running, Ready, non-terminating toolbox pod must match; commands execute through kubernetes.core modules.

Security boundary: use a dedicated Kubernetes principal limited to pod get/list and pod-exec creation in the GitLab namespace. A label selector is not authorization: isolate that namespace from principals that could create or relabel a pod to impersonate the selected toolbox workload.

Properties of kubernetes

kubeconfigstring (path)

Kubeconfig file; omit to use the environment default.


contextstring

Kubeconfig context; omit to use the current context.


namespacestring

Namespace of the GitLab chart deployment. Default: gitlab


containerstring

Toolbox container name. Default: toolbox


toolbox_selectorstringRequired for Kubernetes

Required release-specific label selector locating exactly one toolbox pod. Do not use a broad selector such as app=toolbox by itself.

Example:
gitlab_settings_target:
  rails_strategy: kubernetes_toolbox
  kubernetes:
    kubeconfig: ~/.kube/lab
    context: gitlab-lab
    namespace: gitlab
    container: toolbox
    toolbox_selector: app=toolbox,release=gitlab
...