Skip to content

Admission Controller

The QHx Admission Controller automatically applies Multi-Level Security (MLS) labels to Kubernetes resources based on the identity of the principal creating them. This ensures consistent classification tagging across the cluster without requiring manual label management.

The admission controller runs as part of qhx-manager and enforces policy through Kubernetes mutating and validating webhooks.

When a principal creates or updates a Kubernetes resource:

  1. Identity Extraction: Extract username, groups, and attributes from the authentication context
  2. Policy Evaluation: Map groups to MLS labels using configured policy
  3. Label Application: Apply classification, compartment, and releasability labels
  4. Validation: Reject operations that violate MLS policy

Labels are derived from the principal’s Kubernetes groups and cannot be arbitrarily changed by users.

QHx applies three types of labels:

Format: mls.qhx.dev/level

Represents the classification level of the resource. Exactly one classification level is assigned per resource.

Example values:

  • us:u (Unclassified)
  • us:c (Confidential)
  • us:s (Secret)
  • us:ts (Top Secret)

Format: mls.qhx.dev/compartment

Represents a compartment or special access program. Zero or one compartment is assigned per resource.

Example values:

  • quantum
  • tempest
  • dragon

Format: mls.qhx.dev/releasability

Comma-separated list of releasability namespaces indicating which foreign partners can access the data.

Example values:

  • us,uk,au (FVEY)
  • us,nato
  • “ (empty - no foreign release)

The admission controller maps Kubernetes groups to MLS labels through QHxClusterPolicy configuration.

apiVersion: qhx.dev/v1
kind: QHxClusterPolicy
metadata:
name: qhx-cluster-policy
spec:
groupMapping:
classification:
- group: "mls:classification:unclassified"
level: "us:u"
- group: "mls:classification:secret"
level: "us:s"
- group: "mls:classification:topsecret"
level: "us:ts"
compartment:
- group: "mls:compartment:quantum"
value: "quantum"
- group: "mls:compartment:tempest"
value: "tempest"
releasability:
- group: "mls:releasability:us"
value: "us"
- group: "mls:releasability:uk"
value: "uk"
- group: "mls:releasability:au"
value: "au"
- group: "mls:releasability:nato"
value: "nato"

A principal with these groups:

mls:classification:secret
mls:releasability:us
mls:releasability:uk
mls:releasability:au
mls:compartment:quantum

Creates resources with these labels:

metadata:
labels:
mls.qhx.dev/level: "us:s"
mls.qhx.dev/releasability: "us,uk,au"
mls.qhx.dev/compartment: "quantum"

Releasability labels have special handling to allow users to exercise a subset of their authorized releasabilities.

The principal’s groups define a bounding set of releasabilities. The principal can create resources with any subset of this bounding set.

Example:

Principal has groups: mls:releasability:us, mls:releasability:uk, mls:releasability:au

Valid releasability values the principal can specify:

  • us,uk,au (all authorized)
  • us,uk (subset)
  • us (single value)
  • “ (empty - no foreign release)

Invalid values:

  • us,jp (includes unauthorized jp)
  • nato (not in bounding set)

Configure default releasability in QHxClusterPolicy:

spec:
defaultReleasability: ["us", "uk"]

If a principal does not explicitly specify releasability, the default is applied in addition to the principal’s bounding set.

Example with default ["us", "uk"]:

  • Principal has: mls:releasability:au
  • Resource created with no explicit releasability
  • Result: mls.qhx.dev/releasability: "us,uk,au"

Set to empty list to default to no releasability:

spec:
defaultReleasability: []

QHxClusterPolicy controls whether labels are applied automatically or required explicitly:

spec:
autolabelling: true

The admission controller automatically adds required MLS labels to resources. Users do not specify labels manually.

spec:
autolabelling: false

Users must explicitly apply correct MLS labels to resources. The admission controller validates labels but does not add them automatically.

If labels are missing or incorrect, resource creation is rejected with an error message describing requirements.

Use explicit labeling when:

  • Operators must consciously acknowledge classification levels
  • Compliance requires explicit classification decisions
  • Additional approval workflow is required before resource creation

MLS labels cannot be changed after resource creation, except:

  • Releasability can be reduced (subset of original value)
  • Status field updates by controllers are allowed
  • Some annotation changes are permitted

Attempts to modify classification level or compartment are rejected.

To change MLS labels, delete and recreate the resource.

The admission controller manages labels for these resource types:

  • ConfigMap
  • CronJob
  • DaemonSet
  • Deployment
  • Job
  • Namespace
  • PersistentVolume
  • Pod
  • ReplicaSet
  • Secret
  • Service
  • ServiceAccount
  • StatefulSet

Other resource types are not labeled or controlled.

Seal a QHxClusterPolicy to make it immutable:

apiVersion: qhx.dev/v1
kind: QHxClusterPolicy
metadata:
name: qhx-cluster-policy
annotations:
qhx.dev/sealed: "true"
spec:
# ... policy configuration

Once sealed:

  • The QHxClusterPolicy cannot be modified via normal Kubernetes API operations
  • Mutating and validating webhook configurations cannot be deleted
  • Policy can only be updated via signed resources (see Signed Resources guide)

Sealing enables permanent policy enforcement that cannot be circumvented through the Kubernetes API.

Warning: Seal policy only after thorough testing. Unsealing requires direct access to cluster control plane nodes.

Validate policy configuration before applying:

Terminal window
# Check current policy
kubectl get qhxclusterpolicy qhx-cluster-policy -o yaml
# Test policy by creating a test resource
kubectl create configmap test-mls --dry-run=server -o yaml

Check admission controller logs:

Terminal window
kubectl logs -n qhx-system -l app=qhx-manager | grep admission

Check if admission controller webhook is active:

Terminal window
kubectl get mutatingwebhookconfigurations | grep qhx
kubectl get validatingwebhookconfigurations | grep qhx

Verify qhx-manager is running:

Terminal window
kubectl get pods -n qhx-system -l app=qhx-manager

Check admission controller logs:

Terminal window
kubectl logs -n qhx-system -l app=qhx-manager | tail -50

Common rejection reasons:

Missing Group Mapping:

Error: Principal has group "mls:classification:secret" but no mapping exists in QHxClusterPolicy

Solution: Add group mapping to QHxClusterPolicy.

Invalid Releasability:

Error: Releasability "us,jp" includes unauthorized values. Principal's bounding set: "us,uk"

Solution: Specify only authorized releasabilities.

Policy Validation Failed:

Error: Cannot modify sealed resource

Solution: Use signed resource to update sealed resources.

Verify principal’s groups:

Terminal window
kubectl auth whoami

Check group mapping in policy:

Terminal window
kubectl get qhxclusterpolicy qhx-cluster-policy -o yaml | grep -A 20 groupMapping

MLS labels are immutable by design. To change labels:

Terminal window
# Save resource definition
kubectl get deployment my-app -o yaml > my-app.yaml
# Delete resource
kubectl delete deployment my-app
# Edit labels in YAML
vim my-app.yaml
# Recreate with new labels
kubectl apply -f my-app.yaml

MLS labels applied by the admission controller are used by QHx Manager to automatically generate NetworkPolicies. See the Network Isolation guide for details.

Resources with identical MLS labels can communicate freely. Resources with different MLS labels are isolated by default.

apiVersion: qhx.dev/v1
kind: QHxClusterPolicy
metadata:
name: qhx-cluster-policy
spec:
autolabelling: true
defaultReleasability: []
groupMapping:
classification:
- group: "mls:classification:secret"
level: "us:s"
compartment:
- group: "mls:compartment:quantum"
value: "quantum"
releasability:
- group: "mls:releasability:us"
value: "us"
- group: "mls:releasability:uk"
value: "uk"

Apply policy:

Terminal window
kubectl apply -f qhx-cluster-policy.yaml

Ensure authentication provider issues appropriate groups. Example for OIDC:

apiVersion: v1
kind: Config
users:
- name: operator@example.mil
user:
auth-provider:
config:
extra-scopes: groups
# ... other config

Principal with groups mls:classification:secret, mls:compartment:quantum creates:

apiVersion: v1
kind: ConfigMap
metadata:
name: sensitive-config
data:
key: value

Result:

apiVersion: v1
kind: ConfigMap
metadata:
name: sensitive-config
labels:
mls.qhx.dev/level: "us:s"
mls.qhx.dev/compartment: "quantum"
mls.qhx.dev/releasability: ""
data:
key: value
Terminal window
kubectl get configmap sensitive-config -o yaml | grep mls.qhx.dev
  • Authentication provider must issue trustworthy groups
  • Group mappings should align with organizational security policy
  • Seal policy after deployment to prevent tampering
  • Monitor admission controller logs for policy violations
  • Restrict RBAC permissions to prevent unauthorized policy changes
  • Use signed resources for policy updates in high-security environments