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:
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¶
|
Base URL of the GitLab instance, without a trailing | Example: |
|
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 Properties of
| Example: |
|
Whether to validate TLS certificates. Default: | Example: |
|
Whether API requests may use proxy settings from the execution environment. Default: | Example: |
|
CA bundle used to validate the target certificate, for instances behind a private CA. | Example: |
|
Expected GitLab version, for example | Example: |
|
Edition when | Example: |
|
Transport used by the Rails backend. Can be one of: Default: | Example: |
|
Kubernetes access details for the 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
| Example: |