The module lifecycle stage: Experimental
The module has requirements for installation
ClusterSecurityEventConfig with Loki (explicit allowlist)
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventConfig
metadata:
name: default
spec:
defaultSeverityThreshold: High
enabledSources:
- clusterSecurityEventShipper/kube-audit/kube-apiserver
destinations:
- cluster-lokiClusterSecurityEventConfig with glob masks
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventConfig
metadata:
name: default
spec:
defaultSeverityThreshold: High
enabledSourcesMasks:
- clusterSecurityEventShipper/kube-audit/*
- podSecurityEventShipper/*
destinations:
- cluster-lokiClusterSecurityEventDestination (Loki)
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventDestination
metadata:
name: cluster-loki
spec:
type: Loki
loki:
endpoint: https://loki.d8-monitoring:3100
auth:
strategy: Bearer
token: EXAMPLE
tls:
verifyCertificate: false
verifyHostname: falseCredentials (
token,password) can also be referenced from a Secret usingtokenSecretRef/passwordSecretRefinstead of inline values. The Secret must be in thed8-security-events-managernamespace with the keyvaluein itsdatafield and labeledsecurity-events-manager.deckhouse.io/credential-secret: "true". Inline credential fields and their*SecretRefcounterparts are mutually exclusive.
SecurityEventDefinition
apiVersion: security.deckhouse.io/v1alpha1
kind: SecurityEventDefinition
metadata:
name: k8s-privilege-escalation
spec:
code: K8S_PRIV_ESC
category: Rbac
severity: High
description: "Attempt to create privileged pod or escalate permissions"
source: kube-apiserver
fields:
- name: metadata.extra.privileges
required: truePodSecurityEventShipper (KubernetesPods with inline parser)
apiVersion: security.deckhouse.io/v1alpha1
kind: PodSecurityEventShipper
metadata:
name: my-audit
namespace: my-namespace
spec:
- source: my-audit-app
input:
type: KubernetesPods
kubernetesPods:
labelSelector:
matchLabels:
app: audit
parser:
- name: app
parser:
type: Regex
regex:
patterns:
- '^(?P<level>\w+)\s+(?P<msg>.+)$'
fields:
- name: level
type: String
produces:
- eventCode: K8S_PRIV_ESC
extract:
field: message
operator: Regex
values:
- '.*'
transform:
- key: event.severity
value: levelClusterSecurityEventShipper (File with parserRef)
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventShipper
metadata:
name: kube-audit
spec:
- source: kube-apiserver
input:
type: File
files:
- /var/log/kube-apiserver/audit.log
parserRef: audit-json
produces:
- eventCode: K8S_AUDIT_FAILWith a ClusterSecurityEventLoggingTransformationRules:
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventLoggingTransformationRules
metadata:
name: audit-json
spec:
type: File
file:
paths:
- /var/log/kube-apiserver/audit.log
transform:
parser:
type: JSON
fields:
- name: stage
type: String
- name: responseStatus
type: Int
drop_raw: trueMultiple destinations (Loki + Splunk)
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventDestination
metadata:
name: cluster-loki
spec:
type: Loki
loki:
endpoint: https://loki.example:3100
---
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventDestination
metadata:
name: splunk-prod
spec:
type: SplunkHEC
splunkHEC:
endpoint: https://splunk.example:8088
token: YOUR_TOKEN
---
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventConfig
metadata:
name: default
spec:
defaultSeverityThreshold: Medium
enabledSourcesMasks:
- "*"
destinations:
- cluster-loki
- splunk-prodSecurityEventLoggingTransformationRules (reusable parser)
apiVersion: security.deckhouse.io/v1alpha1
kind: SecurityEventLoggingTransformationRules
metadata:
name: falco
namespace: kube-system
spec:
selector:
matchLabels:
app: runtime-audit-engine
containers:
- name: falco
parser:
type: JSON
fields:
- name: priority
type: String
- name: output
type: String
drop_raw: trueEnrichment via Plugin (k8s-pod-info)
A shipper can resolve a Pod field absent from the raw log by using enrich.source: Plugin.
For the plugin reference (required input parameters, available output values, and the
example enrich element template), see
Enrichment plugins.
apiVersion: security.deckhouse.io/v1alpha1
kind: PodSecurityEventShipper
metadata:
name: app-audit
namespace: my-namespace
spec:
- source: my-audit-app
input:
type: KubernetesPods
kubernetesPods:
labelSelector:
matchLabels:
app: audit
parser:
- name: app
parser:
type: JSON
producesDefaults:
# k8s.pod.name / k8s.namespace.name are populated by the KubernetesPods input.
enrich:
- target: actor.id
source: Plugin
plugin: k8s-pod-info
value: serviceAccountName
args:
- key: pod_name
value: k8s.pod.name
- key: namespace
value: k8s.namespace.name
produces:
- eventCode: K8S_PRIV_ESC
extract:
field: message
operator: Regex
values:
- '.*'Container-ID mode
When the raw event carries a container runtime ID instead of pod name/namespace
(common in syscall/falco events), use the k8s-container-info plugin to resolve the
serviceAccountName, name, or namespace in one lookup:
apiVersion: security.deckhouse.io/v1alpha1
kind: PodSecurityEventShipper
metadata:
name: falco-audit
namespace: kube-system
spec:
- source: falco
input:
type: KubernetesPods
kubernetesPods:
labelSelector:
matchLabels:
app: falco
parser:
- name: falco
parser:
type: JSON
producesDefaults:
# container.id is populated by the parser from the falco JSON output.
enrich:
- target: actor.id
source: Plugin
plugin: k8s-container-info
value: serviceAccountName
args:
- key: container_id
value: container.id
produces:
- eventCode: K8S_PRIV_ESC
extract:
field: message
operator: Regex
values:
- '.*'Enrichment via Plugin (k8s-nodeuser-info)
A shipper can resolve a static-user username (nodeusers.deckhouse.io metadata.name)
from the system UID carried in the event by using enrich.source: Plugin with the
k8s-nodeuser-info plugin. For the plugin reference (required input parameters,
available output values, and the example enrich element template), see
Enrichment plugins.
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventShipper
metadata:
name: falco-nodeuser-enrich
spec:
- source: runtime-audit-engine
input:
type: KubernetesPods
kubernetesPods:
labelSelector:
matchLabels:
app: falco
parser:
- name: falco
parser:
type: JSON
producesDefaults:
# output_fields.user.uid is populated by the parser from the falco JSON output.
enrich:
- target: actor.name
source: Plugin
plugin: k8s-nodeuser-info
value: username
args:
- key: uid
value: output_fields.user.uid
produces:
- eventCode: K8S_SSH_LOGIN
extract:
field: message
operator: Regex
values:
- '.*'Enrichment via custom External plugin
Users can create External
ClusterSecurityEventEnrichmentPlugin
resources to register custom enrichment endpoints. Below is an example of an
External plugin that resolves employee info by badge ID.
First, create the CSEP resource:
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventEnrichmentPlugin
metadata:
name: employee-info
spec:
type: External
description: "Resolve employee info by badge ID"
endpoint:
url: http://employee-enricher.hr-namespace.svc:8080/lookup
args:
- name: badge_id
required: true
description: "Event field path containing the employee badge ID"
returns:
fields:
- name: username
type: String
description: "Employee username"
- name: department
type: String
description: "Employee department"Then, reference it in a shipper’s enrich rule:
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventShipper
metadata:
name: badge-enrich-example
spec:
- source: runtime-audit-engine
input:
type: KubernetesPods
kubernetesPods:
labelSelector:
matchLabels:
app: falco
parser:
- name: falco
parser:
type: JSON
producesDefaults:
# output_fields.user.badge_id is populated by the parser.
enrich:
- target: actor.name
source: Plugin
plugin: employee-info
value: username
args:
- key: badge_id
value: output_fields.user.badge_id
produces:
- eventCode: K8S_BADGE_LOGIN
extract:
field: message
operator: Regex
values:
- '.*'Buffer configuration
Production setup: Disk + Block (default)
The default buffer settings are designed for production — Disk buffer with Block overflow behavior ensures zero data loss of security events during temporary destination outages.
Module config (ModuleConfig):
apiVersion: deckhouse.io/v1alpha1
kind: ModuleConfig
metadata:
name: security-events-manager
spec:
version: 1
settings:
gateway:
buffer:
type: Disk
whenFull: Block
maxSize: 512Mi
maxEvents: 500
logShipperBuffer:
type: Disk
whenFull: Block
maxSize: 257Mi # per node; must be > 256Mi, log-shipper's disk buffer minimum
maxEvents: 500Per-destination override in ClusterSecurityEventDestination, for example a larger buffer for a remote Elasticsearch:
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventDestination
metadata:
name: remote-elasticsearch
spec:
type: Elasticsearch
elasticsearch:
endpoint: https://es.example.com:9200
index: security-events
auth:
strategy: Bearer
tokenSecretRef:
name: es-token
buffer:
type: Disk
whenFull: Block
maxSize: 1Gi # larger buffer for unreliable remote destination
maxEvents: 500Test setup: Memory + DropNewest
For test/dev environments where data loss is acceptable and you want maximum speed without backpressure:
Module config:
apiVersion: deckhouse.io/v1alpha1
kind: ModuleConfig
metadata:
name: security-events-manager
spec:
version: 1
settings:
gateway:
buffer:
type: Memory
whenFull: DropNewest
maxSize: 50Mi
maxEvents: 500
logShipperBuffer:
type: Memory
whenFull: DropNewest
maxSize: 50Mi
maxEvents: 500Alerting on security events
A ClusterSecurityEventAlertRule looks for a pattern in the collected security events and raises a Prometheus alert once the pattern occurs, for example “one actor read more than 20 secrets in 5 minutes” or “a subject was bound to cluster-admin”.
The pattern is expressed as a query in LogQL,
the query language used to search Loki logs, and is evaluated against the
cluster Loki on a schedule. Therefore, the cluster-loki destination must be
enabled for any rule to work: securityEventConfig.destinations must include
cluster-loki, as described in the module configuration reference. If the
destination is missing or its type is not Loki, every alert rule is reported
Degraded in status.conditions and no rule is evaluated.
A firing rule raises the D8SecurityEventAlertFiring alert, an ordinary
Prometheus alert. It becomes a ClusterAlert object, appears in the console
alongside other cluster alerts, and is delivered to every channel the cluster
already sends alerts to. Delivery requires no configuration, and the alert
disappears on its own 5–7 minutes after the rule stops matching. To list the
created objects, run:
d8 k get clusteralertsThe pieces of a rule
Rules are placed in named groups. A group only organizes the rules and has no effect on how a rule is evaluated. Each rule in a group has the following fields:
alert— a short name for what it detects, for exampleMassSecretAccess.mode— which of the two forms the rule is written in:Exprfor a hand-written LogQL query,Matchfor the structured form. The field is required and must agree with the block that is set. It states explicitly what the presence ofexprormatchalready implies, so that a reader of the manifest and the platform web interface both know the form of a rule without inspecting which block is filled.exprormatch— which events count towards this rule, and when it fires. Every rule has exactly one of the two, named bymode:expr— the LogQL query that determines whether the rule fires. It must be a metric query, that is, one that returns a number, and must include the comparison itself, for examplecount_over_time({source_component="kube-audit"}[5m]) > 20. Nothing else defines the firing condition: reaching the threshold written into the query is what firing means. An example is given below.match, with the optionalaggregation, is the same condition built from structured fields: which event codes, sources, categories and actors are counted and, ifaggregationis set, how many of them within which window trigger the alert. The form is compiled into the same kind of LogQL query thatexprwould hold, so it is a way of building that query rather than a separate evaluation path. See the section on the simple form below.
foris the time span over whichexprmust keep matching without a break before the rule fires. It filters out one-off spikes: withfor: 5m, a spike that lasts a couple of minutes and then stops does not fire the rule. The default is0s, meaning that the rule fires on the first match. The pending and firing wait is the same as for theforfield of PrometheusRule.annotationsis the text the fired alert carries, and the field is required. Theannotations.summaryvalue must state what the rule detected. The full structure is given in the custom resource reference.severityLevelandlabelsare described in the custom resource reference.
The structure mirrors PrometheusRule: named groups, each with an interval and
a list of rules carrying alert, expr, for, labels and annotations,
with LogQL used in expr instead of PromQL.
The simplest rule matches one event code and fires as soon as the event occurs:
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventAlertRule
metadata:
name: cluster-admin-bound
spec:
groups:
- name: default
rules:
- alert: ClusterAdminBound
mode: Expr
expr: |
count_over_time({source_component="kube-audit", event_code="K8S_CLUSTER_ADMIN_BOUND"} [1m]) > 0
severityLevel: 5
annotations:
summary: "A subject was bound to cluster-admin"The query consists of the following parts:
- the
{...}selector picks the log streams to search; count_over_time(...[1m])counts the matching lines over the last minute;> 0is the condition itself.
Nothing more elaborate is required, because a cluster-admin binding is a rare event that deserves attention on every occurrence.
Both fields in the selector are stream labels, so Loki resolves them against
its index and does not read the streams they exclude. Only the fields that stay
in the event body require | json to become readable. Which fields belong to
each group, and why a filter placed on the wrong side of that boundary is
expensive, is described in the section on the specifics of writing rules
below.
Counting events per actor
Most rules answer the question of whether an event happened too many times for
one actor rather than whether it happened at all. Such rules use
sum by (...), which keeps a separate count for each distinct value of a field
instead of summing all the matching events together.
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventAlertRule
metadata:
name: mass-secret-access
spec:
groups:
- name: default
interval: 30s
rules:
- alert: MassSecretAccess
mode: Expr
# Platform actors read secrets continuously — on an idle cluster, one
# DKP ServiceAccount produces thousands of reads an hour — so a rule
# about people and workloads excludes the system actor pattern the
# platform web interface's "Exclude system" filter also uses.
expr: |
sum by (actor_id) (
count_over_time(
{source_component="kube-audit",
event_code="K8S_SECRET_ACCESSED",
actor_id!="",
actor_id!~`system:serviceaccount:kube-system:.*|system:serviceaccount:d8-.*|system:node:.*|system:kube-.*|system:apiserver|kubernetes-admin`}
[5m])
) > 20
for: 0m
severityLevel: 6
annotations:
summary: "One actor read an unusual number of secrets"
description: "Actor {{ $labels.actor_id }} read secrets {{ $value }} times in 5m. Check whether the access was expected."Compared to the first example, this rule adds the following:
- a matcher excluding known platform identities (
actor_id!~...), so that routine platform activity does not drown out the rule; sum by (actor_id), which turns a single running total into a separate count per actor, so the rule fires once the count of any one actor crosses 20 rather than once the cluster-wide total does;{{ $labels.actor_id }}and{{ $value }}indescription, which quote the specific actor and count that triggered the alert. This is the same$labelsand$valuetemplating that Prometheus uses for the annotations of PrometheusRule.
Because the query grouped by actor_id, that field also becomes a label of the
resulting alert. The section on the specifics of writing rules below explains
why the list of such fields has to stay short.
The simple form
Writing LogQL by hand is not required. The match field selects events by
structured fields, and the optional aggregation field counts them per group
and fires once the count of a group reaches threshold within window. This
is the same form that the module alert rules used before LogQL, compiled into a
LogQL expr instead of driving a separate evaluation path. Without
aggregation, a match rule fires on every matched event, the same way as the
simplest rule shown above.
The following example rewrites the cluster-admin-bound rule using match:
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventAlertRule
metadata:
name: cluster-admin-bound
spec:
groups:
- name: default
rules:
- alert: ClusterAdminBound
mode: Match
match:
eventCodes: ["K8S_CLUSTER_ADMIN_BOUND"]
severityLevel: 5
annotations:
summary: "A subject was bound to cluster-admin"The next example rewrites the mass-secret-access rule, using aggregation for
grouping and the threshold. The excludeSystemActors parameter defaults to
true, so it does not need to be specified explicitly:
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventAlertRule
metadata:
name: mass-secret-access
spec:
groups:
- name: default
interval: 30s
rules:
- alert: MassSecretAccess
mode: Match
match:
eventCodes: ["K8S_SECRET_ACCESSED"]
aggregation:
groupBy: ["ActorID"]
threshold: 20
window: "5m"
for: 0m
severityLevel: 6
annotations:
summary: "One actor read an unusual number of secrets"
description: "Actor {{ $labels.actor_id }} read secrets {{ $value }} times in 5m. Check whether the access was expected."The other fields of match — sources, categories, severityMin,
actorType, actors and excludeActors — cover the same ground as the line
filters of expr. Each of them is described in the custom resource reference.
Use expr directly when a rule requires something that match cannot express:
a field it does not cover, or a filter more specific than an exact or glob
match, such as a numeric comparison on a JSON field.
Examples of rule configurations for different events
Example of a rule that fires an alert when one actor runs exec into several pods:
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventAlertRule
metadata:
name: repeated-pod-exec
spec:
groups:
- name: default
rules:
- alert: RepeatedPodExec
mode: Expr
expr: |
sum by (actor_id) (
count_over_time({source_component="kube-audit", event_code="K8S_POD_EXEC_ATTACH", actor_id!=""} [10m])
) > 5
severityLevel: 6
annotations:
summary: "One actor opened shells in several pods"Example of a rule that fires an alert on a burst of RBAC edits in one namespace (privilege escalation), grouped per actor–namespace pair:
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventAlertRule
metadata:
name: rbac-edit-burst
spec:
groups:
- name: default
rules:
- alert: RbacEditBurst
mode: Expr
expr: |
sum by (actor_id, object_namespace) (
count_over_time({source_component="kube-audit", event_code="K8S_RBAC_RESOURCES_MODIFIED"} | json [5m])
) > 5
severityLevel: 5
annotations:
summary: "Several RBAC objects changed in a short window"
description: "Compare {{ $labels.actor_id }} and {{ $labels.object_namespace }} against what was planned."Example of a rule that fires an alert on brute force (repeated rejections from one address). Group by the client address rather than the actor, because an unauthorized request has no identity:
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventAlertRule
metadata:
name: unauthorized-burst
spec:
groups:
- name: default
rules:
- alert: UnauthorizedBurst
mode: Expr
expr: |
sum by (actor_sourceIP) (
count_over_time({source_component="kube-audit", event_code="K8S_UNAUTHORIZED_REQUEST"} | json [5m])
) > 10
severityLevel: 5
annotations:
summary: "Repeated unauthorized requests from one address"Example of a rule that fires on an event rare enough that a single occurrence deserves attention, deliberately at a severity that also blocks DKP updates:
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventAlertRule
metadata:
name: containerd-integrity
spec:
groups:
- name: default
rules:
- alert: ContainerdIntegrityViolation
mode: Expr
expr: |
count_over_time({source_component="kube-audit", event_code="D8_CONTAINERD_INTEGRITY_VIOLATION"} [1m]) > 0
severityLevel: 4 # Deliberately low: this event should also stop platform updates.
annotations:
summary: "containerd integrity check failed on a node"Example of a rule that fires an alert when RBAC changes are initiated by subjects other than a ServiceAccount:
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventAlertRule
metadata:
name: person-touched-rbac
spec:
groups:
- name: default
rules:
- alert: PersonTouchedRbac
mode: Expr
expr: |
count_over_time(
{source_component="kube-audit",
event_category="Rbac",
actor_id!~`system:serviceaccount:.*`}
[1m]
) > 0
severityLevel: 5
annotations:
summary: "RBAC changed by a non-ServiceAccount identity"A client certificate or an external identity is also not a ServiceAccount, so this is not quite “a human”. To watch a specific identity instead, match it directly:
expr: |
count_over_time(
{source_component="kube-audit",
event_code="K8S_SECRET_ACCESSED",
actor_id=~`system:serviceaccount:my-app:.*|kubernetes-admin`}
[1m]
) > 0Specifics of writing rules
When writing rules, consider the following:
- The rule applies only to events recorded by the cluster audit policy.
The default DKP policy logs
listof secrets but not agetof one secret by name, so a rule onK8S_SECRET_ACCESSEDfires on listing secrets, not on reading a single known secret. The policy also drops most requests from thesystem:authenticatedgroup, so human activity reaches the event stream only where an explicit rule records it: mutations in platform namespaces, RBAC changes,exec/attach, and similar. To confirm that the rule is not working, check the policy: an event that was never recorded cannot become an alert. - Platform actors are not excluded by default. Deckhouse’s own components
generate most of the routine activity in the event stream — on an idle
cluster, one platform ServiceAccount alone can produce thousands of secret
reads an hour — so a rule about people and workloads needs a line filter
excluding them, as the examples above do. The regex used there is the same
one the console’s “Exclude system” filter uses, so the interface and your
rules stay consistent:
system:serviceaccount:kube-system:.*|system:serviceaccount:d8-.*|system:node:.*|system:kube-.*|system:apiserver|kubernetes-admin. - Filter on stream labels inside
{...}, not after| json. The Loki destination publishes five fields as stream labels —source_component,event_code,event_severity,event_categoryandactor_id. A matcher on one of these placed inside the selector is resolved against Loki’s index, so the streams it excludes are never read at all. The same matcher written after| jsonmakes Loki fetch every line in the window and parse it as JSON first, only to discard almost all of it — on every rule, on every interval. Every other field (object_namespace,object_type,actor_sourceIP, …) lives in the event body and does need| json, which is why two of the examples above still parse: they group by a body field. Thematchform picks the right side of this line on its own. - A cardinality limit protects Prometheus from a runaway rule. Grouping by
a field with an unbounded set of values (an object name, a request path) can
turn one rule into thousands of alerts. Past 500 distinct label sets for one
rule, the rest are dropped — the rule keeps firing for the groups it already
tracks, and it is reported
CardinalityLimitExceededinstatus.conditionsso you notice. {{ $value }}in an annotation multiplies Prometheus series. The renderedsummaryanddescriptiontravel as labels of the firing metric, so each distinct rendering is a distinct series that then lives out the whole retention. A count that changes on every evaluation — the normal case forsum by (...) (count_over_time(...))— therefore produces a fresh series per interval: atinterval: 30sthat is up to 120 series an hour, per group, on top of the groups themselves. Prefer{{ $labels.<name> }}, which stays stable for as long as the group does, and keep{{ $value }}for rules whose count moves rarely.- Evaluation state lives in memory, the same as most in-cluster components.
Restarting the component that evaluates rules restarts every rule’s
forcountdown from scratch; a rule that was about to fire has to waitforagain afterwards. The consequence to watch for is aforlonger than the interval at which the component actually restarts: such a rule never reaches the end of its countdown and so never fires at all. Keepforwell below that, or leave it at0sand let the query’s own window do the smoothing. severityLevel4 or below blocks DKP updates on clusters whereupdate.blockOnAlertsis enabled in thedeckhousemodule. For a critical event this can be a deliberate decision; for a routine one it is unexpected.- All rules raise an alert with the same name —
D8SecurityEventAlertFiring. What tells one rule’s firing apart from another’s is a pair of labels:rule(the object name) andalert(the rule’s ownalert:field). Search by those, not by the alert name. This differs from PrometheusRule, where each rule’s ownalert:field becomes the Prometheus alert name directly — here that name is carried in a label instead, because every ClusterSecurityEventAlertRule rule feeds the one sharedD8SecurityEventAlertFiringalert.
HTTP destination (webhook or HTTP collector)
Send security events with a POST request. Both framing modes exist because
receivers interpret a batch of events differently: some read one JSON array per request,
while most collectors read a stream of JSON records.
Example of sending security events from a DKP cluster to an external system over HTTP:
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventDestination
metadata:
name: siem-http
spec:
type: Http
http:
endpoint: "https://collector.example.com/api/ingest"
framing: NewlineDelimited # One JSON object per line (NDJSON).
auth:
strategy: Bearer
tokenSecretRef:
name: siem-http-token
key: token
tls:
verifyCertificate: true
headers:
X-Scope-OrgID: security
---
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventConfig
metadata:
name: default
spec:
destinations:
- siem-http
enabledSourcesMasks:
- "clusterSecurityEventShipper/*"
- "podSecurityEventShipper/*"
defaultSeverityThreshold: LowDo not put an Authorization header in headers together with auth — the two
conflict and the destination is rejected on admission.
Socket destination (CEF over syslog to SIEM)
Send security events in CEF format wrapped in RFC5424 syslog over TCP/TLS to a SIEM receiver:
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventDestination
metadata:
name: siem-syslog
spec:
type: Socket
socket:
address: "siem.example.com:6514"
mode: TCP
tls:
verifyCertificate: true
encoding:
codec: CEF
syslogWrapper: RFC5424
cef:
deviceVendor: MyCompany
deviceProduct: k8s-security
---
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventConfig
metadata:
name: default
spec:
destinations:
- siem-syslog
enabledSourcesMasks:
- "clusterSecurityEventShipper/*"
- "podSecurityEventShipper/*"
defaultSeverityThreshold: LowSocket destination (raw JSON over UDP)
Send events as JSON over UDP (fire-and-forget, no TLS):
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventDestination
metadata:
name: udp-collector
spec:
type: Socket
socket:
address: "collector.example.com:514"
mode: UDPSocket destination (Unix socket for sidecar SIEM agent)
Send events to a local Unix domain socket, for example for a sidecar SIEM agent:
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventDestination
metadata:
name: sidecar-siem
spec:
type: Socket
socket:
address: "/var/run/siem.sock"
mode: UnixPer-destination override:
apiVersion: security.deckhouse.io/v1alpha1
kind: ClusterSecurityEventDestination
metadata:
name: test-loki
spec:
type: Loki
loki:
endpoint: http://loki:3100
buffer:
type: Memory
whenFull: DropNewest
maxSize: 50Mi
maxEvents: 500