Documentation
¶
Overview ¶
Package gitlab implements the gitlab source plugin: lists projects ("repositories") and members under a single GitLab group and emits the cross-vendor git_repository and directory_user evidence types — suitable for SOC 2 / ISO 27001 branch-protection, code-review, and identity (MFA/admin/lifecycle) policies.
GitLab is one of several substitutable source-code platforms (GitHub, Bitbucket, future Gitea/Azure DevOps); a policy accepts the git_repository evidence type, not a vendor, so this plugin is immediately usable by every policy that already consumes GitHub repositories (the substitutability principle — docs/architecture/04a-evidence-type-registry.md).
Per the KISS-no-DRY axiom (docs/architecture/04-source-plugins.md §The plugin contract), the plugin caches nothing across Collect calls. N policies bound to this plugin → N invocations of Collect.
Test injection: the API interface mirrors the github plugin — the concrete *sdkAPI (backed by gitlab.com/gitlab-org/api/client-go) satisfies it, and unit tests inject an in-memory fake. The real SDK adapter is exercised by an httptest-backed test; live integration tests against gitlab.com are deferred to the testing revamp.
Period-scoped types: alongside the two configuration snapshots the plugin also emits pull_request (one record per merge request MERGED inside the audit window) and deployment (one record per release to a GitLab environment in the window, failed ones included). GitLab merge requests map onto the cross-vendor pull_request type deliberately — the type is vendor-neutral, so one CC8.1 / ISO A.8.32 change-review policy samples GitHub PRs and GitLab MRs through a single contract. The window arrives as period_start / period_end slot params; a caller that passes none (the conformance harness) gets a trailing one-year window ending at the injected clock, never time.Now().
directory_user (WU-2.2): the plugin also lists the group's members and emits one directory_user per member, mapping AccessLevel ≥ Maintainer (or instance-admin) → is_admin, account state → is_active, and the user's two_factor_enabled → mfa_enabled. 2FA and instance-admin status are only readable with a group-owner / instance-admin token (via the Users API); without that privilege the sdkAPI degrades gracefully and mfa_enabled is best-effort false — documented in docs/configuration.md.
Index ¶
Constants ¶
const EvidenceTypeDeployment = "deployment"
EvidenceTypeDeployment is the cross-vendor deployment shape — one record per release to a running environment during the period, failed ones included.
const EvidenceTypeDirectoryUser = "directory_user"
EvidenceTypeDirectoryUser is the cross-vendor directory_user shape this plugin emits for the group's members — identical to the shape the github and okta plugins emit (the same identity evidence type).
const EvidenceTypePullRequest = "pull_request"
EvidenceTypePullRequest is the cross-vendor pull_request shape — one record per change MERGED during the audit period. A GitLab merge request IS that change; the evidence type is vendor-neutral, so the github and gitlab plugins are substitutable for a change-review policy. It answers "did this change actually get reviewed?", which the git_repository configuration snapshot structurally cannot.
const EvidenceTypeRepository = "git_repository"
EvidenceTypeRepository is the cross-vendor git_repository shape this plugin emits.
const SourceID = "gitlab"
SourceID is the registered ID for the gitlab plugin instance.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type API ¶
type API interface {
// ListRepos returns all projects under the configured group,
// normalized into the git_repository contract. Implementations must
// page transparently — callers receive the full list in one slice.
ListRepos(ctx context.Context) ([]Repo, error)
// ListMembers returns all members of the configured group, normalized
// into the directory_user contract. Implementations must page
// transparently — callers receive the full list in one slice.
ListMembers(ctx context.Context) ([]Member, error)
// ListMergedPullRequests returns the group's merge requests merged
// inside [start, end], normalized into the pull_request contract.
// Implementations must page transparently and may over-return —
// the plugin re-applies the window, so it owns the period semantics
// regardless of which API implementation is wired in.
ListMergedPullRequests(ctx context.Context, start, end time.Time) ([]PullRequest, error)
// ListDeployments returns the group's deployments created inside
// [start, end], failed ones included, normalized into the deployment
// contract. Implementations must page transparently and may
// over-return; the plugin re-applies the window.
ListDeployments(ctx context.Context, start, end time.Time) ([]Deployment, error)
}
API is the subset of the GitLab REST API the plugin uses. Defining it as an interface lets tests inject a fake without making real network calls; the concrete *sdkAPI satisfies it.
type Deployment ¶ added in v0.71.0
type Deployment struct {
Repository string
// ID is the vendor-native deployment identifier as a string.
ID string
SHA string
// Environment is the environment name exactly as GitLab reports it.
Environment string
// EnvironmentTier is GitLab's environment tier ("production",
// "staging", …). Empty when the project never set one, in which case
// the plugin falls back to the environment name for is_production.
EnvironmentTier string
Creator string
CreatedAt time.Time
// Status is the raw GitLab deployment status (created / running /
// success / failed / canceled / blocked), normalized by the plugin
// into the deployment type's closed vocabulary.
Status string
}
Deployment is one release of code to a GitLab environment during the audit period, including failed ones — a failed production deployment is still a change event an auditor may ask about.
type Member ¶ added in v0.39.0
type Member struct {
Username string
Name string
Email string
MFAEnabled bool
IsAdmin bool
IsActive bool
}
Member is the subset of fields the plugin extracts from a GitLab group member, normalized into the directory_user contract. Every field maps to a property the consuming policies read so none reads an absent field.
MFAEnabled and the instance-admin component of IsAdmin require a group-owner / instance-admin token (the Users API exposes two_factor_enabled / is_admin); the sdkAPI degrades gracefully when the token can't read them, leaving MFAEnabled best-effort false.
type Options ¶
type Options struct {
API API
// Now is injected so tests can produce deterministic CollectedAt
// values. Production callers leave it nil → time.Now().UTC().
Now func() time.Time
}
Options is the constructor input.
type Plugin ¶
type Plugin struct {
// contains filtered or unexported fields
}
Plugin is the in-process gitlab source.
func New ¶
New constructs a Plugin around an explicit API implementation. Callers using the real GitLab REST API should use NewFromToken.
func NewFromToken ¶
NewFromToken constructs a Plugin backed by the real GitLab REST API for the given group, using the supplied personal-access/group token. A blank baseURL defaults to gitlab.com; self-managed instances pass their own URL. Live integration tests are deferred.
func (*Plugin) Caveats ¶ added in v0.81.0
func (*Plugin) Caveats() []core.SourceCaveat
Caveats declares the fields this plugin can emit without observing them.
Two different limits live in this one list, and the Detail text says which applies, because the operator's remedy is completely different:
- ABSOLUTE — the three pipeline-scanning booleans. GitLab configures SAST, Secret Detection and Dependency Scanning in .gitlab-ci.yml, not as a project-settings field, so there is no read-only API to ask and the plugin hardcodes false (see mapProject). Their policies therefore cannot pass on a GitLab estate — only ever fail — so the honest remedy is a documented exception or a compensating check, not a token change.
- CONDITIONAL — mfa_enabled, and the three protection/approval fields. The read IS possible; a lesser-privileged token or a free-tier project just gets a 403/404 that the sdkAPI deliberately swallows so the listing still succeeds. What is left behind is the zero value, which is indistinguishable from a measured "not configured". Provisioning the right token (or tier) makes the value real.
The conditional ones are declared unconditionally because privilege and tier are not known at plan time: those reads happen during collection, long after the planner has to decide whether to warn. Over-warning a correctly-privileged token is the cheaper error — the operator checks the scope and moves on, where the silent case grades a control on a value nobody read.
Every field here is read by at least one shipped SOC 2 / ISO 27001 policy; a field GitLab cannot observe but no policy consumes costs nothing and is deliberately absent, so the planner's warnings stay worth reading.
func (*Plugin) Collect ¶
func (p *Plugin) Collect(ctx context.Context, req core.SlotRequest) ([]core.EvidenceRecord, error)
Collect dispatches to the per-type collectors for whichever emitted types the slot accepts, returning their records together. A slot that accepts none of the emitted types is rejected. Records are sorted by ID within each type group; the collector splits them by Type for envelope writing.
type PullRequest ¶ added in v0.71.0
type PullRequest struct {
// Repository is the project's full path ("group/project"), the same
// form git_repository uses for name, so the two types join.
Repository string
// Number is the MR iid — the per-project number a customer quotes in
// a waiver, not the instance-wide id.
Number int
Author string
MergedBy string
TargetBranch string
// MergeCommitSHA is the commit the MR produced on the target branch;
// the adapter substitutes the squash commit when the project squashes
// and GitLab therefore reports no merge commit.
MergeCommitSHA string
MergedAt time.Time
// Approvers carries the usernames GitLab reports in approved_by,
// verbatim — the plugin de-duplicates and drops blanks. Empty when
// the approvals endpoint could not be read (graceful degradation) or
// when nobody approved.
Approvers []string
// ChecksPassed is true when the newest pipeline attached to the MR
// succeeded. GitLab exposes no per-check aggregate comparable to
// GitHub's check runs, so the head pipeline is the CI signal.
ChecksPassed bool
}
PullRequest is one merge request MERGED into a project under the group during the audit period. The adapter supplies the raw vendor facts — GitLab's flat approver list and the newest MR pipeline's outcome; the plugin derives approval_count / independent_approval_count / approved_before_merge from them, so the normalization is unit-testable without a network.
type Repo ¶
type Repo struct {
Name string
DefaultBranch string
ProtectionOn bool
RequiredReviews int
RequiresSignedCommits bool
RequiresLinearHistory bool
AllowsForcePush bool
DismissStaleReviews bool
RequireCodeOwnerReviews bool
IsPrivate bool
Archived bool
SecretScanningEnabled bool
PushProtectionEnabled bool
DependabotAlertsEnabled bool
CodeScanningEnabled bool
}
Repo is the subset of fields the plugin extracts from a GitLab project, normalized into the git_repository contract. Every field maps to a property of the git_repository evidence type so policies never read an absent field.
Fields without a read-only GitLab API analog are left at their zero value and documented at the mapping site (sdkAPI.ListRepos):
- SecretScanningEnabled / CodeScanningEnabled / DependabotAlertsEnabled (GitLab pipeline SAST/Secret/Dependency scanning is configured in .gitlab-ci.yml, not exposed as a project-settings boolean).