---
title: 'Security Context'
source: 'https://academia.sh/en/courses/orchestration/security-context'
course: 'Container Orchestration'
language: en
updated: '2026-09-13T05:08:28+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Applies an admission-control rule to the existing pod definitions and counts violations per rule'
    - 'Measures warn mode against block mode separately: stopped deployments and warning records'
    - 'Counts how many definitions an exemption and a rule subset leave exposed relative to the full policy'
---

# Security Context

How the cluster enforces its privilege and capability settings is measured: an admission-control rule is written and applied to the existing pod definitions; how many definitions violate policy, how many deployments the enforcement stops, the difference between warn mode and block mode, and how many definitions stay exposed once an exemption is granted are all counted.

The previous lesson's policy bounded traffic between pods. It did not bound what a pod can do
inside itself: a process that runs privileged, shares the host machine's network, or is opened
with a wide capability set opens up another domain on the node it sits on, even with a fully
written network policy. What each of these settings does individually and what it costs on a
single machine was measured earlier — capability counts, a read-only root, resource limits. That
measurement is not repeated here.

This lesson's question is different and narrower: **how does the cluster enforce these settings.**
Even if a setting's correct value is known, having that value present in all twenty-four separate
definitions does not happen on its own. The delegated decision is this: an admission controller
decides whether a pod definition gets accepted into the cluster by looking at its content. It
needs one item, and a cheap one — the definition itself, which is already submitted to the
cluster. This lesson measures not the cost of information but **the outcome of the decision**:
what stops once enforcement is turned on.

- **OO25.** Twenty-four pod definitions are spread across three namespaces and belong to the
  fictional regional measurement network. The definitions were built with a generator whose seed
  is visible; they were not read from a real repository. No real cluster is run.
- **OO26.** Security context is reduced to six fields. What the fields mean and what they cost on
  a single machine were measured earlier; here only the count of which value appears in which
  definition is measured.
- **OO27.** Admission control looks at **what the definition declares**. What the image actually
  does at runtime is outside this control's field of view; this boundary is taken up separately at
  the end of the measurement.
- **OO28.** The deployment flow is forty attempts, generated with a separate seed. The same
  definition can appear more than once in the flow; the forty attempts touch nineteen distinct
  definitions.
- **OO29.** Exemption is granted in two forms: to an entire namespace, and to definitions marked
  "legacy." Per-definition exemption is not modeled; had it been, the exposed count would drop, but
  its direction would not change.
- **OO30.** No deployment stops in warn mode. A warning record is one line per violation, not per
  definition.

## Definitions and Rules

```js
// admission.mjs — pod definitions and admission control; not a real cluster, a model built with node
export const SEED = 20260805, NAMESPACE = ['development', 'staging', 'production'], DEFINITION_COUNT = 24;
export function generator(seed) {                        // linear congruential generator, seed visible
  let x = seed >>> 0;
  return () => { x = (Math.imul(x, 1664525) + 1013904223) >>> 0; return x / 4294967296; };
}
// Our own schema: every definition carries six security-context fields.
export function definitions() {
  const r = generator(SEED), t = [];
  for (let i = 0; i < DEFINITION_COUNT; i += 1) {
    const namespace = NAMESPACE[i % NAMESPACE.length];
    const legacy = r() < 0.45;                           // legacy definitions are written more loosely
    t.push({ name: `def-${String(i + 1).padStart(2, '0')}`, namespace, legacy,
      privileged: r() < (legacy ? 0.30 : 0.04),
      rootUser: r() < (legacy ? 0.80 : 0.35),
      readOnlyRoot: r() < (legacy ? 0.10 : 0.55),
      allowEscalation: r() < (legacy ? 0.85 : 0.40),
      hostNetwork: r() < (legacy ? 0.25 : 0.04),
      extraCapabilities: Math.floor(r() * (legacy ? 5 : 2)) });
  }
  return t;
}
export const RULE = [                                   // admission-control rules
  ['must not run privileged', (d) => !d.privileged],
  ['must not run as root', (d) => !d.rootUser],
  ['root filesystem must be read-only', (d) => d.readOnlyRoot],
  ['must not allow privilege escalation', (d) => !d.allowEscalation],
  ['must not use host network', (d) => !d.hostNetwork],
  ['must not request extra capabilities', (d) => d.extraCapabilities === 0],
];
export const violations = (d, rules = RULE) => rules.filter(([, f]) => !f(d)).map(([name]) => name);
export const compliant = (d, rules = RULE) => violations(d, rules).length === 0;
```

A rule is a predicate that looks at one field of a definition; policy is the list of these
predicates. The definitions' "legacy" flag is the model's one bias: definitions written earlier
were kept more likely to carry loose values. In a real repository, this bias is measured, not
assumed.

## Existing Definitions Checked Against the Rules

```js
// run10.mjs — existing definitions are checked against the rules: how many fail, which rule how often
import { definitions, RULE, NAMESPACE, SEED, violations, compliant } from './admission.mjs';

const t = definitions();
console.log(`seed ${SEED} | ${t.length} pod definitions, ${NAMESPACE.length} namespaces, ${RULE.length} rules`);
console.log(`definitions that violate policy: ${t.filter((d) => !compliant(d)).length}/${t.length}\n`);
console.log('rule                                  violating definitions');
for (const [name, f] of RULE) console.log(`${name.padEnd(38)} ${t.filter((d) => !f(d)).length}`);
console.log('\nviolations  definitions');
const dist = new Map();
for (const d of t) dist.set(violations(d).length, (dist.get(violations(d).length) ?? 0) + 1);
for (const n of [...dist.keys()].sort((a, b) => a - b)) console.log(`${String(n).padEnd(12)} ${dist.get(n)}`);
console.log('\nnamespace    defs   failing  total violations');
for (const a of NAMESPACE) {
  const g = t.filter((d) => d.namespace === a);
  console.log(`${a.padEnd(12)} ${String(g.length).padEnd(6)} ${String(g.filter((d) => !compliant(d)).length).padEnd(8)} ` +
    `${g.reduce((s, d) => s + violations(d).length, 0)}`);
}
```

```
seed 20260805 | 24 pod definitions, 3 namespaces, 6 rules
definitions that violate policy: 19/24

rule                                  violating definitions
must not run privileged                2
must not run as root                   14
root filesystem must be read-only      11
must not allow privilege escalation    11
must not use host network              5
must not request extra capabilities    10

violations  definitions
0            5
1            6
2            2
3            5
4            3
5            2
6            1

namespace    defs   failing  total violations
development  8      6        19
staging      8      7        13
production   8      6        21
```

Nineteen of twenty-four definitions violate policy. This number alone says nothing; the
interesting part is the second table. Of the nineteen non-compliant definitions, six carry a
single violation, and one carries all six. So "a definition that violates policy" is not one
class: at one end sit definitions a single-line fix would bring into compliance, at the other end
one that needs to be rewritten from scratch. Total violations come to fifty-three, more than twice
the number of definitions.

The rule table shows where enforcement will get harder. The rule that looks strictest — must not
run privileged — hits only two definitions; the rule that hits the most, the ban on root user,
hits fourteen. How "strict" a policy is gets measured not by how severe its rules are, but by **how
far** they sit from existing definitions. The namespace table confirms this: the count of
non-compliant definitions sits between six and seven across all three environments, but total
violations are twenty-one in production, thirteen in staging. Production's definitions are not
fewer in number — they are non-compliant more deeply.

## Warn, Block, Exemption

```js
// run11.mjs — warn mode, block mode, and exemption are counted separately
import { definitions, RULE, generator, violations, compliant } from './admission.mjs';

const FLOW_SEED = 77021, DEPLOYMENTS = 40;
const t = definitions(), r = generator(FLOW_SEED);
const flow = Array.from({ length: DEPLOYMENTS }, () => t[Math.floor(r() * t.length)]);
console.log(`deployment flow: seed ${FLOW_SEED}, ${DEPLOYMENTS} attempts, ${new Set(flow.map((d) => d.name)).size} distinct definitions`);
console.log(`non-compliant attempts in the flow: ${flow.filter((d) => !compliant(d)).length}\n`);

const SCENARIO = [
  ['warn, no exemption', RULE, () => false, false],
  ['block, no exemption', RULE, () => false, true],
  ['block, development exempt', RULE, (d) => d.namespace === 'development', true],
  ['block, legacy definitions exempt', RULE, (d) => d.legacy, true],
  ['block, two rules only', RULE.slice(0, 1).concat([RULE[4]]), () => false, true],
];
console.log('scenario                        rules  stopped     passing         exposed under   warning');
console.log('                                       deployments non-compliant   full policy     records');
for (const [name, rules, exempt, block] of SCENARIO) {
  let stopped = 0, passingNonCompliant = 0, warnings = 0;
  for (const d of flow) {
    const i = violations(d, rules);
    if (i.length === 0) continue;
    if (exempt(d)) { passingNonCompliant += 1; warnings += i.length; continue; }
    if (block) stopped += 1; else { passingNonCompliant += 1; warnings += i.length; }
  }
  const exposed = t.filter((d) => !compliant(d) && (exempt(d) || !block || compliant(d, rules))).length;
  console.log(`${name.padEnd(32)} ${String(rules.length).padEnd(6)} ${String(stopped).padEnd(11)} ` +
    `${String(passingNonCompliant).padEnd(15)} ${String(exposed).padEnd(15)} ${warnings}`);
}
console.log('\nif the six rules were enforced one at a time instead of together, deployments stopped per rule:');
for (const k of RULE) {
  const d = flow.filter((x) => violations(x, [k]).length).length;
  console.log(`  ${k[0].padEnd(37)} ${d}`);
}
```

```
deployment flow: seed 77021, 40 attempts, 19 distinct definitions
non-compliant attempts in the flow: 33

scenario                        rules  stopped     passing         exposed under   warning
                                       deployments non-compliant   full policy     records
warn, no exemption               6      0           33              19              89
block, no exemption              6      33          0               0               0
block, development exempt        6      22          11              6               33
block, legacy definitions exempt 6      17          16              11              59
block, two rules only            2      6           0               13              0

if the six rules were enforced one at a time instead of together, deployments stopped per rule:
  must not run privileged               3
  must not run as root                  24
  root filesystem must be read-only     23
  must not allow privilege escalation   17
  must not use host network             5
  must not request extra capabilities   17
```

Same policy, same definitions, same flow; the only thing that changes is the enforcement mode. In
warn mode, no deployment stopped, eighty-nine warning records were produced, and nineteen
definitions stayed exposed. In block mode, thirty-three of forty attempts stopped — eighty-two
percent. The distance between these two rows is the distance between writing a policy and
enforcing it; switch from one to the other without letting a single day pass in between, and the
cluster's deployment flow effectively stops.

The exemption rows measure two ways of shortening that distance, and the two do not do the same
thing. When the development namespace is exempted, stopped deployments drop from thirty-three to
twenty-two, and six definitions stay exposed. When legacy definitions are exempted, stopped
deployments drop as far as seventeen — the flow passes more comfortably — but exposed definitions
rise to eleven and warning records to fifty-nine. What the measure says is: **the more an exemption
relaxes things, the more it leaves exposed**, and the "legacy definitions" exemption exempts
exactly the worst definitions, because most violations already sit in them.

The last row measures a third route: enforcing not the whole policy but two of its rules. Stopped
deployments drop to six, passing non-compliant deployments to zero — nothing slips past those two
rules. But the last column says thirteen definitions stay exposed relative to the full policy.
This is a different kind of exposure from exemption: with exemption, the rule exists and is
not applied to some definitions; with a rule subset, the rule does not exist at all, so no warning
gets produced either. The warning column reading zero is the proof — none of the thirteen exposed
definitions shows up anywhere.

The block below ties the choice of which two rules to a number. The ban on root user alone would
have stopped twenty-four of the forty attempts, a read-only root twenty-three. The privilege ban
would stop three, the host-network ban five. The rules to enforce first are chosen by looking at
this column: the rules that stop the fewest deployments are the ones that can be closed with the
least resistance, and once closed they do not reopen.

## Progressive Enforcement

The numbers the block below lays out produce a program: rules can be turned on cumulatively,
least-stopping first. At every step, stopped deployments, definitions that need fixing, and
definitions still exposed are counted separately.

```js
// run12.mjs — rules enforced cumulatively, least-stopping first
import { definitions, RULE, generator, violations, compliant } from './admission.mjs';

const t = definitions(), r = generator(77021);
const flow = Array.from({ length: 40 }, () => t[Math.floor(r() * t.length)]);
const order = [...RULE].sort((a, b) =>
  flow.filter((x) => !a[1](x)).length - flow.filter((x) => !b[1](x)).length);
console.log('step  rule added                          stopped     to fix        exposed under');
console.log('                                          deployments definitions   full policy');
for (let n = 1; n <= order.length; n += 1) {
  const subset = order.slice(0, n);
  const stopped = flow.filter((d) => violations(d, subset).length).length;
  const toFix = t.filter((d) => !compliant(d, subset)).length;
  const exposed = t.filter((d) => !compliant(d) && compliant(d, subset)).length;
  console.log(`${String(n).padEnd(5)} ${order[n - 1][0].padEnd(36)} ${String(stopped).padEnd(11)} ` +
    `${String(toFix).padEnd(13)} ${exposed}`);
}
```

```
step  rule added                          stopped     to fix        exposed under
                                          deployments definitions   full policy
1     must not run privileged              3           2             17
2     must not use host network            6           6             13
3     must not allow privilege escalation  20          13            6
4     must not request extra capabilities  30          17            2
5     root filesystem must be read-only    30          17            2
6     must not run as root                 33          19            0
```

The first two steps are cheap: six deployments stop, six definitions get fixed, and thirteen definitions
stay exposed. The third step makes the jump — stopped deployments go from six to twenty — but in return,
exposed definitions drop from thirteen to six. This shape of the curve shows that it is possible to stop
somewhere: in three steps, about two-thirds of the full policy's exposure closes while stopping exactly
half the deployment flow.

The fourth and fifth rows carry the same three numbers. Nothing changes when the read-only-root
rule is added: after step four, every definition that violates that rule already violates another
rule too. The rule alone hit eleven definitions, but in the cumulative order it brings **zero new
definitions**. A rule's value is measured not by how many definitions it hits alone, but by what
it adds on top of the others; an enforcement program written without knowing the order cannot see
this difference.

## What the Control Does Not See

This entire measurement was done over values the definitions **declare**. Admission control reads
a pod definition, looks at six fields, and decides; the decision costs one read and one request's
worth of latency. The source of this cheapness is that declaration is the only thing the control
can look at.

A declaration being correct does not mean the behavior is correct too. A definition's image that
declares it wants no extra capability might still attempt an operation at startup that requires
one; the control cannot see this, because it runs before deployment, while the process does not
yet exist. The reverse holds too: a definition that declares it runs as root user might do nothing
with root at all. In the first case, the cluster assumes compliance and the process fails at
runtime; in the second, the cluster counts it non-compliant and stops it needlessly. How many of
the nineteen non-compliant definitions fall into the second class cannot be read from this model —
reading it requires runtime observation, and that is another information item.

This is the delegated decision's boundary. Admission control answers "does this definition comply
with policy" with certainty; it does not answer "is this workload safe." What closes the gap
between the two is not one more rule but a production process that keeps declarations reflecting
reality.

## Summary

- Nineteen of twenty-four definitions violate the six-rule policy, with fifty-three total
  violations; six non-compliant definitions carry one violation, one carries six, so
  non-compliance is not one class. Policy strictness also comes from distance to existing
  definitions rather than rule severity: the privilege ban hits two definitions, the root-user ban
  fourteen.
- In warn mode, zero deployments stop and eighty-nine warnings get produced; in block mode,
  thirty-three of forty attempts stop, eighty-two percent. Switching between the two modes crosses
  that entire distance.
- An exemption leaves exposed in proportion to how much it relaxes: the development-namespace
  exemption drops stopped deployments to twenty-two and leaves six definitions exposed; the
  legacy-definition exemption drops it to seventeen and leaves eleven exposed.
- Enforcing a rule subset produces a different kind of exposure than exemption: with two rules,
  six deployments stop and thirteen definitions stay exposed relative to the full policy, but the
  warning count is zero — the exposure shows up nowhere.
- When rules are turned on least-stopping first, exposure drops from nineteen to six in three
  steps; the rule added at the fifth step brings no new definitions, because its violations are
  already covered by the other rules.
- Admission control looks at what the definition declares, not what the process does; it answers
  "does it comply with policy" with certainty and never answers "is it safe."

## Next Step

This lesson's six fields had to be written separately into each of the twenty-four definitions,
and nineteen of them were written wrong. The same holds for the previous three lessons' outputs:
the deployment pattern's steps, the three probes' periods and thresholds, role definitions, and
network rules all sit in the same kind of definition files and get rewritten for every service.
Repetition itself is a measure — how many files the same value appears in, how many files changing
one value requires touching. The next lesson counts this repetition and measures what generating
definitions from a single source removes, and what it adds.
