diff --git a/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java b/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java index 4117677cbd..51b29a90e3 100644 --- a/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java +++ b/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java @@ -26,6 +26,7 @@ import io.grpc.Attributes; import io.grpc.CallCredentials; import io.grpc.Metadata; import io.grpc.MethodDescriptor; +import io.grpc.SecurityLevel; import io.grpc.Status; import io.grpc.StatusException; import java.io.IOException; @@ -51,7 +52,10 @@ final class GoogleAuthLibraryCallCredentials implements CallCredentials { = Logger.getLogger(GoogleAuthLibraryCallCredentials.class.getName()); private static final JwtHelper jwtHelper = createJwtHelperOrNull(GoogleAuthLibraryCallCredentials.class.getClassLoader()); + private static final Class googleCredentialsClass + = loadGoogleCredentialsClass(); + private final boolean requirePrivacy; @VisibleForTesting final Credentials creds; @@ -65,9 +69,18 @@ final class GoogleAuthLibraryCallCredentials implements CallCredentials { @VisibleForTesting GoogleAuthLibraryCallCredentials(Credentials creds, JwtHelper jwtHelper) { checkNotNull(creds, "creds"); + boolean requirePrivacy = false; + if (googleCredentialsClass != null) { + // All GoogleCredentials instances are bearer tokens and should only be used on private + // channels. This catches all return values from GoogleCredentials.getApplicationDefault(). + // This should be checked before upgrading the Service Account to JWT, as JWT is also a bearer + // token. + requirePrivacy = googleCredentialsClass.isInstance(creds); + } if (jwtHelper != null) { creds = jwtHelper.tryServiceAccountToJwt(creds); } + this.requirePrivacy = requirePrivacy; this.creds = creds; } @@ -77,6 +90,14 @@ final class GoogleAuthLibraryCallCredentials implements CallCredentials { @Override public void applyRequestMetadata(MethodDescriptor method, Attributes attrs, Executor appExecutor, final MetadataApplier applier) { + SecurityLevel security = checkNotNull(attrs.get(ATTR_SECURITY_LEVEL), "securityLevel"); + if (requirePrivacy && security != SecurityLevel.PRIVACY_AND_INTEGRITY) { + applier.fail(Status.UNAUTHENTICATED + .withDescription("Credentials require channel with PRIVACY_AND_INTEGRITY security level. " + + "Observed security level: " + security)); + return; + } + String authority = checkNotNull(attrs.get(ATTR_AUTHORITY), "authority"); final URI uri; try { @@ -214,6 +235,19 @@ final class GoogleAuthLibraryCallCredentials implements CallCredentials { return null; } + @Nullable + private static Class loadGoogleCredentialsClass() { + Class rawGoogleCredentialsClass; + try { + // Can't use a loader as it disables ProGuard's reference detection and would fail to rename + // this reference. Unfortunately this will initialize the class. + rawGoogleCredentialsClass = Class.forName("com.google.auth.oauth2.GoogleCredentials"); + } catch (ClassNotFoundException ex) { + return null; + } + return rawGoogleCredentialsClass.asSubclass(Credentials.class); + } + @VisibleForTesting static class JwtHelper { private final Class serviceAccountClass; diff --git a/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java b/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java index 53a5859b17..5104ceaa28 100644 --- a/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java +++ b/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java @@ -31,6 +31,7 @@ import static org.mockito.Mockito.when; import com.google.auth.Credentials; import com.google.auth.RequestMetadataCallback; import com.google.auth.oauth2.AccessToken; +import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.OAuth2Credentials; import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.common.collect.Iterables; @@ -254,10 +255,14 @@ public class GoogleAuthLibraryCallCredentialsTest { return token; } }; + // Security level should not impact non-GoogleCredentials + Attributes securityNone = attrs.toBuilder() + .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.NONE) + .build(); GoogleAuthLibraryCallCredentials callCredentials = new GoogleAuthLibraryCallCredentials(credentials); - callCredentials.applyRequestMetadata(method, attrs, executor, applier); + callCredentials.applyRequestMetadata(method, securityNone, executor, applier); assertEquals(1, runPendingRunnables()); verify(applier).apply(headersCaptor.capture()); @@ -267,6 +272,45 @@ public class GoogleAuthLibraryCallCredentialsTest { Iterables.toArray(authorization, String.class)); } + @Test + public void googleCredential_privacyAndIntegrityAllowed() { + final AccessToken token = new AccessToken("allyourbase", new Date(Long.MAX_VALUE)); + final Credentials credentials = GoogleCredentials.create(token); + Attributes privacy = attrs.toBuilder() + .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY) + .build(); + + GoogleAuthLibraryCallCredentials callCredentials = + new GoogleAuthLibraryCallCredentials(credentials); + callCredentials.applyRequestMetadata(method, privacy, executor, applier); + runPendingRunnables(); + + verify(applier).apply(headersCaptor.capture()); + Metadata headers = headersCaptor.getValue(); + Iterable authorization = headers.getAll(AUTHORIZATION); + assertArrayEquals(new String[]{"Bearer allyourbase"}, + Iterables.toArray(authorization, String.class)); + } + + @Test + public void googleCredential_integrityDenied() { + final AccessToken token = new AccessToken("allyourbase", new Date(Long.MAX_VALUE)); + final Credentials credentials = GoogleCredentials.create(token); + // Anything less than PRIVACY_AND_INTEGRITY should fail + Attributes integrity = attrs.toBuilder() + .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.INTEGRITY) + .build(); + + GoogleAuthLibraryCallCredentials callCredentials = + new GoogleAuthLibraryCallCredentials(credentials); + callCredentials.applyRequestMetadata(method, integrity, executor, applier); + runPendingRunnables(); + + verify(applier).fail(statusCaptor.capture()); + Status status = statusCaptor.getValue(); + assertEquals(Status.Code.UNAUTHENTICATED, status.getCode()); + } + @Test public void serviceUri() throws Exception { GoogleAuthLibraryCallCredentials callCredentials =