Protection plans
Time-bounded admission policies, scoped to discovered applications, with cluster-truth health and a violations feed.
Protection plans are the flagship feature of telark. A plan binds
one or more policy templates to a scope (applications, namespaces,
or both), a mode (audit or enforce), and an optional time
range. When the plan is active, an in-cluster controller has
deployed the corresponding admission policies to the cluster; when
the plan ends, those policies are cleaned up.
This page covers the full surface as the UI uses it.
The plan resource
A ProtectionPlan is persisted as a custom resource owned by the
exporter service. The UI form maps directly onto the same shape.
| Field | Type | Notes |
|---|---|---|
id | string | Generated server-side. |
name | string | Human label. |
description | string? | Optional. |
severity | string? | UI-only annotation. |
priority | int? | Sorting hint; the UI submits it as an int. |
scope.type | applications | namespaces | Picked in the Scope section. |
scope.applicationIds | string[] | Used when type=applications. |
scope.namespaces | string[] | Used when type=namespaces. |
policies[].templateID | string | A template ID from the catalog. |
policies[].params | Record<string, string[]> | Required when the template declares them. |
mode | audit | enforce | Per-plan. Drives Kyverno validationFailureAction. |
timeMode | permanent | time_range | Permanent plans run until cancelled. |
timeRange.startAt | RFC3339 | Required when timeMode=time_range. |
timeRange.endAt | RFC3339 | Required when timeMode=time_range. |
phase | draft | scheduled | active | terminated | canceled | failed | Controller-owned. |
reason | string? | Populated on failure or manual cancel. |
renderedPolicies | string[]? | Names of admission policies the applier deployed. |
participantsIDs | string[]? | User / group IDs responsible for the plan. |
health | unknown | healthy | drifted | degraded | Set by the health check; only meaningful when phase=active. |
healthCheckedAt | string? | ISO timestamp of the last health probe. |
healthDetail[] | PlanHealthDetail | Per-policy presence / readiness / failure action. |
createdAt / createdBy | audit | Set on prepare. |
lastUpdatedAt / lastUpdatedBy | audit | Set on every patch. |
startedAt / startedBy | audit | Set on activate. |
terminatedAt / terminatedBy | audit | Set on terminate. |
Lifecycle endpoints
Discovery owns the plan logic; the API surface looks like:
| Method | Path | Purpose |
|---|---|---|
POST | /protection-plans/prepare | Create a plan, validate, persist. |
PATCH | /protection-plans/{id} | Update scope / templates / schedule. |
POST | /protection-plans/{id}/cancel | Cancel a running or scheduled plan. |
POST | /protection-plans/{id}/reactivate | Bring a cancelled plan back into the schedule. |
POST | /protection-plans/{id}/duplicate | Clone into a new draft with regenerated IDs. |
GET | /protection-plans/{id}/status | Latest phase + health. |
GET | /protection-plans/{id}/violations | Sorted, paginated cluster-side violations. |
GET | /protection-plans/templates | The template catalog. |
DELETE | /protection-plans/{id} | Remove (controller cleans up cluster-side policies first). |
Every write endpoint requires the user header — the audit fields are populated from it.
Phases
| Phase | Set by | Cluster effect |
|---|---|---|
draft | prepare for permanent plans you haven't started, or for new schedules ahead of time. | None. |
scheduled | prepare when timeMode=time_range and startAt is in the future. | None until startAt. |
active | Controller when startAt ≤ now. | Applier deploys policies; health and violations begin. |
terminated | Controller when endAt ≤ now for time_range plans. | Applier cleans up policies; phase is terminal. |
canceled | Manual cancel. | Applier cleans up policies; phase is terminal. |
failed | Controller on activation failure. | Partial cluster state may be left; manual reactivate to retry. |
Controller tick interval defaults to 31 seconds and is
overridable via the PROTECTION_PLAN_TICK_INTERVAL_SEC environment
variable on the discovery deployment.
Mode: audit vs enforce
The per-plan mode field drives the
validationFailureAction on every deployed Kyverno policy:
audit— admission is not blocked. Violations are recorded against the KyvernoPolicyReportand surfaced in the telark violations feed. Good for staging a rule before turning it on.enforce— admission is rejected when a policy fails. Operators see a Kubernetes API error at the source of the change.
Health flags the plan as drifted if the deployed
validationFailureAction no longer matches the plan's mode.
Templates
Nine templates ship today, each declaring its id, code, the
scopes it supports, and the parameters it needs:
| Code | ID | Scope | Params |
|---|---|---|---|
bc | block-create | apps + namespaces | — |
bu | block-update | apps + namespaces | — |
bd | block-delete | apps + namespaces | — |
bit | block-image-types | apps + namespaces | imagePatterns (glob) |
bitg | block-image-tags | apps + namespaces | tags |
brs | block-replica-scaling | apps only | — |
bsc | block-storage-changes | apps + namespaces | — |
bcsr | block-config-secret-resource-changes | apps + namespaces | — |
bwcm | block-workload-config-mount-changes | apps + namespaces | — |
Templates are static today (built into the discovery service's template registry). A customer-authored template CRD is in the ideas list — not shipped.
Plan health
Health is computed by reading the cluster, not by trusting the API
write. The discovery service lists every Kyverno
kyverno.io/v1/Policy labelled with the plan ID, walks each
renderedPolicies entry the plan declared, and decides:
healthy— every declared policy present, ready, and using the failure action the plan demands.drifted— a declared policy missing, an unexpected policy wearing the plan label, orvalidationFailureActionno longer matchesmode.degraded— declared policy present but not ready (the policy engine has not reconciled it yet).unknown— plan is not yetactive.
The health result is persisted back to the ProtectionPlan CRD
via a PATCH so the dashboard has a single source of truth.
Reconciliation runs in parallel for active plans on every
controller tick.
Violations
The violations endpoint walks Kyverno PolicyReport
(wgpolicyk8s.io/v1alpha2/policyreports) across every namespace
that the plan's scope resolves to, filters to the policy names the
plan deployed, and returns:
| Field | Notes |
|---|---|
policy | The Kyverno policy that fired. |
rule | The rule within the policy. |
namespace | The namespace of the violating resource. |
resource.kind / name / namespace | The resource that triggered the rule. |
result | pass | fail | warn | error | skip. |
message | The engine's message. |
timestamp | The PolicyReport timestamp. |
Query params:
limit— default 50, max 200, sorted newest first.result— optional filter on one of the result enum values.
Where it lives in the UI
The protection-plan feature in the dashboard
(features/plans/protection) renders:
- A list page with phase quick filters and applied search.
- A detail page with overview / scope / policies / participants / health / violations sections.
- A create panel built from five
SectionCardblocks: Basic info, Scope, Policies, Schedule, Participants. - A small set of per-row actions: edit, duplicate, cancel, reactivate, delete.