Skip to content

Workload control CRDs

These five kinds describe the active side of the platform: what to do about a detection (EventResponse), how to harden a workload post-fact (SeccompTrainingRun / SeccompTrainingProfile), what counts as a tenant (TenantBoundary), and how the GitOps responder routes change requests (GitOpsResponderConfig).

KindScopeCardinalityOwning operator
EventResponseClusterper actionresponder operators
SeccompTrainingRunNamespacedper training windowseccomp-gen
SeccompTrainingProfileNamespacedper training runseccomp-gen
TenantBoundaryClusterper tenanttenant-escape
GitOpsResponderConfigNamespacedsingleton per namespacegitops-responder

Cluster-scoped, one CR per action. Owned by responder-style operators (forensics steps, gitops-responder, future containment controllers).

Spec

  • securityEventRef.name (string) - the parent SE.
  • responder.kind / responder.name (string) - controller kind and name. Two responders can coexist for the same action type by matching on responder.name.
  • action.type (string) - the typed action identifier (PodFreeze, EvidenceUpload, GitOpsChange, …).
  • action.targetRef (ObjectReference) - the K8s target the action applies to.
  • action.parameters (map) - action-specific parameters.
  • retryPolicy.maxAttempts (int) - default 3.
  • retryPolicy.backoffStrategy (enum) - Exponential (default) or Linear.

Status

  • phase (enum) - Pending, Running, Succeeded, Failed, Cancelled.
  • attempts (int).
  • lastAttemptAt, nextAttemptAt.
  • outcome.type (enum) - action-result type, e.g. EvidenceManifest, PullRequestOpened.
  • error.type (enum) - Transient, Permanent, etc., used to decide whether to retry.
  • conditions[].

The spec is immutable post-creation; updates are rejected by an admission policy. To find every EventResponse for a given SecurityEvent, list with the label ugallu.io/security-event-uid=<uid>.

Example

apiVersion: security.ugallu.io/v1alpha1
kind: EventResponse
metadata:
name: pod-freeze-1f4abc
labels:
ugallu.io/security-event-uid: "1f4abc..."
ugallu.io/incident-uid: "1f4abc..."
ugallu.io/step: podfreeze
spec:
securityEventRef: { name: ugse-honeypot-trigger-7c4d }
responder:
kind: ForensicsPipeline
name: ugallu-forensics
action:
type: PodFreeze
targetRef:
apiVersion: v1
kind: Pod
namespace: payments
name: suspect-7c4d
parameters:
cniBackend: Cilium
retryPolicy:
maxAttempts: 3
backoffStrategy: Exponential
status:
phase: Succeeded
attempts: 1
outcome:
type: PodFreezeApplied

Namespace-scoped, one CR per training window. Owned by seccomp-gen.

Spec

  • targetSelector (LabelSelector) - pods to record (empty = all in the namespace).
  • targetNamespace (string).
  • duration (Duration) - default 30m, bounded at 24h.
  • replicaRatio (int 1-100) - fraction of matching pods to attach to (default 50). Keeps a control replica untraced for safe comparison.
  • bridgeEndpoint (string) - tetragon-bridge service address override.
  • defaultAction (enum) - SCMP_ACT_ERRNO (default), SCMP_ACT_KILL, SCMP_ACT_LOG, SCMP_ACT_TRACE.

Status

  • phase (enum).
  • startTime, completionTime.
  • observedSyscallCount (int) - distinct syscalls recorded across attached pods.
  • selectedReplicas (int) - pods actually attached after the ratio sample.
  • profileRef.
  • conditions[].

A policy rejects ratios that would leave fewer than 1 untrained pod when >=2 replicas match - a guardrail against accidentally training every replica simultaneously.

Example

apiVersion: security.ugallu.io/v1alpha1
kind: SeccompTrainingRun
metadata: { name: payments-api-canary, namespace: ugallu-system }
spec:
targetSelector:
matchLabels: { app: api, tier: canary }
targetNamespace: payments
duration: 15m
replicaRatio: 50
defaultAction: SCMP_ACT_ERRNO

Namespace-scoped, one CR per training run.

Spec

  • profileJSON (bytes) - the complete OCI-runtime seccomp.json, preserved as raw bytes so the syscall array order is stable.
  • derivedFromRun (LocalProfileRef).
  • defaultAction (enum) - mirrors the run.
  • podSelector (LabelSelector) - pods that should receive this profile via the seccomp-gen ValidatingAdmissionPolicy injector.

Status

  • appliedPodCount (int) - pods the injector has touched.
  • lastAppliedAt.

Example (controller-produced)

apiVersion: security.ugallu.io/v1alpha1
kind: SeccompTrainingProfile
metadata: { name: payments-api-canary, namespace: ugallu-system }
spec:
derivedFromRun: { name: payments-api-canary }
defaultAction: SCMP_ACT_ERRNO
podSelector:
matchLabels: { app: api, tier: canary }
profileJSON: |
{
"defaultAction": "SCMP_ACT_ERRNO",
"syscalls": [
{
"names": ["read","write","openat","close","fstat",
"mmap","munmap","brk","rt_sigaction","futex",
"clone","execve","exit_group"],
"action": "SCMP_ACT_ALLOW"
}
]
}
status:
appliedPodCount: 1
lastAppliedAt: "2026-04-29T20:30:11Z"

Cluster-scoped, one CR per tenant. Owned by tenant-escape.

Spec

  • namespaceSelector (LabelSelector) - the namespaces in this tenant. Empty = match-none (defensive default; an empty selector in K8s normally matches everything, which would break the per-tenant model).
  • hostPathPolicy.allow[] (string) - host filesystem prefixes permitted for hostPath mounts (suffix match).
  • serviceAccountAllowlist[] (string) - SA usernames (system:serviceaccount:<ns>:<name>) allowed to cross this boundary without firing CrossTenantSecretAccess.
  • trustedNamespaces[] (string) - other namespaces allowed as ingress sources without triggering CrossTenantNetworkPolicy.

Status

  • matchedNamespaces[] - live namespaces matched by the selector.
  • matchedPods (int) - pod count across matched namespaces (helps diagnose mis-labelled namespaces).
  • lastReconcileAt.
  • conditions[].

Cross-CR overlap (two boundaries claiming the same namespace) is reported via status + a meta-event but never silently merged.

Example

apiVersion: security.ugallu.io/v1alpha1
kind: TenantBoundary
metadata: { name: team-payments }
spec:
namespaceSelector:
matchLabels:
tenant: payments
hostPathPolicy:
allow:
- "/var/lib/payments/"
serviceAccountAllowlist:
- "system:serviceaccount:payments:deployer"
- "system:serviceaccount:payments:ci-runner"
trustedNamespaces:
- shared-secrets
status:
matchedNamespaces: [payments, payments-staging]
matchedPods: 42

Namespace-scoped singleton. Owned by gitops-responder. Loaded once at boot; live reload is on the roadmap.

Spec

  • providers[] - {name, type, host, auth, secretRef} for each configured Git host.
  • defaultProvider (string).
  • defaultRepo (GitRepo) - {provider, owner, repo, branch}.
  • bot (BotIdentity) - {name, email, sshSigningKey}.
  • changeDefaults.draft (bool) - draft PRs / MRs by default.
  • changeDefaults.labels[] (string) - labels auto-applied to PRs / MRs.
  • conflictBehavior (enum) - abort (default), retry, override.
  • routing[] - rules selecting target repo by matchLabels or matchNamespacePattern.

Status - none.

Example

apiVersion: security.ugallu.io/v1alpha1
kind: GitOpsResponderConfig
metadata: { name: default, namespace: ugallu-system }
spec:
defaultProvider: gh-app
providers:
- name: gh-app
type: github-app
host: github.com
auth:
appCredentialsRef:
name: gitops-responder-github-app
installationID: 12345
- name: gitlab-pat
type: gitlab
host: gitlab.example.com
auth:
secretRef: { name: gitops-responder-gitlab-pat, key: token }
defaultRepo:
provider: gh-app
owner: ninsun-labs
repo: ugallu-gitops
branch: main
bot:
name: "ugallu-bot"
email: "ugallu-bot@ninsun.example"
changeDefaults:
draft: false
labels: ["security/auto", "needs-review"]
conflictBehavior: abort
routing:
- match: { matchLabels: { tenant: payments } }
repo: { provider: gh-app, owner: payments, repo: gitops, branch: main }
- match: { matchNamespacePattern: "team-*" }
repo: { provider: gh-app, owner: ninsun-labs, repo: gitops-shared }