Skip to content

Add audit hooks for inter-node user data transfers - #18569

Open
HTHou wants to merge 6 commits into
apache:masterfrom
HTHou:codex/audit-user-data-transfer
Open

Add audit hooks for inter-node user data transfers#18569
HTHou wants to merge 6 commits into
apache:masterfrom
HTHou:codex/audit-user-data-transfer

Conversation

@HTHou

@HTHou HTHou commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Description

Add sender-side audit hooks for inter-node user-data transfers required by the FDP_ITT.1 audit requirements from GB/T 20273-2019.

This PR only adds audit event modeling, audit hooks, and routing through the existing audit logger. It does not introduce a new audit sink, persistence mechanism, protection mechanism, or policy implementation.

Audit record

Each transfer attempt records only:

  • timestamp
  • subject/initiator
  • source and destination
  • protection method
  • result
  • one error value when the attempt fails

The event is routed as AuditEventType.USER_DATA_TRANSFER. Payload contents are never retained.

Instrumented transfer paths

  • cross-DataNode MPP TsBlock transfer, recorded by the pull initiator
  • cross-DataNode write dispatch when the plan tree contains an InsertNode
  • remote Load TsFile piece transfer
  • IoTConsensus user-data log batches and snapshot fragment transfer
  • IoTConsensusV2 tablet and TsFile piece transfer

Review follow-ups

  • classify IoTConsensus requests and emit only for batches containing user data
  • exclude audit-origin and audit-database writes to prevent recursive audit events
  • isolate handler lookup, enablement checks, and event delivery from transfer and replication paths
  • record MPP success only after exact response validation and local buffer acceptance
  • reduce the event model to the minimum audit fields and add focused behavior tests
  • classify transfer audit records as CONTROL so concrete audit filters can retain them
  • preserve MPP retry and terminal failure behavior for short non-empty responses
  • skip IoTConsensus user-data classification entirely while auditing is disabled
  • use the documented protection_method values TLS and NONE

Explicitly out of scope

  • Ratis transfer paths
  • AINode
  • ordinary query FragmentInstance or query-plan dispatch
  • SchemaRegion and ConfigRegion consensus transfer
  • heartbeats, control commands, delete events, and file seal signals
  • local in-process calls and general Pipe transfers

This PR has:

  • been self-reviewed.
  • added Javadocs for the audit event model and non-blocking handler contract.
  • added comments explaining audit isolation and payload exclusion.
  • added focused unit tests for event fields, user-data classification, recursion prevention, handler isolation, and MPP response acceptance.
  • added integration tests.
  • been tested in a test IoTDB cluster.

Verification

  • mvn spotless:apply -pl iotdb-core/node-commons,iotdb-core/consensus,iotdb-core/datanode
  • targeted unit tests for the changed audit and transfer paths
  • mvn test-compile -DskipTests
  • mvn test-compile -DskipTests -P with-zh-locale
  • git diff --check

All commands above pass locally.

@HTHou HTHou left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Main findings are inline. Please also add behavior-level tests that capture emitted events for success, remote-status failure, exception/retry, and source/destination direction, plus tests proving that a throwing audit handler never changes the transfer outcome and that audit-log writes do not recursively generate USER_DATA_TRANSFER events. The current tests only cover the event value object and the PlanNode classifier.

@HTHou HTHou left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second-pass findings after 43a7104 are inline. The earlier recursion, batch-classification, handler-isolation, and event-field issues are otherwise addressed.

@HTHou

HTHou commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the latest audit review feedback in 85ff1e2:

  • MPP PlanNode classification now short-circuits on DataNodeUserDataTransferAuditor.isEnabled() before inspecting the plan tree; the regression test verifies getChildren() is never called while audit logging is disabled.
  • Added consensus-group-level user-data classification for snapshot transfers. DataRegion groups resolve their database once per snapshot, and __audit / root.__audit snapshots are excluded before any fragment event is emitted.

Validation passed: focused consensus/DataNode tests (10 tests), full English reactor test-compile, and full Chinese-locale reactor test-compile.

…ta-transfer

# Conflicts:
#	iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/CommonMessages.java
#	iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/CommonMessages.java
@HTHou HTHou changed the title [Feature] Add audit hooks for inter-node user data transfers Add audit hooks for inter-node user data transfers Sep 2, 2026
@HTHou
HTHou marked this pull request as ready for review September 2, 2026 10:05
Comment on lines +228 to +230
} catch (RuntimeException ignored) {
// Audit recording must not affect consensus replication.
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leave a warn log for debugging?

@HTHou HTHou Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 9e6cbee. Audit callback/classification RuntimeExceptions remain isolated from consensus replication, but now emit a WARN with the stack trace for diagnosis. The new operator-facing message is localized in both en/zh.

Comment on lines +90 to +97
public static boolean containsUserData(ConsensusGroupId consensusGroupId) {
if (!(consensusGroupId instanceof DataRegionId)) {
return false;
}
final DataRegion dataRegion =
StorageEngine.getInstance().getDataRegion((DataRegionId) consensusGroupId);
return dataRegion != null && containsUserData(dataRegion.getDatabaseName());
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it reasonable to exclude schema regions?
Attributes also seem to be part of the user data.

@HTHou HTHou Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that schema and attribute data are user data. In IoTDB, SchemaRegion supports Ratis (and SimpleConsensus when the replication factor is 1); IoTConsensus and IoTConsensusV2 are rejected for SchemaRegion, and this PR wires the IoTConsensus classifier only into DataRegionConsensusImpl. The Ratis-side transfer audit hook needs to be implemented inside Ratis and will be handled under RATIS-2681, so this PR intentionally does not add a non-functional SchemaRegion classifier branch.

Comment on lines +111 to +114
} else if (request instanceof IoTConsensusRequest) {
planNode = WALEntry.deserializeForConsensus(request.serializeToByteBuffer().duplicate());
} else if (request instanceof ByteBufferConsensusRequest) {
planNode = PlanNodeType.deserialize(request.serializeToByteBuffer().duplicate());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the cost be too high for these branches?
Can we somehow store and encode the flag in IConsensusRequest?

@HTHou HTHou Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 9e6cbee. The live sender path classifies a PlanNode once and stores the bit on IndexedConsensusRequest. During WAL reconstruction, the bit is now restored directly from WALEntryType, then combined with the consensus-group classifier to preserve the audit-database exclusion. The remote receiver no longer classifies the serialized request because auditing is sender-side. This removes the IoTConsensusRequest/ByteBufferConsensusRequest serialization and deserialization branches, without adding a wire-format field. A test also verifies that a generic IConsensusRequest is not serialized solely for audit classification.

Comment on lines +88 to +97
final TSStatus failedStatus =
status.stream()
.filter(tsStatus -> tsStatus.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode())
.findFirst()
.orElse(null);
connector.recordUserDataTransferAudit(
failedStatus == null,
failedStatus == null ? null : String.valueOf(failedStatus.getCode()),
null);
transferAuditRecorded = true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it is ok to report only the first failure.
Maybe we should conclude/concat the other failures too?

@HTHou HTHou Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept one representative error intentionally and clarified it in 9e6cbee. The batch RPC is one physical transfer attempt, so it produces one audit event. The event result is failure if any sub-response fails, and the first failed status code fills the minimum schema single error value. Concatenating every sub-status would make the audit payload grow with batch size; detailed per-item failures are still processed by the existing status handler.

Comment on lines 130 to +135
@Override
public void onError(final Exception exception) {
if (!transferAuditRecorded) {
connector.recordUserDataTransferAudit(false, null, exception);
transferAuditRecorded = true;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same here, not sure if we should only record the first one.

@HTHou HTHou Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarified in 9e6cbee. onError receives one exception for that physical RPC attempt. A retry is sent with a new handler and therefore emits a new audit event. transferAuditRecorded only prevents double-recording when onComplete has already recorded the response and later response-processing code throws into onError.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants