mirror of https://github.com/grpc/grpc-java.git
auth: Require PRIVACY_AND_INTEGRITY for GoogleCredentials
This keeps them more secure. Other types of creds are left as-is, since we don't quite know if it makes sense to have a similar restriction. (It likely does make sense, but this is a more precise change for our needs.)
This commit is contained in:
parent
e41e054776
commit
8e9d4cbe5c
|
|
@ -26,6 +26,7 @@ import io.grpc.Attributes;
|
||||||
import io.grpc.CallCredentials;
|
import io.grpc.CallCredentials;
|
||||||
import io.grpc.Metadata;
|
import io.grpc.Metadata;
|
||||||
import io.grpc.MethodDescriptor;
|
import io.grpc.MethodDescriptor;
|
||||||
|
import io.grpc.SecurityLevel;
|
||||||
import io.grpc.Status;
|
import io.grpc.Status;
|
||||||
import io.grpc.StatusException;
|
import io.grpc.StatusException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -51,7 +52,10 @@ final class GoogleAuthLibraryCallCredentials implements CallCredentials {
|
||||||
= Logger.getLogger(GoogleAuthLibraryCallCredentials.class.getName());
|
= Logger.getLogger(GoogleAuthLibraryCallCredentials.class.getName());
|
||||||
private static final JwtHelper jwtHelper
|
private static final JwtHelper jwtHelper
|
||||||
= createJwtHelperOrNull(GoogleAuthLibraryCallCredentials.class.getClassLoader());
|
= createJwtHelperOrNull(GoogleAuthLibraryCallCredentials.class.getClassLoader());
|
||||||
|
private static final Class<? extends Credentials> googleCredentialsClass
|
||||||
|
= loadGoogleCredentialsClass();
|
||||||
|
|
||||||
|
private final boolean requirePrivacy;
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
final Credentials creds;
|
final Credentials creds;
|
||||||
|
|
||||||
|
|
@ -65,9 +69,18 @@ final class GoogleAuthLibraryCallCredentials implements CallCredentials {
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
GoogleAuthLibraryCallCredentials(Credentials creds, JwtHelper jwtHelper) {
|
GoogleAuthLibraryCallCredentials(Credentials creds, JwtHelper jwtHelper) {
|
||||||
checkNotNull(creds, "creds");
|
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) {
|
if (jwtHelper != null) {
|
||||||
creds = jwtHelper.tryServiceAccountToJwt(creds);
|
creds = jwtHelper.tryServiceAccountToJwt(creds);
|
||||||
}
|
}
|
||||||
|
this.requirePrivacy = requirePrivacy;
|
||||||
this.creds = creds;
|
this.creds = creds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,6 +90,14 @@ final class GoogleAuthLibraryCallCredentials implements CallCredentials {
|
||||||
@Override
|
@Override
|
||||||
public void applyRequestMetadata(MethodDescriptor<?, ?> method, Attributes attrs,
|
public void applyRequestMetadata(MethodDescriptor<?, ?> method, Attributes attrs,
|
||||||
Executor appExecutor, final MetadataApplier applier) {
|
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");
|
String authority = checkNotNull(attrs.get(ATTR_AUTHORITY), "authority");
|
||||||
final URI uri;
|
final URI uri;
|
||||||
try {
|
try {
|
||||||
|
|
@ -214,6 +235,19 @@ final class GoogleAuthLibraryCallCredentials implements CallCredentials {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static Class<? extends Credentials> 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
|
@VisibleForTesting
|
||||||
static class JwtHelper {
|
static class JwtHelper {
|
||||||
private final Class<? extends Credentials> serviceAccountClass;
|
private final Class<? extends Credentials> serviceAccountClass;
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import static org.mockito.Mockito.when;
|
||||||
import com.google.auth.Credentials;
|
import com.google.auth.Credentials;
|
||||||
import com.google.auth.RequestMetadataCallback;
|
import com.google.auth.RequestMetadataCallback;
|
||||||
import com.google.auth.oauth2.AccessToken;
|
import com.google.auth.oauth2.AccessToken;
|
||||||
|
import com.google.auth.oauth2.GoogleCredentials;
|
||||||
import com.google.auth.oauth2.OAuth2Credentials;
|
import com.google.auth.oauth2.OAuth2Credentials;
|
||||||
import com.google.auth.oauth2.ServiceAccountCredentials;
|
import com.google.auth.oauth2.ServiceAccountCredentials;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
|
@ -254,10 +255,14 @@ public class GoogleAuthLibraryCallCredentialsTest {
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// Security level should not impact non-GoogleCredentials
|
||||||
|
Attributes securityNone = attrs.toBuilder()
|
||||||
|
.set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.NONE)
|
||||||
|
.build();
|
||||||
|
|
||||||
GoogleAuthLibraryCallCredentials callCredentials =
|
GoogleAuthLibraryCallCredentials callCredentials =
|
||||||
new GoogleAuthLibraryCallCredentials(credentials);
|
new GoogleAuthLibraryCallCredentials(credentials);
|
||||||
callCredentials.applyRequestMetadata(method, attrs, executor, applier);
|
callCredentials.applyRequestMetadata(method, securityNone, executor, applier);
|
||||||
assertEquals(1, runPendingRunnables());
|
assertEquals(1, runPendingRunnables());
|
||||||
|
|
||||||
verify(applier).apply(headersCaptor.capture());
|
verify(applier).apply(headersCaptor.capture());
|
||||||
|
|
@ -267,6 +272,45 @@ public class GoogleAuthLibraryCallCredentialsTest {
|
||||||
Iterables.toArray(authorization, String.class));
|
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<String> 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
|
@Test
|
||||||
public void serviceUri() throws Exception {
|
public void serviceUri() throws Exception {
|
||||||
GoogleAuthLibraryCallCredentials callCredentials =
|
GoogleAuthLibraryCallCredentials callCredentials =
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue