Skip to content

feature: add HMAC-SHA256 mutual authentication between NamingServer and Server - #8185

Open
slievrly wants to merge 2 commits into
apache:2.xfrom
slievrly:2.x_26_07_26
Open

feature: add HMAC-SHA256 mutual authentication between NamingServer and Server#8185
slievrly wants to merge 2 commits into
apache:2.xfrom
slievrly:2.x_26_07_26

Conversation

@slievrly

Copy link
Copy Markdown
Member

Adds signed-request authentication for the previously unauthenticated NamingServer ↔ Seata Server control plane, with multi-tenant isolation via cluster-id and permission scoping.

Components

  • common/security: reusable canonical-request builder, HMAC signer/verifier (constant-time compare), nonce cache with pluggable clock, and structured verification result.
  • namingserver/security: inbound Servlet Filter enforcing signature + namespace/cluster/vgroup allow-lists + per-route permission; outbound signer for vGroup writes / Console proxy / MCP calls to TC.
  • server/security: inbound filter for /vgroup/v1/* and /api//console/ with its own allowed-callers table (supports two coexisting entries for zero-downtime key rotation).

Both filters ship as Spring Boot auto-configuration gated by an enabled flag, with WARN and ENFORCE modes for gray rollout. Secrets are loaded via a SecretResolver supporting env:, base64: and plain: schemes to keep raw key material out of config files.

Tests

122 unit tests across three modules; common module coverage 93% instruction / 89% branch. An InteropIntegrationTest guards against wire-format drift between the two sides.

Docs

docs/SECURITY_AUTH_GUIDE.md covers full config reference, three deployment scenarios (single cluster / multi-tenant / Raft HA), key generation, zero- downtime rotation, gray rollout, monitoring, and troubleshooting.

Also adds jakarta.servlet-api (provided scope) to server/pom.xml so the new Spring Boot 4.x-compatible filter compiles.

Ⅰ. Describe what this PR did

Ⅱ. Does this pull request fix one issue?

Ⅲ. Why don't you add test cases (unit test/integration test)?

Ⅳ. Describe how to verify it

Ⅴ. Special notes for reviews

…nd Seata Server

Adds signed-request authentication for the previously unauthenticated
NamingServer ↔ Seata Server control plane, with multi-tenant isolation via
cluster-id and permission scoping.

Components
----------
- common/security: reusable canonical-request builder, HMAC signer/verifier
  (constant-time compare), nonce cache with pluggable clock, and structured
  verification result.
- namingserver/security: inbound Servlet Filter enforcing signature +
  namespace/cluster/vgroup allow-lists + per-route permission; outbound signer
  for vGroup writes / Console proxy / MCP calls to TC.
- server/security: inbound filter for /vgroup/v1/* and /api/*/console/* with
  its own allowed-callers table (supports two coexisting entries for
  zero-downtime key rotation).

Both filters ship as Spring Boot auto-configuration gated by an enabled flag,
with WARN and ENFORCE modes for gray rollout. Secrets are loaded via a
SecretResolver supporting env:, base64: and plain: schemes to keep raw key
material out of config files.

Tests
-----
122 unit tests across three modules; common module coverage 93% instruction
/ 89% branch. An InteropIntegrationTest guards against wire-format drift
between the two sides.

Docs
----
docs/SECURITY_AUTH_GUIDE.md covers full config reference, three deployment
scenarios (single cluster / multi-tenant / Raft HA), key generation, zero-
downtime rotation, gray rollout, monitoring, and troubleshooting.

Also adds jakarta.servlet-api (provided scope) to server/pom.xml so the new
Spring Boot 4.x-compatible filter compiles.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Copilot AI review requested due to automatic review settings July 26, 2026 17:07
@slievrly slievrly added the Do Not Merge Do not merge into develop label Jul 26, 2026

Copilot AI left a comment

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.

Pull request overview

This PR introduces an HMAC-signed request authentication layer for the previously unauthenticated HTTP control-plane traffic between NamingServer and Seata Server (TC), including permission-scoped authorization and replay protection, with Spring Boot auto-configuration and operator documentation.

Changes:

  • Add reusable signing/verifying primitives in common/security (canonical request, HMAC signer, nonce cache, verifier, structured result).
  • Add NamingServer inbound filter + outbound signer with permission/allow-list enforcement and config binding/auto-config.
  • Add TC inbound filter with allowed-caller registry, route authorization, config binding/auto-config, plus comprehensive tests and an operator guide.

Reviewed changes

Copilot reviewed 46 out of 46 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
server/src/test/java/org/apache/seata/server/security/ServerSecretResolverTest.java Unit tests for TC-side secret reference resolution schemes.
server/src/test/java/org/apache/seata/server/security/SeataServerAuthFilterTest.java Unit tests for TC inbound auth filter behavior (warn/enforce, replay, tamper).
server/src/test/java/org/apache/seata/server/security/RouteAuthorizerTest.java Unit tests for TC route→permission mapping and authorization decisions.
server/src/test/java/org/apache/seata/server/security/InteropIntegrationTest.java End-to-end interop tests guarding wire-format drift between signer and verifier.
server/src/test/java/org/apache/seata/server/security/AllowedCallerRegistryTest.java Unit tests for allowed-caller registry semantics and defensive copying.
server/src/main/java/org/apache/seata/server/security/ServerSecurityProperties.java TC inbound security configuration properties (enabled/mode/cache windows/exclusions/callers).
server/src/main/java/org/apache/seata/server/security/ServerSecurityAutoConfiguration.java Spring Boot auto-config to wire TC inbound auth pipeline and servlet filter.
server/src/main/java/org/apache/seata/server/security/ServerSecretResolver.java TC-side secret reference resolver (env/base64/plain).
server/src/main/java/org/apache/seata/server/security/SeataServerAuthFilter.java TC inbound servlet filter: header parsing, body buffering, signature verification, permission enforcement.
server/src/main/java/org/apache/seata/server/security/RouteAuthorizer.java TC route→permission mapper for protected HTTP endpoints.
server/src/main/java/org/apache/seata/server/security/CallerPermission.java TC-side permission vocabulary for inbound NamingServer calls.
server/src/main/java/org/apache/seata/server/security/AllowedCallerRegistry.java TC allowed-caller in-memory registry (supports reload/rotation).
server/src/main/java/org/apache/seata/server/security/AllowedCaller.java TC caller identity model with secret + permission set (defensive secret copies).
server/pom.xml Adds servlet API dependency needed to compile the new Jakarta-based filter.
namingserver/src/test/java/org/apache/seata/namingserver/security/SecurityFilterTest.java Unit tests for NamingServer inbound filter verification and authorization pipeline.
namingserver/src/test/java/org/apache/seata/namingserver/security/SecurityAutoConfigurationTest.java Tests for glob→regex translation used in vgroup allow-lists.
namingserver/src/test/java/org/apache/seata/namingserver/security/SecretResolverTest.java Unit tests for NamingServer secret reference resolution schemes.
namingserver/src/test/java/org/apache/seata/namingserver/security/PermissionCheckerTest.java Unit tests for NamingServer route→permission mapping and scoped authorization checks.
namingserver/src/test/java/org/apache/seata/namingserver/security/OutboundSignerTest.java Unit tests for NamingServer outbound signer header production and verification.
namingserver/src/test/java/org/apache/seata/namingserver/security/ClusterIdentityTest.java Unit tests for NamingServer identity allow-list semantics and defensive copying.
namingserver/src/test/java/org/apache/seata/namingserver/security/ClusterIdentityRegistryTest.java Unit tests for NamingServer identity registry behavior.
namingserver/src/main/java/org/apache/seata/namingserver/security/SecurityProperties.java NamingServer security configuration properties (enabled/mode/exclusions/clusters/outbound).
namingserver/src/main/java/org/apache/seata/namingserver/security/SecurityFilter.java NamingServer inbound servlet filter enforcing signed requests + permission/allow-list checks.
namingserver/src/main/java/org/apache/seata/namingserver/security/SecurityAutoConfiguration.java Spring Boot auto-config to wire NamingServer security filter + supporting beans.
namingserver/src/main/java/org/apache/seata/namingserver/security/SecretResolver.java NamingServer secret reference resolver (env/base64/plain).
namingserver/src/main/java/org/apache/seata/namingserver/security/PermissionChecker.java NamingServer authorization logic (route→permission + allow-list scoping).
namingserver/src/main/java/org/apache/seata/namingserver/security/Permission.java NamingServer-side permission vocabulary.
namingserver/src/main/java/org/apache/seata/namingserver/security/OutboundSigner.java NamingServer outbound signer producing X-Seata-* headers for TC calls.
namingserver/src/main/java/org/apache/seata/namingserver/security/ClusterIdentityRegistry.java NamingServer in-memory identity registry (supports reload).
namingserver/src/main/java/org/apache/seata/namingserver/security/ClusterIdentity.java NamingServer caller identity model (secret, allow-lists, permissions).
docs/SECURITY_AUTH_GUIDE.md Operator guide for configuration, rollout, rotation, monitoring, troubleshooting.
common/src/test/java/org/apache/seata/common/security/VerificationResultTest.java Tests for structured verification outcomes.
common/src/test/java/org/apache/seata/common/security/SignatureVerifierTest.java Tests for freshness, replay, signature validation, and failure modes.
common/src/test/java/org/apache/seata/common/security/SignatureCanonicalizerTest.java Tests for canonicalization stability and encoding rules.
common/src/test/java/org/apache/seata/common/security/SignatureAlgorithmTest.java Tests for wire-name parsing and JCA name stability.
common/src/test/java/org/apache/seata/common/security/NonceCacheTest.java Tests for replay cache behavior (TTL, concurrency, eviction).
common/src/test/java/org/apache/seata/common/security/HmacSignerTest.java Tests for signing/verifying and constant-time compare behavior.
common/src/test/java/org/apache/seata/common/security/CanonicalRequestTest.java Tests for canonical request immutability and defaults.
common/src/main/java/org/apache/seata/common/security/VerificationResult.java Structured verification result returned by verifier pipeline.
common/src/main/java/org/apache/seata/common/security/SignatureVerifier.java Verification pipeline combining freshness, nonce uniqueness, and signature validity.
common/src/main/java/org/apache/seata/common/security/SignatureCanonicalizer.java Canonical string builder (method/path/query/id/ts/nonce/body-hash).
common/src/main/java/org/apache/seata/common/security/SignatureAlgorithm.java Supported HMAC algorithms + wire identifiers.
common/src/main/java/org/apache/seata/common/security/SecurityConstants.java Shared header names, defaults, and error codes.
common/src/main/java/org/apache/seata/common/security/NonceCache.java Anti-replay cache interface + in-memory implementation.
common/src/main/java/org/apache/seata/common/security/HmacSigner.java HMAC signing/verifying with constant-time compare.
common/src/main/java/org/apache/seata/common/security/CanonicalRequest.java Immutable request model used by signer/verifier.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +29 to +44
private final ConcurrentHashMap<String, AllowedCaller> byId = new ConcurrentHashMap<>();

public void register(AllowedCaller caller) {
byId.put(caller.getId(), caller);
}

/**
* Swap the whole set. During a rotation the new secret and the old secret can coexist
* — the caller passes both entries in and this method installs them atomically.
*/
public void reload(Collection<AllowedCaller> callers) {
byId.clear();
for (AllowedCaller c : callers) {
byId.put(c.getId(), c);
}
}
Comment on lines +35 to +48
private final Map<String, ClusterIdentity> byId = new ConcurrentHashMap<>();

/** Register (or replace) an identity. Callers must have already validated the secret length. */
public void register(ClusterIdentity identity) {
byId.put(identity.getId(), identity);
}

/** Bulk-load, replacing everything atomically-ish (individual puts, not a swap). */
public void reload(Collection<ClusterIdentity> identities) {
byId.clear();
for (ClusterIdentity id : identities) {
byId.put(id.getId(), id);
}
}
Comment on lines +141 to +145
private static String key(String clusterId, String nonce) {
// Deliberately a plain concatenation with a delimiter that cannot appear inside
// a UUID nonce or a cluster-id. Avoids the overhead of building a composite key object.
return clusterId + '|' + nonce;
}
Comment on lines +105 to +117
static boolean constantTimeEquals(byte[] a, byte[] b) {
if (a == null || b == null) {
return false;
}
// XOR the length difference into the accumulator too: mismatched lengths must
// never fast-fail (that would leak length via timing).
int diff = a.length ^ b.length;
int len = Math.min(a.length, b.length);
for (int i = 0; i < len; i++) {
diff |= (a[i] ^ b[i]);
}
return diff == 0;
}
Comment on lines +19 to +28
/**
* Maps TC's inbound HTTP routes to a required {@link CallerPermission}. Deny-by-default:
* any path that isn't matched here returns {@code null} and the filter treats that as
* forbidden.
*
* <p>Kept intentionally small — only paths that could conceivably be reached by
* NamingServer or by an operator's console proxy need protection here. Netty ports and
* anything auto-served by Spring Boot infrastructure (actuator, static files) are
* expected to be either excluded up-front or protected by other means.
*/
Comment on lines +211 to +221
/** Minimal Ant-style matcher: supports trailing {@code /**} and exact match. */
static boolean matches(String pattern, String uri) {
if (pattern == null || uri == null) {
return false;
}
if (pattern.endsWith("/**")) {
String prefix = pattern.substring(0, pattern.length() - 3);
return uri.startsWith(prefix);
}
return pattern.equals(uri);
}
Comment on lines +281 to +286
private static String escapeJson(String v) {
if (v == null) {
return "";
}
return v.replace("\\", "\\\\").replace("\"", "\\\"");
}
Comment on lines +264 to +269
private static String escapeJson(String v) {
if (v == null) {
return "";
}
return v.replace("\\", "\\\\").replace("\"", "\\\"");
}
Comment thread server/pom.xml
Comment on lines 134 to +138
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<!-- Jakarta Servlet API required by the security auth filter, aligned with Spring Boot 4.x. -->
Comment on lines +84 to +85
// spaces become %20 (not '+'), slash '/' stays literal per URLEncoder,
// '=' inside value becomes %3D, '&' becomes %26.
@codecov

codecov Bot commented Jul 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.94009% with 24 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.11%. Comparing base (2376244) to head (74bce46).
⚠️ Report is 1 commits behind head on 2.x.

Files with missing lines Patch % Lines
...pache/seata/common/security/SignatureVerifier.java 77.41% 4 Missing and 3 partials ⚠️
...a/org/apache/seata/common/security/NonceCache.java 84.61% 2 Missing and 4 partials ⚠️
.../seata/common/security/SignatureCanonicalizer.java 84.61% 4 Missing and 2 partials ⚠️
...a/org/apache/seata/common/security/HmacSigner.java 87.87% 4 Missing ⚠️
...ache/seata/common/security/SignatureAlgorithm.java 93.33% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##                2.x    #8185      +/-   ##
============================================
+ Coverage     72.99%   73.11%   +0.11%     
+ Complexity     1142     1141       -1     
============================================
  Files          1151     1159       +8     
  Lines         42272    42489     +217     
  Branches       5045     5074      +29     
============================================
+ Hits          30858    31066     +208     
- Misses         8935     8940       +5     
- Partials       2479     2483       +4     
Files with missing lines Coverage Δ
...apache/seata/common/security/CanonicalRequest.java 100.00% <100.00%> (ø)
...pache/seata/common/security/SecurityConstants.java 100.00% <100.00%> (ø)
...ache/seata/common/security/VerificationResult.java 100.00% <100.00%> (ø)
...ache/seata/common/security/SignatureAlgorithm.java 93.33% <93.33%> (ø)
...a/org/apache/seata/common/security/HmacSigner.java 87.87% <87.87%> (ø)
...a/org/apache/seata/common/security/NonceCache.java 84.61% <84.61%> (ø)
.../seata/common/security/SignatureCanonicalizer.java 84.61% <84.61%> (ø)
...pache/seata/common/security/SignatureVerifier.java 77.41% <77.41%> (ø)

... and 7 files with indirect coverage changes

Impacted file tree graph

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

- docs/SECURITY_AUTH_GUIDE.md: add ASF license header (fixes check-license CI).
- AllowedCallerRegistry / ClusterIdentityRegistry: hold table as immutable snapshot
  behind AtomicReference; reload() now publishes atomically via a single set(),
  eliminating the clear()+put() race that could yield transient 401 during rotation.
- NonceCache: length-prefix the composite key so attacker-controlled clusterId or
  nonce values containing '|' can no longer collide across tenants (DoS via forced
  REPLAY_DETECTED).
- SecurityFilter / SeataServerAuthFilter matches(): require exact match or proper
  descendant for "/prefix/**" patterns to prevent sibling paths like "/actuatorX"
  from bypassing auth when "/actuator/**" is on the exclude list.
- SecurityFilter / SeataServerAuthFilter escapeJson(): escape control characters
  (\\n, \\r, \\t, \\b, \\f and other <0x20) so untrusted header values cannot break
  the JSON error response body.
- HmacSigner.constantTimeEquals: clarify Javadoc — iteration is bounded by the
  shorter input, which is safe because `expected` has a fixed algorithm-defined
  length and only `received` is attacker-controlled.
- RouteAuthorizer: correct Javadoc; unknown routes return null and are passed
  through by the filter (not treated as deny-by-default at this layer).
- SignatureCanonicalizerTest: fix misleading comment — URLEncoder form-encoding
  turns '/' into %2F.

Also picks up spotless-formatting adjustments in files it hadn't touched yet.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Do Not Merge Do not merge into develop

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants