Git, CI/CD & GitOpsCXVII · Compliance and AuditControls
Segregation of duties in the pipeline — the structural control
What you'll learn
- Define segregation of duties as a structural control enforced by the pipeline, not a procedural control enforced by policy
- Identify the four duty pairs that must be segregated: author/approver, author/deployer, approver/deployer, deployer/operator
- Recognise the role of OIDC identities and signed approvals in enforcing segregation at the cluster API
- Apply the segregation test to a sample pipeline and identify the gaps a procedural policy would miss
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
Segregation of duties is the structural control that prevents one identity from taking a change from idea to production without anyone else’s involvement. The control is structural because it is enforced by the pipeline itself - OIDC tokens, branch protection, signed identities - not by a wiki page. A procedural control is a wiki page; a structural control is a check the pipeline cannot bypass. This lesson defines the four duty pairs and the mechanisms that enforce them.
What segregation prevents
Segregation prevents a single identity from causing and authorising a change, or from authorising and applying it. The risk is not malice; it is that a single identity can be compromised, coerced, or mistaken, and a pipeline without segregation lets that identity take a change through:
flowchart LR
A["Author"] -->|"commit"| P["PR"]
P -->|"review"| AP["Approver"]
AP -->|"merge"| CI["CI"]
CI -->|"deploy"| D["Deployer"]
D -->|"apply"| PROD["Production"]
O["Operator"] -->|"runtime"| PROD
SED["Segregation"] -.-> A
SED -.-> AP
SED -.-> CI
SED -.-> D
SED -.-> O
Without segregation, any role can be the same identity. With segregation, the pipeline prevents the same identity from occupying two roles that must be separated.
The four duty pairs
flowchart TB
P1["1. Author != Approver"] --> M1["Branch protection:\nno self-approve"]
P2["2. Author != Deployer"] --> M2["Deploy is CI,\nnot author"]
P3["3. Approver != Deployer"] --> M3["Deployer is CI OIDC,\nnot the approver"]
P4["4. Deployer != Operator"] --> M4["RBAC: deployer writes,\noperator reads"]
- Author != approver. Branch protection.
- Author != deployer. Deploy runs as CI OIDC.
- Approver != deployer. Separate OIDC identity.
- Deployer != operator. RBAC: write vs read.
The role of OIDC and signed identities
OIDC turns segregation from a wiki page into a pipeline check. The CI runner presents an OIDC token to the cluster API; the cluster validates the token and grants a specific role with specific rights. Rights do not extend to humans holding different tokens.
- The author pushes a signed commit (GPG or SSH).
- The CI runner presents its OIDC token (the
token.actions.githubusercontent.comsubject); the cluster API grants thedeployerrole with write-on-create rights only. - The operator presents their own identity; the
cluster API grants the
readerrole with read and exec but no write rights.
The segregation is the OIDC configuration. An
auditor verifies it with kubectl auth can-i.
The segregation test
Run quarterly against a sample pipeline. Identities: human author, human approver, CI runner, human operator. Operations: write to repo, approve PR, trigger deploy, hit cluster API, exec into pod.
flowchart LR
TEST["Segregation test"] --> I1["Can author approve own PR?"]
I1 -->|"yes"| FAIL1["FAIL: branch protection misconfigured"]
I1 -->|"no"| I2["Can author trigger deploy?"]
I2 -->|"yes (manual)"| FAIL2["FAIL: deploy not CI-only"]
I2 -->|"no"| I3["Can approver trigger deploy?"]
I3 -->|"yes (manual)"| FAIL3["FAIL: approver has CI token"]
I3 -->|"no"| I4["Can deployer exec into pods?"]
I4 -->|"yes"| FAIL4["FAIL: deployer role too broad"]
I4 -->|"no"| PASS["PASS: segregation holds"]
A pipeline that fails any check has a segregation gap: either a configuration error (most common) or a design error.
Production discipline
- Segregation is structural. A wiki page is a hint; the OIDC configuration is the control.
- Long-lived credentials cannot enforce segregation. PATs, static SA tokens, shared passwords are the absence of segregation.
- The segregation test is run quarterly. A pipeline that passed last quarter can fail after a config change.
- The audit posture is queryable.
kubectl auth can-iis the test.
Cross-course references
- This course, Part CXV (OperatingModel) named the roles whose segregation this enforces.
- This course, Part LXVII (SupplyChain) covered OIDC and signed identities.
- Terraform for Production Sysadmins - Part IX covers the pattern at the Terraform layer.
Quiz
Knowledge check · 4 questions
Q1. A wiki page states 'engineers must not approve their own PRs'. An engineer authors a PR, walks to a colleague's desk, has the colleague click 'Approve' on the engineer's screen, then merges and deploys. What has happened?
Q2. Long-lived credentials can enforce segregation of duties only when humans and CI use distinct identities that the cluster API can distinguish.
Q3. Name the four duty pairs that must be segregated in a GitOps pipeline, and the mechanism that enforces each.
Q4. Diagnose the segregation posture and recommend the structural fix.
Team T's pipeline: developers push commits with their own GitHub credentials; CI runs as a GitHub Actions runner using a long-lived AWS access key stored as a repository secret; the runner applies Terraform changes with that access key; on-call operators have IAM roles that allow them to read the cluster and to exec into pods. The branch policy requires one peer reviewer. The CODEOWNERS file lists the platform team. The AWS access key has not been rotated in 14 months.
Passing score: 75%. Answers are checked in this browser.