Repository navigation
Description
alts: AuthorizationUtil.clientAuthorizationCheck compares the peer service account case-sensitively
**正文(Markdown 原文)**
`````markdown
### What version of gRPC-Java are you using?
`master` at [`15d64a4`](https://github.com/grpc/grpc-java/commit/15d64a4678450991aa5bf7af23066d36b3391351) (`1.86.0-SNAPSHOT`); `AuthorizationUtil` is unchanged on current `master`.
### What is your environment?
Linux x86_64, OpenJDK 21.0.2, Gradle 8.14.5 (wrapper).
### What did you expect to see?
`AuthorizationUtil.clientAuthorizationCheck(call, expectedServiceAccounts)` accepts a client whose ALTS peer service account differs from an allowed entry only in letter case (for example peer `alice`, allowed `ALICE`), as grpc-go's equivalent `alts.ClientAuthorizationCheck` does.
### What did you see instead?
It returns `PERMISSION_DENIED` ("Client alice is not authorized").
**Location** (`grpc/grpc-java` @ `15d64a4`):
- [`alts/src/main/java/io/grpc/alts/AuthorizationUtil.java:35-47`](https://github.com/grpc/grpc-java/blob/15d64a4678450991aa5bf7af23066d36b3391351/alts/src/main/java/io/grpc/alts/AuthorizationUtil.java#L35-L47)
```java
public static Status clientAuthorizationCheck(
ServerCall<?, ?> call, Collection<String> expectedServiceAccounts) {
...
if (expectedServiceAccounts.contains(altsContext.getPeerServiceAccount())) {
return Status.OK;
}
return Status.PERMISSION_DENIED.withDescription(
"Client " + altsContext.getPeerServiceAccount() + " is not authorized");
}
Problem
Collection.contains() uses String.equals(), so the check is exact-match. Service account identifiers are e-mail style names that people often write in mixed case in config. grpc-go changed its helper for exactly this reason ("expectedServiceAccounts are commonly mixed-case, while the PeerServiceAccount is lower-case"). With the Java helper, an allow-list entry that differs only in case rejects a legitimate client, and the two languages give different answers for the same allow-list. This only concerns letter case; any other difference in the name is still rejected.
Steps to reproduce the bug
Reproduction
Regression test added to AuthorizationUtilTest (reuses its FakeServerCall, which sets the ALTS peer service account):
diff --git a/alts/src/test/java/io/grpc/alts/AuthorizationUtilTest.java b/alts/src/test/java/io/grpc/alts/AuthorizationUtilTest.java
--- a/alts/src/test/java/io/grpc/alts/AuthorizationUtilTest.java
+++ b/alts/src/test/java/io/grpc/alts/AuthorizationUtilTest.java
@@ -55,6 +55,14 @@ public final class AuthorizationUtilTest {
assertThat(status.getDescription()).endsWith("not authorized");
}
+ @Test
+ public void altsAuthorizationCheck_ignoresCase() throws Exception {
+ Status status =
+ AuthorizationUtil.clientAuthorizationCheck(
+ new FakeServerCall("alice"), Lists.newArrayList("ALICE", "Bob"));
+ assertThat(status.getCode()).isEqualTo(Status.Code.OK);
+ }
+
private static class FakeServerCall extends ServerCall<String, String> {
final Attributes attrs;
./gradlew :grpc-alts:test --tests io.grpc.alts.AuthorizationUtilTest -PskipCodegen=true -PskipAndroid=true
AuthorizationUtilTest > altsAuthorizationCheck_ignoresCase FAILED
value of: getCode()
expected: OK
but was : PERMISSION_DENIED
at app//io.grpc.alts.AuthorizationUtilTest.altsAuthorizationCheck_ignoresCase(AuthorizationUtilTest.java:63)
152 tests completed, 1 failed
Environment:
- Commit:
15d64a4678450991aa5bf7af23066d36b3391351 - Toolchain: OpenJDK 21.0.2, Gradle 8.14.5
Already fixed in grpc/grpc-go
PR: grpc/grpc-go#3792 ("credentials/alts: ClientAuthorizationCheck to case-fold compare of peer SA", merged 2020-09-01T23:23:56Z, merge commit 48bf772d0c2). It switched the peer service account comparison to strings.EqualFold.
How grpc-go fixed it (relevant hunks, verbatim):
--- a/credentials/alts/utils.go
+++ b/credentials/alts/utils.go
@@ -152,12 +152,13 @@ func AuthInfoFromPeer(p *peer.Peer) (AuthInfo, error) {
func ClientAuthorizationCheck(ctx context.Context, expectedServiceAccounts []string) error {
authInfo, err := AuthInfoFromContext(ctx)
if err != nil {
- return status.Newf(codes.PermissionDenied, "The context is not an ALTS-compatible context: %v", err).Err()
+ return status.Errorf(codes.PermissionDenied, "The context is not an ALTS-compatible context: %v", err)
}
+ peer := authInfo.PeerServiceAccount()
for _, sa := range expectedServiceAccounts {
- if authInfo.PeerServiceAccount() == sa {
+ if strings.EqualFold(peer, sa) {
return nil
}
}
- return status.Newf(codes.PermissionDenied, "Client %v is not authorized", authInfo.PeerServiceAccount()).Err()
+ return status.Errorf(codes.PermissionDenied, "Client %v is not authorized", peer)
}Suggested fix
Compare the peer service account to each expected entry with equalsIgnoreCase, and document it:
diff --git a/alts/src/main/java/io/grpc/alts/AuthorizationUtil.java b/alts/src/main/java/io/grpc/alts/AuthorizationUtil.java
--- a/alts/src/main/java/io/grpc/alts/AuthorizationUtil.java
+++ b/alts/src/main/java/io/grpc/alts/AuthorizationUtil.java
@@ -29,8 +29,8 @@ public final class AuthorizationUtil {
/**
* Given a server call, performs client authorization check, i.e., checks if the client service
- * account matches one of the expected service accounts. It returns OK if client is authorized and
- * an error otherwise.
+ * account matches one of the expected service accounts, ignoring case. It returns OK if client
+ * is authorized and an error otherwise.
*/
public static Status clientAuthorizationCheck(
ServerCall<?, ?> call, Collection<String> expectedServiceAccounts) {
@@ -39,10 +39,13 @@ public final class AuthorizationUtil {
if (altsContext == null) {
return Status.PERMISSION_DENIED.withDescription("Peer ALTS AuthContext not found");
}
- if (expectedServiceAccounts.contains(altsContext.getPeerServiceAccount())) {
- return Status.OK;
+ String peerServiceAccount = altsContext.getPeerServiceAccount();
+ for (String serviceAccount : expectedServiceAccounts) {
+ if (peerServiceAccount.equalsIgnoreCase(serviceAccount)) {
+ return Status.OK;
+ }
}
return Status.PERMISSION_DENIED.withDescription(
- "Client " + altsContext.getPeerServiceAccount() + " is not authorized");
+ "Client " + peerServiceAccount + " is not authorized");
}
}Verified on 15d64a4 (OpenJDK 21.0.2, Gradle 8.14.5): the test above fails without this patch and passes with it; no other test results change (:grpc-alts:test).
I can submit a PR for this.
Checked: searches for clientAuthorizationCheck case, ALTS service account case-insensitive and AuthorizationUtil found only #11042 and #7840 (ALTS context API changes), not this comparison.
Activity
shivaspeaks commentedon Nov 3, 2026
I agree with this.
But there is no cross-language spec mention for this. gRPC C++ also matches exactly, and Go's case-folding was a Go-only change.