Skip to content

[PROPOSAL]Standalone Audit Logging Plugin for OpenSearch #501

@niravpi

Description

@niravpi

What/Why

What are you proposing?

A standalone OpenSearch plugin (opensearch-audit-logging) that provides audit logging as an independent, first-class capability — decoupled from the security plugin.

Today, audit logging is tightly embedded inside the security plugin (org.opensearch.security.auditlog package). It only works when the security plugin is installed and fine-grained access control (FGAC) is enabled. This proposal extracts audit logging into a separate plugin that:

  • Works with or without the security plugin — no FGAC dependency
  • Captures data-plane operations: REST requests, transport actions, document reads/writes
  • Enriches events with user identity when the security plugin is present (via ThreadContext)
  • Provides an extensible sink architecture — built-in Log4j and OpenSearch index sinks, plus custom sinks loadable via reflection
  • Runs alongside the existing security plugin with zero breaking changes
  • Follows the same build system, project structure, and release conventions as existing OpenSearch plugins (Gradle with opensearch.opensearchplugin, GitHub Actions CI, plugin-descriptor.properties)

What users have asked for this feature?

  • Audit logging without FGAC: Users running OpenSearch for log analytics and observability often don't need FGAC but still require audit trails for compliance. The current hard dependency on the security plugin forces them to adopt the full security stack just for audit logging.

  • Separation of concerns: Audit logging is a compliance and observability feature, not strictly a security feature. Multiple users have expressed that coupling it to the security plugin adds unnecessary complexity and configuration overhead.

  • Custom SIEM integration: Users integrating OpenSearch with enterprise SIEM systems (Splunk, Datadog, custom pipelines) must modify the security plugin itself since the current sink architecture is internal and not independently extensible.

What problems are you trying to solve?

  1. When deploying OpenSearch for log analytics without FGAC, an operations team wants to track who accessed which indices and when, so they can meet their organization's compliance requirements (SOC2, HIPAA, PCI-DSS, ISO 27001, GDPR) without enabling the full security plugin.

  2. When running OpenSearch with basic authentication only, a platform engineer wants to get audit trails of all data-plane operations, so they can investigate security incidents and demonstrate compliance to auditors.

  3. When integrating OpenSearch with an enterprise SIEM, a security engineer wants to route audit events to a custom destination via a simple interface, so they can correlate OpenSearch activity with events from other systems without forking the security plugin.

  4. When operating OpenSearch at scale, a cluster administrator wants audit logging that can be upgraded and configured independently from the security plugin, so they can reduce the blast radius of plugin updates and simplify their operational runbook.

  5. When building a managed OpenSearch offering, a service provider wants a modular audit logging component with an extensible sink architecture, so they can add platform-specific log destinations without modifying the open-source plugin.

What is the developer experience going to be?

Installation

# Standard OpenSearch plugin installation
bin/opensearch-plugin install opensearch-audit-logging

Configuration (opensearch.yml)

# Master switch
plugins.audit.enabled: true

# Layer control
plugins.audit.enable_rest: true
plugins.audit.enable_transport: true

# Category filtering (same categories as security plugin for compatibility)
plugins.audit.disabled_rest_categories:
  - AUTHENTICATED
  - GRANTED_PRIVILEGES
plugins.audit.disabled_transport_categories:
  - AUTHENTICATED
  - GRANTED_PRIVILEGES

# User/request filtering
plugins.audit.ignore_users:
  - kibanaserver
plugins.audit.ignore_requests:
  - "SearchRequest"
  - "indices:data/read/*"

# Request body and index resolution
plugins.audit.log_request_body: true
plugins.audit.resolve_indices: true
plugins.audit.resolve_bulk_requests: false
plugins.audit.exclude_sensitive_headers: true

# Thread pool for async event dispatch
plugins.audit.threadpool.size: 10
plugins.audit.threadpool.max_queue_len: 100000

# Sink: Log4j (file-based logging)
plugins.audit.sink.log4j.enabled: true
plugins.audit.sink.log4j.logger_name: "opensearch.audit"
plugins.audit.sink.log4j.level: "INFO"

# Sink: Internal OpenSearch index
plugins.audit.sink.index.enabled: true
plugins.audit.sink.index.name: "'audit-'YYYY.MM.dd"

# Compliance (document-level tracking)
plugins.audit.compliance.enabled: true
plugins.audit.compliance.write_watched_indices:
  - "sensitive-*"
plugins.audit.compliance.read_watched_fields:
  sensitive-data:
    - "ssn"
    - "credit_card"
plugins.audit.compliance.write_metadata_only: false
plugins.audit.compliance.read_metadata_only: false

# Security plugin integration (when security plugin is co-installed)
plugins.audit.security_integration.enabled: true
plugins.audit.security_integration.read_user_from_threadcontext: true

REST API

GET  /_plugins/_audit/config     # Retrieve current audit configuration
PUT  /_plugins/_audit/config     # Update configuration (hot-reload, no restart)
PATCH /_plugins/_audit/config    # Partial update
GET  /_plugins/_audit/health     # Sink health status
GET  /_plugins/_audit/stats      # Event counts per category, per sink

Extensible Sink Interface

Third-party developers can implement custom sinks:

public interface AuditSink extends Closeable {
    /**
     * Store an audit event. Returns true if successfully stored.
     */
    boolean store(AuditEvent event);

    /**
     * Check if the sink is healthy and accepting events.
     */
    boolean isHealthy();
}

Custom sinks are loaded via reflection — no compile-time dependency required:

plugins.audit.sink.custom.type: "com.example.MySplunkAuditSink"
plugins.audit.sink.custom.config.endpoint: "https://splunk.example.com:8088"
plugins.audit.sink.custom.config.token: "${SPLUNK_HEC_TOKEN}"

Audit Event Model

Every audit event contains:

{
  "audit_format_version": 5,
  "audit_timestamp": "2026-04-15T00:00:00.000Z",
  "audit_category": "REST_REQUEST",
  "audit_origin": "REST",
  "audit_node_id": "node-1",
  "audit_node_name": "opensearch-node-1",
  "audit_cluster_name": "my-cluster",
  "audit_request_effective_user": "admin",
  "audit_request_effective_user_roles": ["all_access"],
  "audit_request_remote_address": "192.168.1.100",
  "audit_request_action": "indices:data/read/search",
  "audit_trace_indices": ["my-index-*"],
  "audit_trace_resolved_indices": ["my-index-2026.04.15"],
  "audit_request_body": "{\"query\":{\"match_all\":{}}}",
  "audit_request_headers": {}
}

The format is compatible with the existing security plugin audit event format (format version 4+), ensuring existing log parsers and dashboards continue to work during migration.

Tracked Event Categories

Category Layer Description
REST_REQUEST REST Any REST API call received by the cluster
TRANSPORT_ACTION Transport Internal transport actions between nodes
INDEX_EVENT Transport Index administration (create, delete, alias, force merge)
DOCUMENT_WRITE Index Document indexed, updated, or deleted (compliance)
DOCUMENT_READ Index Document read during search/get (compliance)
FAILED_LOGIN REST/Transport Authentication failure (when security plugin present)
AUTHENTICATED REST/Transport Successful authentication (when security plugin present)
MISSING_PRIVILEGES Transport Authorization failure (when security plugin present)
GRANTED_PRIVILEGES Transport Successful authorization (when security plugin present)
SSL_EXCEPTION REST/Transport TLS/SSL errors
BAD_HEADERS REST/Transport Spoofed or invalid headers

Are there any security considerations?

  • The plugin is read-only with respect to user data — it observes requests but does not authenticate, authorize, or modify them.
  • Sensitive headers (Authorization, Cookie, etc.) are excluded from audit events by default.
  • Request body logging is configurable and can be disabled for sensitive deployments.
  • The audit configuration index (.plugins-audit-config) and audit data indices (audit-*) should be protected via the security plugin's index-level permissions when FGAC is enabled.
  • The plugin reads user identity from ThreadContext using the same transient key (_opendistro_security_user) that the security plugin sets. It does not access the security plugin's internal index or configuration directly.

Are there any breaking changes to the API?

No. This is a new plugin with new REST endpoints under /_plugins/_audit/. It does not modify any existing OpenSearch APIs or any security plugin APIs. The security plugin's existing audit logging continues to work unchanged.

What is the user experience going to be?

Scenario 1: OpenSearch without security plugin

User installs audit plugin → configures sinks → all REST/transport/index
operations are logged with timestamp, source IP, action, indices.
User identity shows as "<anonymous>" since there's no authentication layer.

Scenario 2: OpenSearch with security plugin (parallel operation)

Both plugins installed → security plugin handles auth as usual →
audit plugin reads user identity from ThreadContext →
events enriched with username, roles, backend roles →
both plugins log independently (dual logging during transition)

Scenario 3: Custom SIEM integration

User implements AuditSink interface → packages as JAR →
places on classpath → configures via opensearch.yml →
audit events flow to custom destination (Splunk, Datadog, etc.)

Architecture Diagram

                        ┌─────────────────────────────────────────────┐
                        │              OpenSearch Node                 │
                        │                                             │
  REST Request ────────►│  ┌───────────────────┐  ┌────────────────┐  │
                        │  │  Security Plugin   │  │  Audit Plugin  │  │
                        │  │  (optional)        │  │  (NEW)         │  │
                        │  │                    │  │                │  │
                        │  │  • Authenticates   │  │  Interceptors: │  │
                        │  │  • Authorizes      │  │  • ActionFilter│  │
                        │  │  • Sets user in    │  │  • IndexOpList.│  │
                        │  │    ThreadContext ──────►• SecurityCtx   │  │
                        │  │                    │  │    Enricher    │  │
                        │  │  (unchanged)       │  │       │        │  │
                        │  └───────────────────┘  │       ▼        │  │
                        │                          │  SinkRouter    │  │
                        │                          │  ┌──────────┐  │  │
                        │                          │  │Log4jSink │  │  │
                        │                          │  │IndexSink │  │  │
                        │                          │  │CustomSink│  │  │
                        │                          │  └──────────┘  │  │
                        │                          └────────────────┘  │
                        └─────────────────────────────────────────────┘

How Interception Works (OpenSearch Extension Points)

REST Request
    │
    ▼
ActionFilter.apply()              ◄── Standard OpenSearch plugin API
    │                                  (same as alerting, anomaly-detection)
    ├── Captures: action, source IP, indices, request body
    ├── Reads user from ThreadContext (if security plugin present)
    ├── Builds AuditEvent
    └── Sends to SinkRouter (async, non-blocking)
    │
    ▼
Normal request processing continues (zero latency impact on request path)


Index Write/Delete
    │
    ▼
IndexingOperationListener          ◄── Standard OpenSearch plugin API
    │                                   (same as security plugin's
    │                                    ComplianceIndexingOperationListener)
    ├── Captures: index, doc ID, shard ID, operation
    ├── Builds AuditEvent (DOCUMENT_WRITE)
    └── Sends to SinkRouter (async)

Sink Architecture

                    AuditEvent
                        │
                        ▼
                   SinkRouter
                   (async thread pool)
                        │
            ┌───────────┼───────────┐
            ▼           ▼           ▼
       Log4jSink   IndexSink   CustomSink
       (built-in)  (built-in)  (via reflection)
            │           │           │
            ▼           ▼           ▼
       Log files    audit-*     Any destination
                    index       (SIEM, webhook,
                                 cloud logging, etc.)

Phased Rollout (No Breaking Changes)

Phase 1 (Initial Release):
  • Audit plugin runs alongside security plugin
  • Both log independently — acceptable for validation
  • Zero changes to security plugin required

Phase 2 (Integration):
  • Security plugin optionally forwards auth events to audit plugin
  • Security plugin's own audit sinks can be set to noop
  • Single audit pipeline

Phase 3 (Migration Complete):
  • Audit logging code removed from security plugin
  • Audit plugin is the sole audit system
  • Security plugin focuses on auth/authz only

Are there breaking changes to the User Experience?

No. This is purely additive. The existing security plugin audit logging is completely unaffected. Users can adopt the new plugin at their own pace.

Why should it be built? Any reason not to?

Why build it:

  • Modularity: Audit logging is a compliance/observability concern, not strictly a security concern. Decoupling it follows the OpenSearch project's philosophy of modular, composable plugins. This is the same pattern as opensearch-storage-encryption being decoupled from core.

  • Broader reach: Today, audit logging is gated behind FGAC. Removing this dependency makes audit logging available to the entire OpenSearch user base — including users who run OpenSearch for log analytics, observability, and search without FGAC.

  • Extensibility: The AuditSink interface enables the community to build integrations without forking the security plugin. This lowers the barrier for SIEM vendors and managed service providers to add OpenSearch audit support.

  • Reduced complexity in security plugin: The security plugin's auditlog package (~20 classes across config/, impl/, routing/, sink/) adds significant surface area. Extracting it reduces the security plugin's maintenance burden and test matrix.

  • Independent release cycle: Audit logging improvements and bug fixes can ship without waiting for security plugin releases.

Reasons not to:

  • Dual logging during transition: During Phase 1, both plugins may log the same events. This is mitigated by allowing users to disable the security plugin's audit sinks (plugins.security.audit.type: noop) when they adopt this plugin.

  • Additional repo to maintain: Offset by reduced complexity in the security plugin.

What will it take to execute?

Build System & Project Structure

The plugin will follow the same conventions as existing OpenSearch plugins (security, anomaly-detection, alerting):

opensearch-audit-logging/
├── .github/
│   └── workflows/              # CI: build, test, release (GitHub Actions)
├── build-tools/                # Shared build utilities
├── gradle/
│   └── wrapper/                # Gradle wrapper
├── release-notes/              # Per-version release notes
├── scripts/                    # Helper scripts
├── src/
│   ├── main/
│   │   ├── java/org/opensearch/audit/
│   │   │   ├── AuditLoggingPlugin.java
│   │   │   ├── interceptor/
│   │   │   ├── event/
│   │   │   ├── sink/
│   │   │   ├── config/
│   │   │   └── ...
│   │   └── resources/
│   │       └── plugin-descriptor.properties
│   ├── test/                   # Unit tests
│   └── integrationTest/        # Integration tests (separate source set)
├── build.gradle                # Uses opensearch.opensearchplugin
├── settings.gradle
├── gradle.properties
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── DEVELOPER_GUIDE.md
├── LICENSE.txt                 # Apache 2.0
├── MAINTAINERS.md
├── NOTICE.txt
└── README.md

build.gradle approach:

  • Uses opensearch.opensearchplugin Gradle plugin (same as security plugin)
  • opensearch.pluginzip for artifact packaging
  • opensearch.testclusters for integration testing
  • Spotless for code formatting, Checkstyle for style enforcement
  • JaCoCo for code coverage
  • Maven Central publishing via maven-publish plugin

Dependencies

  • Compile-time: OpenSearch core APIs only (ActionFilter, IndexingOperationListener, ThreadContext, ClusterService, Settings)
  • Runtime: Log4j2 (already provided by OpenSearch), Jackson (already provided by OpenSearch)
  • No dependency on the security plugin JAR — user identity is read from ThreadContext via reflection/string parsing, not by importing security plugin classes
  • No external SDK dependencies — keeps the plugin lightweight

Any remaining open questions?

  1. Event format version: Should the plugin use the same audit event format version as the security plugin (currently v4) for backward compatibility, or define a new format (v5) with additional fields?

  2. System index protection: Should the audit configuration index (.plugins-audit-config) and audit data indices (audit-*) be registered as system indices?

  3. Security plugin integration mechanism: For Phase 2, what is the preferred mechanism for the security plugin to forward auth-specific events (FAILED_LOGIN, MISSING_PRIVILEGES, etc.) to this plugin?

    • Option A: ThreadContext injection (proven pattern — already used by the Jetty proxy integration)
    • Option B: SPI/ExtensiblePlugin interface
    • Option C: Event bus (e.g., Guava EventBus, already used by security plugin for config changes)
  4. Compliance features scope: Should document-level compliance tracking (watched indices, field-level read tracking, write diffs) be included in the initial release or deferred to a follow-up?

  5. Dashboards UI: Should a companion Dashboards plugin for audit log configuration and visualization be part of this proposal, or a separate future proposal?

  6. Naming: Is opensearch-audit-logging the right name, or should it be opensearch-audit or opensearch-audit-log?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    New

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions