feature: add HMAC-SHA256 mutual authentication between NamingServer and Server - #8185
Open
slievrly wants to merge 2 commits into
Open
feature: add HMAC-SHA256 mutual authentication between NamingServer and Server#8185slievrly wants to merge 2 commits into
slievrly wants to merge 2 commits into
Conversation
…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]>
Contributor
There was a problem hiding this comment.
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 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 Report❌ Patch coverage is 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
🚀 New features to boost your workflow:
|
- 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds signed-request authentication for the previously unauthenticated NamingServer ↔ Seata Server control plane, with multi-tenant isolation via cluster-id and permission scoping.
Components
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