Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0fc5d2a
Add initial audit logging pipeline:
hilmarf Apr 28, 2026
286d3a2
Ensure GHA work properly on fork or are disabled!
hilmarf Apr 28, 2026
ab977fa
refactor: migrated the separate package `api\audit` into `api\all`
hilmarf Apr 28, 2026
0cf1ed0
refactored fields to attributes
hilmarf Apr 29, 2026
2b42761
integrityValue + algorithm + certificate
hilmarf Apr 29, 2026
7228c89
Add audit signal tests, fix exporter imports, update apidiffs and bui…
hilmarf Jun 5, 2026
ff2dc41
sync with main
hilmarf Jun 8, 2026
506fe01
gradle jApiCmp
hilmarf Jun 9, 2026
d9f3f82
fix spotlessJavaCheck issues
hilmarf Jun 9, 2026
9826997
audit.* semantic conventions
hilmarf Jun 10, 2026
8df71fd
AuditRecordData extends LogRecordData
hilmarf Jun 10, 2026
d8d93b7
simplify audit API
hilmarf Jun 10, 2026
9164af9
fix: resolve AutoValue constructor mismatch in SdkAuditRecordData
hilmarf Jun 10, 2026
12c33f0
apidiffs
hilmarf Jun 10, 2026
82ce56f
remove duplicate condition
hilmarf Jun 15, 2026
2d3ea52
clean API
hilmarf Jun 24, 2026
e39fe58
use proper delegate method
hilmarf Jun 24, 2026
0636ace
why are all classes final?
hilmarf Jun 24, 2026
973e387
remove NoOp
hilmarf Jun 24, 2026
94c723d
Collection is sufficient
hilmarf Jun 24, 2026
65a6fda
fix return types
hilmarf Jun 24, 2026
1a2d68f
using fallback name: "unknown"
hilmarf Jun 24, 2026
400cd48
remove useless null checks
hilmarf Jun 24, 2026
4c6de38
spotlessapply
hilmarf Jun 24, 2026
9aa88eb
null is not allowed
hilmarf Jun 25, 2026
38b9242
depend on generic https://maven.pkg.github.com/apeirora/AuditAPI
hilmarf Jun 26, 2026
3f8a119
set env GITHUB_TOKEN
hilmarf Jun 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ jobs:
# JMH-based tests run only if this environment variable is set to true
RUN_JMH_BASED_TESTS: ${{ matrix.jmh-based-tests }}
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_ACTOR: ${{ github.actor }}

- name: Check for diff
# The jApiCmp diff compares current to latest, which isn't appropriate for release branches
Expand Down Expand Up @@ -134,6 +136,8 @@ jobs:
./gradlew nativeTest --no-configuration-cache --no-parallel
env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_ACTOR: ${{ github.actor }}

required-status-check:
# markdown-link-check is not required so pull requests are not blocked if external links break
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ jobs:
run: ./gradlew assemble --no-build-cache --no-daemon
env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_ACTOR: ${{ github.actor }}

- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@

## Before creating a git commit

Run `./gradlew jApiCmp` as the last step before committing **when Kotlin sources changed**.
Verify successfull builds with: `gradle spotlessApply build` then
run `./gradlew jApiCmp` as the last step before committing **when Kotlin sources changed**.
This updates the API diff files in `docs/apidiffs/current_vs_latest/` and must be included in
the commit. CI enforces that these files are up to date and will fail if they are stale.
YAML, Markdown, and workflow-only changes do not require running `jApiCmp`.
Expand All @@ -25,4 +26,3 @@ The signing block activates when the `CI` env var is set. Omit `CI` in a workflo
signing without removing GPG secrets.

Version is always `1.64.0-SNAPSHOT` while `snapshot = true` in `version.gradle.kts`.

1 change: 1 addition & 0 deletions all/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ dependencies {
}

testImplementation("com.tngtech.archunit:archunit-junit5")
testImplementation("eu.apeirora:audit.log")
}

// Custom task type for writing artifacts and jars - configuration cache compatible
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@ class NoSharedInternalCodeTest {
"opentelemetry-exporter-common",
"opentelemetry-exporter-logging",
"opentelemetry-exporter-logging-otlp",
"opentelemetry-exporter-otlp-audit",
"opentelemetry-exporter-prometheus",
"opentelemetry-extension-trace-propagators",
"opentelemetry-opencensus-shim",
"opentelemetry-sdk-common",
"opentelemetry-sdk-audit",
"opentelemetry-sdk-logs",
"opentelemetry-sdk-metrics",
"opentelemetry-sdk-profiles",
Expand Down
23 changes: 23 additions & 0 deletions api/all/src/main/java/io/opentelemetry/api/audit/ActorType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.api.audit;

/**
* Classifies the kind of entity that performed an auditable action.
*
* @see AuditRecordBuilder#setActor(String, ActorType)
*/
public enum ActorType {

/** A human user, identified by a user account. */
USER,

/** An automated service, daemon, or service account. */
SERVICE,

/** The operating system or a privileged system component. */
SYSTEM;
}
62 changes: 62 additions & 0 deletions api/all/src/main/java/io/opentelemetry/api/audit/Audit.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.api.audit;

/**
* Semantic convention attribute names for OpenTelemetry audit records.
*
* <p>These constants correspond to the {@code audit.*} attributes defined by the audit record data
* model.
*/
public final class Audit {

/** Unique stable identifier for an audit record. */
public static final String RECORD_ID = "audit.record.id";

/** Identity of the actor that performed the action. */
public static final String ACTOR_ID = "audit.actor.id";

/** Type of the actor that performed the action. */
public static final String ACTOR_TYPE = "audit.actor.type";

/** Verb describing what the actor did. */
public static final String ACTION = "audit.action";

/** Result of the auditable action. */
public static final String OUTCOME = "audit.outcome";

/** Identifier of the target resource acted upon. */
public static final String TARGET_ID = "audit.target.id";

/** Type of the target resource acted upon. */
public static final String TARGET_TYPE = "audit.target.type";

/** Identifier of the source of the auditable action. */
public static final String SOURCE_ID = "audit.source.id";

/** Type of the source of the auditable action. */
public static final String SOURCE_TYPE = "audit.source.type";

/** Base64-encoded cryptographic integrity proof for the record. */
public static final String INTEGRITY_VALUE = "audit.integrity.value";

/** Monotonic sequence number used for hash-chain continuity. */
public static final String SEQUENCE_NUMBER = "audit.sequence.number";

/** Hash of the previous record in the same audit stream. */
public static final String PREV_HASH = "audit.prev.hash";

/** Schema version of the audit payload. */
public static final String SCHEMA_VERSION = "audit.schema.version";

/** Resource attribute naming the integrity algorithm used for emitted audit records. */
public static final String INTEGRITY_ALGORITHM = "audit.integrity.algorithm";

/** Resource attribute referencing the certificate or key used for integrity proofs. */
public static final String INTEGRITY_CERTIFICATE = "audit.integrity.certificate";

private Audit() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.api.audit;

/**
* Hard-error thrown by {@link AuditRecordBuilder#emit()} when the audit sink cannot be reached and
* all retries are exhausted. This is an unchecked exception so that audit-logging call sites remain
* clean, but callers SHOULD catch it and escalate the failure through their incident-management
* process.
*/
public final class AuditDeliveryException extends RuntimeException {

private static final long serialVersionUID = 1L;

public AuditDeliveryException(String message) {
super(message);
}

public AuditDeliveryException(String message, Throwable cause) {
super(message, cause);
}
}
25 changes: 25 additions & 0 deletions api/all/src/main/java/io/opentelemetry/api/audit/AuditLogger.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.api.audit;

import javax.annotation.concurrent.ThreadSafe;

/**
* The entry point for emitting audit records.
*
* <p>Obtain an {@link AuditRecordBuilder} via {@link #auditRecordBuilder()}, populate all required
* fields, and call {@link AuditRecordBuilder#emit()} to deliver the record to the audit sink.
*
* <p>Unlike {@link io.opentelemetry.api.logs.Logger}, this interface does <em>not</em> expose an
* {@code isEnabled} check: audit records are ALWAYS emitted. Dropping audit records is prohibited
* by the audit logging specification.
*/
@ThreadSafe
public interface AuditLogger {

/** Returns an {@link AuditRecordBuilder} for constructing and emitting an audit record. */
AuditRecordBuilder auditRecordBuilder();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.api.audit;

/**
* Builder for creating named {@link AuditLogger} instances.
*
* <p>The {@code name} (set on the owning {@link AuditProvider}) is stored as a diagnostic label on
* the logger. Unlike {@link io.opentelemetry.api.logs.LoggerBuilder}, the name is NOT mapped to an
* OTLP {@code InstrumentationScope}.
*/
public interface AuditLoggerBuilder {

/**
* Sets the schema URL to be recorded on emitted {@link AuditRecordBuilder}s for semantic
* convention versioning.
*/
AuditLoggerBuilder setSchemaUrl(String schemaUrl);

/** Sets the version of the component or library that is emitting audit records. */
AuditLoggerBuilder setInstrumentationVersion(String instrumentationVersion);

/** Returns the configured {@link AuditLogger}. */
AuditLogger build();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.api.audit;

import javax.annotation.concurrent.ThreadSafe;

/**
* The entry point of the Audit Logging API. Provides named {@link AuditLogger} instances.
*
* <p>The provider is expected to be accessed from a central place. Use {@link
* GlobalAuditProvider#get()} to obtain the globally registered instance, or create an {@link
* AuditProvider} directly via the SDK.
*/
@ThreadSafe
public interface AuditProvider {

/**
* Gets or creates a named {@link AuditLogger} instance.
*
* @param name A string identifying the component or subsystem emitting audit records (for example
* {@code "com.example.auth"}). MUST NOT be empty.
*/
default AuditLogger get(String name) {
return auditLoggerBuilder(name).build();
}

/**
* Creates an {@link AuditLoggerBuilder} for a named {@link AuditLogger}.
*
* @param name A string identifying the component or subsystem emitting audit records. MUST NOT be
* empty.
*/
AuditLoggerBuilder auditLoggerBuilder(String name);
}
87 changes: 87 additions & 0 deletions api/all/src/main/java/io/opentelemetry/api/audit/AuditReceipt.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.api.audit;

import javax.annotation.concurrent.Immutable;

/**
* Proof-of-delivery returned by {@link AuditLogger} once the audit sink has persisted the record.
*
* <p>The {@code recordId} echoes the caller's {@link AuditRecordBuilder#setRecordId(String)}.
* {@code integrityHash} is the SHA-256 of the record as written by the sink. {@code
* sinkTimestampEpochNanos} is the nanosecond UNIX epoch at which the sink persisted the record.
*/
@Immutable
public final class AuditReceipt {

private final String recordId;
private final String integrityHash;
private final long sinkTimestampEpochNanos;

private AuditReceipt(String recordId, String integrityHash, long sinkTimestampEpochNanos) {
this.recordId = recordId;
this.integrityHash = integrityHash;
this.sinkTimestampEpochNanos = sinkTimestampEpochNanos;
}

/** Creates an {@link AuditReceipt} with the given fields. */
public static AuditReceipt create(
String recordId, String integrityHash, long sinkTimestampEpochNanos) {
return new AuditReceipt(recordId, integrityHash, sinkTimestampEpochNanos);
}

/** Returns the {@code RecordId} echoed from the corresponding {@link AuditRecordBuilder}. */
public String recordId() {
return recordId;
}

/**
* Returns the SHA-256 hex digest of the canonical serialization of the {@code AuditRecord} as
* persisted by the audit sink.
*/
public String integrityHash() {
return integrityHash;
}

/** Returns the nanosecond UNIX epoch at which the audit sink persisted the record. */
public long sinkTimestampEpochNanos() {
return sinkTimestampEpochNanos;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof AuditReceipt)) {
return false;
}
AuditReceipt other = (AuditReceipt) obj;
return recordId.equals(other.recordId)
&& integrityHash.equals(other.integrityHash)
&& sinkTimestampEpochNanos == other.sinkTimestampEpochNanos;
}

@Override
public int hashCode() {
int result = recordId.hashCode();
result = 31 * result + integrityHash.hashCode();
result = 31 * result + Long.hashCode(sinkTimestampEpochNanos);
return result;
}

@Override
public String toString() {
return "AuditReceipt{"
+ "recordId="
+ recordId
+ ", integrityHash="
+ integrityHash
+ ", sinkTimestampEpochNanos="
+ sinkTimestampEpochNanos
+ "}";
}
}
Loading
Loading