Skip to content
LinkState
Go back

Identity labels churn faster than ACL templates

Designing a CI Workflow for Identity-Based Segmentation When Labels, Tags, and Service Ownership Change Daily

Engineering design review memo – Sophia Lin, Cloud & Automation Architect


Introduction

Identity‑Based Segmentation

Identity‑based segmentation (IBS) enforces network policies using immutable or semi‑immutable workload attributes—labels, tags, or service‑ownership metadata—rather than static IP addresses. In Kubernetes or service‑mesh environments, a policy engine (e.g., Open Policy Agent, Istio AuthorizationPolicy) evaluates in‑flight requests against a rule set that references these attributes. When a workload’s label set changes, the applicable policy must be updated atomically to avoid unintended exposure or accidental denial‑of‑service.

Why CI Matters in Dynamic Environments

When labels, tags, and ownership are updated multiple times per day (feature‑branch deployments, autoscaling groups, dev self‑service namespaces), a manual policy‑update process becomes a bottleneck and a source of drift. CI provides a repeatable, automated gate that:

  1. Validates proposed label/tag changes against the current segmentation model.
  2. Generates the corresponding policy artefacts (OPA rules, Istio objects, firewall ACLs).
  3. Enforces approval workflows before artefacts are promoted to the production policy store.

Without CI, operators risk pushing stale or contradictory policies, leading to overly permissive traffic (security incident) or overly restrictive traffic (service outage).


Designing the CI Workflow

Trust Boundaries

The trust boundary is enforced at the sidecar proxy (Envoy) via Istio AuthorizationPolicy, which consumes OPA‑generated JSON policy bundles.

Assumption: the service mesh fetches policy bundles from a well‑known HTTP endpoint (e.g., http://policy-store:8080/bundle.tar.gz).

Placing the PDP inside the application container is rejected because it expands the attack surface and complicates artefact versioning.

Managing Labels, Tags, and Service Ownership

The source of truth for labels/tags/ownership lives in a GitOps repository (infra/label-store). Each file under labels/ represents a Kubernetes namespace or workload selector and contains a YAML map, e.g.:

# labels/frontend.yml
app: frontend
team: product-a
environment: prod
version: "1.4.2"

A CI pipeline watches this directory for changes and:

  1. Reads the changed files.
  2. Validates the schema (using jsonschema or a Pydantic model).
  3. Generates OPA rule files (*.rego) that encode the allow‑list for each label combination.
  4. Packages the rules into a versioned bundle (bundle.tar.gz) and pushes it to an immutable object store (versioned S3 or a dedicated policy registry).

Rejected alternatives

Approval Gates

Approval gates are separate stages in the GitLab CI pipeline:

StagePurposeAutomationHuman Involvement
lintYAML/RegO syntax checkFullNone
unit-testPytest‑based label‑schema and rule‑logic testsFullNone
security-scanOPA conftest against baseline security policiesFullNone
reviewMerge request approvals from platform‑engineer and security‑ownerManual≥2 approvers
promotePush new bundle to policy store and trigger mesh reloadFull (guarded by only:manual if desired)Optional manual trigger

The minimum safe approval gate for any change that touches the trust boundary is unit-test + review. The security-scan stage is strongly recommended but may be omitted in low‑risk environments with documented risk acceptance.


Managing Daily Changes

Automated Detection of Changes

GitLab’s trigger pipelines on push feature runs the pipeline only when files under labels/ change:

stages:
  - lint
  - unit-test
  - security-scan
  - review
  - promote

lint:
  stage: lint
  script:
    - yamllint labels/
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      changes:
        - labels/**/*

If a GitOps approach is infeasible, a lightweight polling agent can query a label API (e.g., Consul KV) every 5 minutes, commit any diff to a temporary branch, and trigger the same pipeline.

Keeping the Pipeline Static

The pipeline itself remains static; only the generated artefact (the OPA bundle) changes. To avoid rewriting the CI config on each label change:

Thus the CI configuration does not need version bumps for each label update; only the bundle changes.

Mitigating Stale‑Cache Risks

Stale cache can appear in two places:

  1. Envoy sidecar cache – Envoy caches the fetched policy bundle (TTL default 60 s).
  2. GitLab runner artefact cache – runners may cache bundle.tar.gz between jobs if not disabled.

Envoy mitigations

CI pipeline mitigations

promote:
  stage: promote
  script:
    - aws s3 cp bundle.tar.gz s3://policy-store/bundle-${CI_COMMIT_SHA}.tar.gz
    - curl -X POST http://mesh-controller/reload-policy
  when: manual
  only:
    - branches
  cache: false

If the bundle grows beyond a few megabytes, fetch latency may become noticeable—a scaling consideration for later tuning.


Troubleshooting

Stale‑Cache Errors

Symptom: Policy decisions reflect an old label set after a merge.

Diagnosis

  1. Check Envoy sidecar logs for RBAC: cached policy version X.
  2. Verify the SHA256 of bundle.tar.gz served by the policy store matches the latest commit (curl -I … returns an ETag).
  3. Look for xDS: stale resource warnings if using xDS.

Resolution

Approval Gate Failures

Symptom: Pipeline stalls at the review stage with no approvers listed.

Diagnosis

Resolution

Trust Boundary Violations

Symptom: OPA logs show decision_id: DENY for a legitimate call, or ALLOW for a call that should be blocked.

Diagnosis

Resolution


Code and CLI Examples

GitLab CI Configuration

# .gitlab-ci.yml
stages:
  - lint
  - unit-test
  - security-scan
  - review
  - promote

variables:
  POLICY_STORE_BUCKET: policy-store
  POLICY_BUNDLE_PATH: bundle.tar.gz

lint:
  stage: lint
  script:
    - yamllint labels/
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      changes:
        - labels/**/*

unit-test:
  stage: unit-test
  script:
    - ./generate_policy.sh   # produces *.rego and bundle.tar.gz
    - opa test . -v
  artifacts:
    paths:
      - bundle.tar.gz
    expire_in: 1 hour

security-scan:
  stage: security-scan
  script:
    - conftest test -p policy bundle.tar.gz
  needs: ["unit-test"]

review:
  stage: review
  script:
    - echo "Waiting for manual approvals..."
  when: manual
  allow_failure: false

promote:
  stage: promote
  script:
    - aws s3 cp bundle.tar.gz s3://$POLICY_STORE_BUCKET/bundle-${CI_COMMIT_SHA}.tar.gz
    - curl -X POST http://mesh-controller/reload-policy?bundle=s3://$POLICY_STORE_BUCKET/bundle-${CI_COMMIT_SHA}.tar.gz
  when: manual
  only:
    - branches
  cache: false

Policy Generation Script (generate_policy.sh)

#!/usr/bin/env bash
set -euo pipefail

# Read label files and produce Rego rules
mkdir -p generated
for file in labels/*.yml; do
  base=$(basename "$file" .yml)
  yq eval '. as $item | "package {{ .app }}\nallow if {\n    input.source.app == \"{{ $item.app }}\"\n    input.source.team == \"{{ $item.team }}\"\n    input.destination.app == \"{{ $item.app }}\"\n}"' "$file" > "generated/${base}.rego"
done

# Build OPA bundle
opa build -b generated -o bundle.tar.gz

Make the script executable: chmod +x generate_policy.sh.


End of document.


Share this post on:

Previous Post
ICMP Is Not Enough for Reachability CI
Next Post
Half-dead ECMP and the flows that suffer