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).
| Kind | Scope | Cardinality | Owning operator |
|---|---|---|---|
EventResponse | Cluster | per action | responder operators |
SeccompTrainingRun | Namespaced | per training window | seccomp-gen |
SeccompTrainingProfile | Namespaced | per training run | seccomp-gen |
TenantBoundary | Cluster | per tenant | tenant-escape |
GitOpsResponderConfig | Namespaced | singleton per namespace | gitops-responder |
EventResponse
Section titled “EventResponse”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 onresponder.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) - default3.retryPolicy.backoffStrategy(enum) -Exponential(default) orLinear.
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/v1alpha1kind: EventResponsemetadata: name: pod-freeze-1f4abc labels: ugallu.io/security-event-uid: "1f4abc..." ugallu.io/incident-uid: "1f4abc..." ugallu.io/step: podfreezespec: 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: Exponentialstatus: phase: Succeeded attempts: 1 outcome: type: PodFreezeAppliedSeccompTrainingRun
Section titled “SeccompTrainingRun”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) - default30m, bounded at24h.replicaRatio(int 1-100) - fraction of matching pods to attach to (default50). 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/v1alpha1kind: SeccompTrainingRunmetadata: { name: payments-api-canary, namespace: ugallu-system }spec: targetSelector: matchLabels: { app: api, tier: canary } targetNamespace: payments duration: 15m replicaRatio: 50 defaultAction: SCMP_ACT_ERRNOSeccompTrainingProfile
Section titled “SeccompTrainingProfile”Namespace-scoped, one CR per training run.
Spec
profileJSON(bytes) - the complete OCI-runtimeseccomp.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/v1alpha1kind: SeccompTrainingProfilemetadata: { 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"TenantBoundary
Section titled “TenantBoundary”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 firingCrossTenantSecretAccess.trustedNamespaces[](string) - other namespaces allowed as ingress sources without triggeringCrossTenantNetworkPolicy.
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/v1alpha1kind: TenantBoundarymetadata: { 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-secretsstatus: matchedNamespaces: [payments, payments-staging] matchedPods: 42GitOpsResponderConfig
Section titled “GitOpsResponderConfig”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 bymatchLabelsormatchNamespacePattern.
Status - none.
Example
apiVersion: security.ugallu.io/v1alpha1kind: GitOpsResponderConfigmetadata: { 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 }