246 lines
12 KiB
YAML
246 lines
12 KiB
YAML
apiVersion: helm.toolkit.fluxcd.io/v2
|
||
kind: HelmRelease
|
||
metadata:
|
||
name: audit-collector
|
||
namespace: security-audit
|
||
spec:
|
||
interval: 10m
|
||
timeout: 10m
|
||
chart:
|
||
spec:
|
||
chart: opentelemetry-collector-prod
|
||
version: "0.117.1"
|
||
sourceRef:
|
||
kind: HelmRepository
|
||
name: yc-oci-charts
|
||
namespace: flux-system
|
||
interval: 10m
|
||
install:
|
||
remediation:
|
||
retries: 3
|
||
upgrade:
|
||
remediation:
|
||
retries: 3
|
||
values:
|
||
mode: daemonset
|
||
fullnameOverride: audit-collector
|
||
|
||
# Дефолт чарта 0.117.1 (appVersion 0.120.0), задан явно: contrib-сборка с filelog, transform,
|
||
# kafka exporter, file_storage, health_check, memory_limiter. Зеркало в registry контура — см. README.
|
||
image:
|
||
repository: cr.yandex/crp3ccidau046kdj8g9q/opentelemetry-collector/opentelemetry-collector-contrib
|
||
tag: "0.121.0-amd64"
|
||
command:
|
||
name: otelcol-contrib
|
||
imagePullSecrets:
|
||
- name: regcred
|
||
|
||
# Только мастера: журнал аудита есть только там.
|
||
nodeSelector:
|
||
node-role.kubernetes.io/control-plane: ""
|
||
tolerations:
|
||
- key: node-role.kubernetes.io/control-plane
|
||
operator: Exists
|
||
effect: NoSchedule
|
||
rollout:
|
||
rollingUpdate:
|
||
maxUnavailable: 1
|
||
|
||
# Доступ к Kubernetes API коллектору не нужен.
|
||
clusterRole:
|
||
create: false
|
||
serviceAccount:
|
||
create: true
|
||
name: audit-collector # = роль Vault security-audit, bound SA
|
||
presets:
|
||
logsCollection: {enabled: false}
|
||
hostMetrics: {enabled: false}
|
||
kubernetesAttributes: {enabled: false}
|
||
kubeletMetrics: {enabled: false}
|
||
kubernetesEvents: {enabled: false}
|
||
clusterMetrics: {enabled: false}
|
||
|
||
# В daemonset-режиме чарт вешает hostPort на OTLP/Jaeger/Zipkin: приёмники не нужны,
|
||
# а hostPort конфликтует с другими DaemonSet-коллекторами на тех же узлах.
|
||
ports:
|
||
otlp: {enabled: false}
|
||
otlp-http: {enabled: false}
|
||
jaeger-compact: {enabled: false}
|
||
jaeger-thrift: {enabled: false}
|
||
jaeger-grpc: {enabled: false}
|
||
zipkin: {enabled: false}
|
||
metrics: {enabled: false}
|
||
service:
|
||
enabled: false
|
||
|
||
# Журнал аудита — 0600 root:root. Root нужен как владелец файла, capabilities не нужны.
|
||
podSecurityContext:
|
||
runAsUser: 0
|
||
runAsGroup: 0
|
||
seccompProfile: {type: RuntimeDefault}
|
||
securityContext:
|
||
runAsUser: 0
|
||
runAsNonRoot: false
|
||
readOnlyRootFilesystem: true
|
||
allowPrivilegeEscalation: false
|
||
privileged: false
|
||
capabilities:
|
||
drop: [ALL]
|
||
|
||
resources:
|
||
requests: {cpu: 50m, memory: 128Mi}
|
||
limits: {cpu: 500m, memory: 512Mi}
|
||
|
||
extraVolumes:
|
||
- name: audit-log
|
||
hostPath: {path: /var/log/audit, type: DirectoryOrCreate}
|
||
- name: otelcol-storage
|
||
hostPath: {path: /var/lib/otelcol-audit, type: DirectoryOrCreate}
|
||
extraVolumeMounts:
|
||
- {name: audit-log, mountPath: /var/log/audit, readOnly: true}
|
||
- {name: otelcol-storage, mountPath: /var/lib/otelcol-audit}
|
||
|
||
# Метка исключает под из webhook'а инъекции Istio ещё до мутации (namespace без istio-injection).
|
||
podLabels:
|
||
sidecar.istio.io/inject: "false"
|
||
podAnnotations:
|
||
sidecar.istio.io/inject: "false"
|
||
vault.hashicorp.com/agent-inject: "true"
|
||
vault.hashicorp.com/agent-init-first: "true"
|
||
vault.hashicorp.com/agent-pre-populate-only: "true"
|
||
vault.hashicorp.com/agent-requests-cpu: "25m"
|
||
vault.hashicorp.com/agent-limits-cpu: "100m"
|
||
vault.hashicorp.com/auth-path: auth/kubernetes
|
||
vault.hashicorp.com/role: security-audit
|
||
# Чарт рендерит podAnnotations через toYaml | tpl: однострочный шаблон toYaml переносит,
|
||
# и литерал {{ "..." }} рвётся. Поэтому по строке на действие (block scalar не переносится).
|
||
vault.hashicorp.com/agent-inject-secret-kafka-username: secrets/data/security-audit/kafka
|
||
vault.hashicorp.com/agent-inject-template-kafka-username: |-
|
||
{{ "{{- with secret \"secrets/data/security-audit/kafka\" -}}" }}
|
||
{{ "{{ .Data.data.username }}" }}
|
||
{{ "{{- end -}}" }}
|
||
vault.hashicorp.com/agent-inject-secret-kafka-password: secrets/data/security-audit/kafka
|
||
vault.hashicorp.com/agent-inject-template-kafka-password: |-
|
||
{{ "{{- with secret \"secrets/data/security-audit/kafka\" -}}" }}
|
||
{{ "{{ .Data.data.password }}" }}
|
||
{{ "{{- end -}}" }}
|
||
vault.hashicorp.com/agent-inject-secret-kafka-ca.crt: secrets/data/security-audit/kafka
|
||
vault.hashicorp.com/agent-inject-template-kafka-ca.crt: |-
|
||
{{ "{{- with secret \"secrets/data/security-audit/kafka\" -}}" }}
|
||
{{ "{{ .Data.data.ca_crt }}" }}
|
||
{{ "{{- end -}}" }}
|
||
|
||
# Контур-специфичное (CONTOUR, KAFKA_BROKERS) задаётся в оверлее через extraEnvs.
|
||
# Полный конфиг вместо мержа с дефолтами чарта.
|
||
alternateConfig:
|
||
extensions:
|
||
health_check:
|
||
endpoint: ${env:MY_POD_IP}:13133
|
||
file_storage/audit:
|
||
directory: /var/lib/otelcol-audit
|
||
compaction:
|
||
on_start: true
|
||
directory: /var/lib/otelcol-audit
|
||
|
||
receivers:
|
||
filelog/audit:
|
||
include:
|
||
- /var/log/audit/kube-apiserver-audit*.log # текущий и ротированные (lumberjack)
|
||
start_at: beginning
|
||
storage: file_storage/audit # позиции чтения переживают рестарт
|
||
max_log_size: 8MiB # дефолт 1MiB режет RequestResponse
|
||
poll_interval: 1s
|
||
include_file_name: false
|
||
operators:
|
||
- type: json_parser
|
||
parse_to: body
|
||
|
||
processors:
|
||
memory_limiter:
|
||
check_interval: 1s
|
||
limit_mib: 400
|
||
spike_limit_mib: 100
|
||
transform/audit:
|
||
error_mode: ignore
|
||
log_statements:
|
||
- context: log
|
||
statements:
|
||
# --- объём и шум ---
|
||
- delete_key(body["requestObject"]["metadata"], "managedFields") where body["requestObject"] != nil
|
||
- delete_key(body["responseObject"]["metadata"], "managedFields") where body["responseObject"] != nil
|
||
- delete_key(body["requestObject"]["metadata"]["annotations"], "kubectl.kubernetes.io/last-applied-configuration") where body["requestObject"] != nil
|
||
- delete_key(body["responseObject"]["metadata"]["annotations"], "kubectl.kubernetes.io/last-applied-configuration") where body["responseObject"] != nil
|
||
# --- маскирование (п.23): значения, а не сама команда exec ---
|
||
- replace_pattern(body["requestURI"], "(?i)(password|passwd|pwd|token|secret|api[_-]?key)(=|%3D)[^&]*", "$$1$$2***")
|
||
# --- обогащение ---
|
||
- set(body["sarex_contour"], "${env:CONTOUR}")
|
||
- set(body["sarex_dry_run"], IsMatch(body["requestURI"], "[?&]dryRun=All"))
|
||
# --- действие ---
|
||
- set(body["sarex_action"], "read") where body["verb"] == "get" or body["verb"] == "list"
|
||
- set(body["sarex_action"], "mutate") where body["verb"] == "create" or body["verb"] == "update" or body["verb"] == "patch" or body["verb"] == "delete" or body["verb"] == "deletecollection"
|
||
- set(body["sarex_action"], "status") where body["objectRef"]["subresource"] == "status"
|
||
- set(body["sarex_action"], "interactive") where IsMatch(body["objectRef"]["subresource"], "^(exec|attach|portforward)$")
|
||
# --- исход ---
|
||
- set(body["sarex_outcome"], "applied") where body["responseStatus"]["code"] >= 200 and body["responseStatus"]["code"] < 300 and body["sarex_dry_run"] == false
|
||
- set(body["sarex_outcome"], "dry_run") where body["responseStatus"]["code"] >= 200 and body["responseStatus"]["code"] < 300 and body["sarex_dry_run"] == true
|
||
- set(body["sarex_outcome"], "denied_authn") where body["responseStatus"]["code"] == 401
|
||
- set(body["sarex_outcome"], "denied_authz") where body["responseStatus"]["code"] == 403 and body["annotations"]["authorization.k8s.io/decision"] == "forbid"
|
||
- set(body["sarex_outcome"], "denied_admission") where body["responseStatus"]["code"] >= 400 and body["responseStatus"]["code"] < 500 and body["annotations"]["authorization.k8s.io/decision"] == "allow" and body["responseStatus"]["code"] != 404 and body["responseStatus"]["code"] != 409 and body["responseStatus"]["code"] != 422
|
||
- set(body["sarex_outcome"], "invalid") where body["responseStatus"]["code"] == 422
|
||
- set(body["sarex_outcome"], "conflict") where body["responseStatus"]["code"] == 409
|
||
- set(body["sarex_outcome"], "not_found") where body["responseStatus"]["code"] == 404
|
||
- set(body["sarex_outcome"], "error") where body["responseStatus"]["code"] >= 500
|
||
# --- субъект (порядок важен: сначала общее, потом частное) ---
|
||
- set(body["sarex_actor_type"], "human")
|
||
- set(body["sarex_actor_type"], "serviceaccount") where IsMatch(body["user"]["username"], "^system:serviceaccount:")
|
||
- set(body["sarex_actor_type"], "controller") where IsMatch(body["user"]["username"], "^system:serviceaccount:kube-system:") or IsMatch(body["user"]["username"], "^system:(kube-|apiserver)")
|
||
- set(body["sarex_actor_type"], "node") where IsMatch(body["user"]["username"], "^system:node:")
|
||
- set(body["sarex_actor_type"], "gitops") where IsMatch(body["user"]["username"], "^system:serviceaccount:flux-system:")
|
||
batch:
|
||
send_batch_size: 500
|
||
send_batch_max_size: 1000
|
||
timeout: 2s
|
||
|
||
exporters:
|
||
kafka/audit:
|
||
brokers:
|
||
- ${env:KAFKA_BROKERS}
|
||
protocol_version: 2.8.0
|
||
topic: security.k8s-audit
|
||
encoding: raw # в сообщение — только body (JSON события), без OTLP-обёртки
|
||
auth:
|
||
sasl:
|
||
mechanism: SCRAM-SHA-512
|
||
username: ${file:/vault/secrets/kafka-username}
|
||
password: ${file:/vault/secrets/kafka-password}
|
||
tls:
|
||
insecure: false
|
||
ca_file: /vault/secrets/kafka-ca.crt
|
||
producer:
|
||
required_acks: -1
|
||
compression: zstd
|
||
max_message_bytes: 5242880
|
||
sending_queue:
|
||
enabled: true
|
||
storage: file_storage/audit # очередь на диске: Kafka недоступна → данные не теряются
|
||
queue_size: 10000
|
||
num_consumers: 2
|
||
retry_on_failure:
|
||
enabled: true
|
||
initial_interval: 5s
|
||
max_interval: 60s
|
||
max_elapsed_time: 0 # повторять бесконечно; при полной очереди — backpressure на filelog
|
||
|
||
service:
|
||
extensions: [health_check, file_storage/audit]
|
||
pipelines:
|
||
logs/audit:
|
||
receivers: [filelog/audit]
|
||
processors: [memory_limiter, transform/audit, batch]
|
||
exporters: [kafka/audit]
|
||
telemetry:
|
||
logs:
|
||
level: info
|
||
metrics:
|
||
address: ${env:MY_POD_IP}:8888
|