Parses RadosGW operation logs and exposes Prometheus metrics. A Kubernetes mutating webhook injects it as a sidecar into RGW pods.
Can also publish CADF audit events to RabbitMQ for compliance and security monitoring.
- Rook-Ceph operator with a CephObjectStore deployed
- cert-manager installed
- RabbitMQ reachable from the cluster (only if you want the audit trail)
kubectl create namespace webhookThe webhook needs TLS. cert-manager handles certificate generation.
kubectl apply -f - <<'EOF'
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: selfsigned-issuer
namespace: webhook
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: prysm-webhook-cert
namespace: webhook
spec:
secretName: prysm-webhook-cert
dnsNames:
- prysm-webhook-service.webhook.svc
issuerRef:
name: selfsigned-issuer
kind: Issuer
EOFkubectl apply -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: prysm-webhook-service
namespace: webhook
spec:
replicas: 1
selector:
matchLabels:
app: prysm-webhook-service
template:
metadata:
labels:
app: prysm-webhook-service
spec:
containers:
- name: prysmwebhook
image: ghcr.io/cobaltcore-dev/prysm-webhook:0.0.36
ports:
- containerPort: 8443
volumeMounts:
- name: certs
mountPath: /certs
readOnly: true
env:
- name: SIDECAR_IMAGE
value: "ghcr.io/cobaltcore-dev/prysm:0.0.36"
imagePullPolicy: Always
volumes:
- name: certs
secret:
secretName: prysm-webhook-cert
EOFPin SIDECAR_IMAGE to a specific version tag. This is the prysm image that gets injected into RGW pods.
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Service
metadata:
name: prysm-webhook-service
namespace: webhook
spec:
selector:
app: prysm-webhook-service
ports:
- port: 443
targetPort: 8443
EOFkubectl apply -f - <<'EOF'
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: prysm-webhook
annotations:
cert-manager.io/inject-ca-from: "webhook/prysm-webhook-cert"
webhooks:
- name: prysm-webhook.injector.webhook
clientConfig:
service:
name: prysm-webhook-service
namespace: webhook
path: "/mutate"
admissionReviewVersions: ["v1"]
sideEffects: None
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: ["apps"]
apiVersions: ["v1"]
resources: ["deployments"]
EOFAdd prysm-sidecar: "yes" to your CephObjectStore gateway labels:
apiVersion: ceph.rook.io/v1
kind: CephObjectStore
metadata:
name: my-store
namespace: rook-ceph
spec:
gateway:
labels:
prysm-sidecar: "yes"The webhook injects the sidecar only when all five labels are present:
| Label | Value |
|---|---|
app |
rook-ceph-rgw |
app.kubernetes.io/component |
cephobjectstores.ceph.rook.io |
app.kubernetes.io/created-by |
rook-ceph-operator |
app.kubernetes.io/managed-by |
rook-ceph-operator |
prysm-sidecar |
yes |
Rook sets the first four automatically. You only add prysm-sidecar: "yes".
The injected sidecar starts with these defaults:
local-producer ops-log --log-file=/var/log/ceph/ops-log.log --max-log-file-size=10 --prometheus=true --prometheus-port=9090 -v=info
To override behavior, create a Secret or ConfigMap and reference it via annotations.
kubectl -n rook-ceph create secret generic prysm-sidecar-env \
--from-literal=AUDIT_ENABLED="true" \
--from-literal=AUDIT_RABBITMQ_URL="amqp://user:password@rabbitmq.rook-ceph.svc:5672" \
--from-literal=AUDIT_QUEUE_NAME="keystone.notifications.info"Add the annotation to your CephObjectStore:
spec:
gateway:
labels:
prysm-sidecar: "yes"
annotations:
prysm-sidecar/sidecar-env-secret: "prysm-sidecar-env"kubectl apply -f - <<'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
name: prysm-sidecar-config
namespace: rook-ceph
data:
LOG_FILE_PATH: "/var/log/ceph/ops-log.log"
MAX_LOG_FILE_SIZE: "10"
PROMETHEUS_PORT: "9090"
IGNORE_ANONYMOUS_REQUESTS: "true"
TRACK_REQUESTS_PER_TENANT: "true"
TRACK_LATENCY_PER_METHOD: "true"
TRACK_ERRORS_PER_USER: "true"
TRACK_BYTES_SENT_PER_BUCKET: "true"
TRACK_BYTES_RECEIVED_PER_BUCKET: "true"
EOFAdd the annotation:
annotations:
prysm-sidecar/sidecar-env-configmap: "prysm-sidecar-config"You can use both annotations together. The Secret loads first, then the ConfigMap.
The Secret or ConfigMap must exist before the RGW deployment is created or updated. Otherwise, pod startup fails.
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: prysm-ops-log
namespace: rook-ceph
labels:
prometheus: kube-prometheus
spec:
selector:
matchLabels:
app: rook-ceph-rgw
podMetricsEndpoints:
- port: prysm-metrics
interval: 60s
path: /metrics# Check webhook pods
kubectl -n webhook get pods
# Confirm RGW pods have the sidecar
kubectl -n rook-ceph get pods -l app=rook-ceph-rgw -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{range .spec.containers[*]}{.name}{" "}{end}{"\n"}{end}'
# Check sidecar logs
kubectl -n rook-ceph logs <rgw-pod-name> -c prysm-sidecar --tail=20
# Test metrics
kubectl -n rook-ceph port-forward <rgw-pod-name> 9090:9090 &
curl -s http://localhost:9090/metrics | grep radosgw_The ops-log producer can publish CADF (Cloud Auditing Data Federation) events to RabbitMQ. Downstream consumers like Hermes process these for audit and compliance.
Skip this if you already have RabbitMQ running.
# Deploy RabbitMQ using your organization's standard method
# (operator, Helm chart, standalone -- whatever you use)Make sure the target queue exists. Default name: keystone.notifications.info.
Create or update the Secret with RabbitMQ credentials:
kubectl -n rook-ceph create secret generic prysm-sidecar-env \
--from-literal=AUDIT_ENABLED="true" \
--from-literal=AUDIT_RABBITMQ_URL="amqp://user:password@rabbitmq.rook-ceph.svc:5672" \
--from-literal=AUDIT_QUEUE_NAME="keystone.notifications.info" \
--from-literal=AUDIT_QUEUE_SIZE="20" \
--from-literal=AUDIT_DEBUG="false" \
--dry-run=client -o yaml | kubectl apply -f -kubectl -n rook-ceph rollout restart deployment -l app=rook-ceph-rgw# Check sidecar logs for audit activity
kubectl -n rook-ceph logs <rgw-pod-name> -c prysm-sidecar --tail=30 | grep -i audit
# Check audit metrics
curl -s http://localhost:9090/metrics | grep audittools
# audittools_successful_submissions - successful publishes
# audittools_failed_submissions - failed publishes (retried)- Non-blocking. Audit publishing never stalls log processing.
- Buffered. 20-event internal queue (configurable via
AUDIT_QUEUE_SIZE). - Auto-retry. Failed events retry every minute.
- Graceful startup. If RabbitMQ is unreachable at boot, the producer starts with a no-op auditor and logs a warning.
- Connection recovery. If RabbitMQ drops mid-operation, events buffer in memory and flush when it comes back.
Watch out: retries are unbounded and buffered events are not capped. During long RabbitMQ outages, memory grows with the backlog. Monitor audittools_failed_submissions and RabbitMQ availability.
Each S3 operation produces a CADF event with:
| Field | Content |
|---|---|
| Initiator | Keystone user, project, domain, roles, application credentials |
| Target | Object, bucket, or account (depends on the operation) |
| Action | Mapped from the RadosGW operation (see table below) |
| Outcome | success or failure based on HTTP status |
| Observer | prysm-ops-log service ID |
| RadosGW operation | CADF action |
|---|---|
list_buckets, list_bucket |
read/list |
get_obj, get_bucket_info, head_obj, head_bucket |
read |
put_obj, create_bucket |
create |
delete_obj, delete_bucket |
delete |
copy_obj |
update/copy |
post_obj |
update |
| Variable | Description | Default |
|---|---|---|
LOG_FILE_PATH |
RGW ops-log file path | |
SOCKET_PATH |
Unix socket for live ops logs | |
MAX_LOG_FILE_SIZE |
Max log file size (MB) before rotation | |
LOG_RETENTION_DAYS |
Days to keep rotated logs | |
TRUNCATE_LOG_ON_START |
Rotate log at startup | false |
PROMETHEUS_ENABLED |
Enable metrics endpoint | false |
PROMETHEUS_PORT |
HTTP port for metrics | 8080 |
PROMETHEUS_INTERVAL |
Metrics update interval (seconds) | |
IGNORE_ANONYMOUS_REQUESTS |
Skip anonymous requests in metrics | false |
LOG_TO_STDOUT |
Print parsed entries to stdout | false |
| Variable | Description | Default |
|---|---|---|
AUDIT_ENABLED |
Publish to RabbitMQ | false |
AUDIT_RABBITMQ_URL |
Connection URL (amqp://host:port) |
|
AUDIT_RABBITMQ_USERNAME |
Username; overrides URL userinfo (e.g. from Vault) | |
AUDIT_RABBITMQ_PASSWORD |
Password; overrides URL userinfo | |
AUDIT_QUEUE_NAME |
Queue name (dataplane.audit → durable queue) |
keystone.notifications.info |
AUDIT_QUEUE_SIZE |
Internal event buffer size | 20 |
AUDIT_DEBUG |
Log published events | false |
AUDIT_REQUIRE_TENANT |
Drop events lacking project_id/domain_id (counted in prysm_audit_events_dropped_total) |
true |
AUDIT_OBSERVER_NAME |
CADF observer name (storage service) | radosgw |
AUDIT_REGION |
Static region stamped on events (empty = off) | |
AUDIT_INCLUDE_READS |
Audit reads (get/head/list) too; false = mutations-only | true |
AUDIT_SKIP_BUCKETS |
Buckets excluded from audit (comma-list, loop prevention) | hermes |
Set TRACK_EVERYTHING=true to turn on all metrics, or pick what you need:
| Variable | What it tracks |
|---|---|
TRACK_EVERYTHING |
All of the below (shortcut) |
TRACK_REQUESTS_DETAILED |
Requests with full labels |
TRACK_REQUESTS_PER_USER |
Requests per user |
TRACK_REQUESTS_PER_BUCKET |
Requests per bucket |
TRACK_REQUESTS_PER_TENANT |
Requests per tenant |
TRACK_LATENCY_DETAILED |
Latency histograms with full labels |
TRACK_LATENCY_PER_METHOD |
Latency per HTTP method |
TRACK_LATENCY_PER_BUCKET |
Latency per bucket |
TRACK_ERRORS_PER_USER |
Errors per user |
TRACK_ERRORS_BY_CATEGORY |
Errors by category (timeout, connection, client, server) |
TRACK_TIMEOUT_ERRORS |
Timeout errors (408, 504, 598, 499) |
TRACK_BYTES_SENT_PER_BUCKET |
Bytes sent per bucket |
TRACK_BYTES_RECEIVED_PER_BUCKET |
Bytes received per bucket |
Full list of 60+ tracking variables: environment variable reference.
Minimal production:
TRACK_LATENCY_PER_METHOD=true
TRACK_REQUESTS_PER_TENANT=true
TRACK_ERRORS_PER_USER=true
Full production:
TRACK_LATENCY_DETAILED=true
TRACK_LATENCY_PER_METHOD=true
TRACK_REQUESTS_PER_USER=true
TRACK_REQUESTS_PER_BUCKET=true
TRACK_ERRORS_PER_USER=true
TRACK_BYTES_SENT_PER_BUCKET=true
Be careful with TRACK_EVERYTHING. It creates many time series, which hits Prometheus storage and query performance.
Full table: metrics reference. Highlights:
| Metric | Type | Description |
|---|---|---|
radosgw_total_requests |
Counter | Total requests (full labels) |
radosgw_total_requests_per_tenant |
Counter | Requests per tenant |
radosgw_bytes_sent |
Counter | Bytes sent |
radosgw_bytes_received |
Counter | Bytes received |
radosgw_errors_detailed |
Counter | Errors with full labels |
radosgw_timeout_errors |
Counter | Timeout errors (useful for OSD detection) |
radosgw_errors_by_category |
Counter | Errors by category |
radosgw_requests_duration |
Histogram | Request latency distribution |
audittools_successful_submissions |
Counter | Successful audit publishes |
audittools_failed_submissions |
Counter | Failed audit publishes |