Git, CI/CD & GitOpsLXXXIX · Repository SecurityRepoSecurity
Access control and the principle of least privilege
What you'll learn
- Map the GitHub and GitLab repository role ladders onto the operations each role can actually perform
- Choose team-based grants over individual grants and justify the choice in audit terms
- Scope machine identities to the narrowest credential that still completes the job
- Run a periodic access review that produces revocations rather than a signed list
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
Least privilege is easy to agree with and hard to keep. The permission list of a five-year-old infrastructure repository is usually a historical record of everyone who has ever been adjacent to the project, and the reason is structural: access is granted at the moment of need, under time pressure, and removed at no moment in particular. This lesson is about the grants themselves and the review that removes them.
The role ladder
Both major forges expose a small ladder of repository roles. The names differ; the operational distinctions are close:
| GitHub | GitLab | What it adds |
|---|---|---|
| Read | Guest / Reporter | Clone, open issues, comment |
| Triage | Reporter | Manage issues and PR metadata |
| Write | Developer | Push branches, open and merge PRs |
| Maintain | Maintainer | Manage settings short of destruction |
| Admin | Owner | Protection rules, secrets, deletion |
Two boundaries matter more than the rest. The first is between read and write: write is the ability to place code in the repository, and everything downstream - CI runs, review requests, protection rules - exists to constrain what happens after that placement. The second is between maintain and admin: admin is the ability to change the constraints themselves, including turning off branch protection.
Teams, not people
Granting access to an individual creates a permission that
belongs to a person, which means it has no lifecycle. Nobody
knows why alice has write on infra-prod; the grant
outlives the project that justified it.
Granting access to a team creates a permission that belongs to a role, and the lifecycle comes for free: joining the platform team confers the access, leaving it removes the access, and the answer to “why does this person have write?” is “because they are on the team that owns this repository”.
flowchart TD
A["identity provider group"] --> B["forge team: platform-oncall"]
B --> C["repo: infra-prod (write)"]
B --> D["repo: infra-modules (write)"]
E["individual grant: alice (admin)"] -.->|"no lifecycle, no owner"| C
F["offboarding"] --> A
F -.->|"misses"| E
The dotted edges are where audits find the problem: the individual grant survives the offboarding process because the offboarding process removes group membership, and the grant was never a group membership.
Machine identities are not small humans
A pipeline, a bot, and a deploy key are identities too, and they should be scoped harder than humans because they are unattended:
- Deploy keys attach to one repository. Make them read-only unless the pipeline genuinely writes back; a read-only deploy key that leaks is a disclosure, a read-write one is a supply-chain event.
- Tokens should carry the narrowest scope and the shortest life the job tolerates. A token that can read one repository is not the same risk as a token that can administer an organisation.
- Forge apps are usually the right answer at scale: permissions are declared, installation is per-repository, and the credential is short-lived by construction.
REPO=example-org/infra-prod
gh api "repos/$REPO/collaborators" \
--jq '.[] | [.login, .role_name] | @tsv'
The review that actually removes access
A workable cadence for an infrastructure repository:
- Quarterly, list every principal with write or above, including bots, deploy keys, and outside collaborators.
- Cross-check against the identity provider. Anyone not in an owning group is a finding, not a discussion.
- Revoke first, restore on request. Restoration is cheap and instantaneous; standing access is neither.
- Record the review with a date and the revocation count. A review with no artefact did not happen, and an auditor will treat it that way.
Production discipline
- Default to read. Write is granted for a named piece of work by someone accountable, and the grant is recorded.
- No individual grants on production repositories. If a person needs access, the team they are on needs access, or they should join the team that has it.
- Machine identities get a named owner and an expiry. An unowned token with no expiry is the credential that will still be valid three years after the service it served was decommissioned.
Cross-course references
- Ansible for Production Sysadmins - the inventory and vault parts depend on exactly which identities can read the repository that holds them.
- Terraform for Production Sysadmins - state backend permissions and repository permissions must be reasoned about together; write on the repo plus apply in the pipeline is a single privilege path.
- Kubernetes for Production Engineers - RBAC roles and bindings are the same modelling problem one layer down.
Quiz
Knowledge check · 4 questions
Q1. A contractor with read access to an infrastructure repository finishes their engagement. Access is removed the same day. Two weeks later a credential that was committed to the repository in 2022 is used against production. What is the most likely explanation?
Q2. Granting repository access through a team rather than to an individual gives the permission a lifecycle it would not otherwise have.
Q3. Name the two most operationally significant boundaries in the repository role ladder and state what each one permits.
Q4. An access review found 41 principals with write access on a repository owned by a team of six. Design the remediation and the ongoing control.
Repository `infra-prod` has 41 principals with write access or above: 22 individual user grants, 3 teams, 9 bot accounts, 5 deploy keys, and 2 outside collaborators. Six engineers are on the owning team. Four of the bot accounts have not authenticated in over a year, two deploy keys are read-write with no recorded owner, and one outside collaborator belongs to a vendor whose contract ended last year. Nobody can say who granted most of the individual permissions.
Passing score: 75%. Answers are checked in this browser.