Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: migrate to google-auth-library for pre-emptive oauth refresh #3228

Merged
merged 3 commits into from
Sep 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,7 @@ public class RetryOptions implements Serializable, Cloneable {
/** For internal use only - public for technical reasons. */
@InternalApi("For internal usage only")
public static final Set<Status.Code> DEFAULT_ENABLE_GRPC_RETRIES_SET =
ImmutableSet.of(
Status.Code.DEADLINE_EXCEEDED,
Status.Code.UNAVAILABLE,
Status.Code.ABORTED,
Status.Code.UNAUTHENTICATED);
ImmutableSet.of(Status.Code.DEADLINE_EXCEEDED, Status.Code.UNAVAILABLE, Status.Code.ABORTED);

/**
* We can timeout when reading large cells with a low value here.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import com.google.cloud.bigtable.config.BulkOptions;
import com.google.cloud.bigtable.config.CredentialOptions;
import com.google.cloud.bigtable.config.Logger;
import com.google.cloud.bigtable.config.RetryOptions;
import com.google.cloud.bigtable.core.IBigtableDataClient;
import com.google.cloud.bigtable.core.IBigtableTableAdminClient;
import com.google.cloud.bigtable.core.IBulkMutation;
Expand Down Expand Up @@ -392,10 +391,9 @@ private WatchdogInterceptor setupWatchdog() {
private static ClientInterceptor createAuthInterceptor(BigtableOptions options)
throws IOException {
CredentialInterceptorCache credentialsCache = CredentialInterceptorCache.getInstance();
RetryOptions retryOptions = options.getRetryOptions();
CredentialOptions credentialOptions = options.getCredentialOptions();
try {
return credentialsCache.getCredentialsInterceptor(credentialOptions, retryOptions);
return credentialsCache.getCredentialsInterceptor(credentialOptions);
} catch (GeneralSecurityException e) {
throw new IOException("Could not initialize credentials.", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,13 @@
import com.google.cloud.bigtable.config.CredentialFactory;
import com.google.cloud.bigtable.config.CredentialOptions;
import com.google.cloud.bigtable.config.CredentialOptions.CredentialType;
import com.google.cloud.bigtable.config.RetryOptions;
import com.google.cloud.bigtable.util.ThreadUtil;
import com.google.common.util.concurrent.MoreExecutors;
import io.grpc.ClientInterceptor;
import io.grpc.auth.ClientAuthInterceptor;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
* Caches {@link com.google.cloud.bigtable.grpc.io.RefreshingOAuth2CredentialsInterceptor} for
* default authorization cases. In other types of authorization, such as file based Credentials, it
* will create a new one.
* Caches {@link CredentialsInterceptor} for default authorization cases. In other types of
* authorization, such as file based Credentials, it will create a new one.
*
* <p>For internal use only - public for technical reasons.
*/
Expand All @@ -51,9 +44,6 @@ public static CredentialInterceptorCache getInstance() {
return instance;
}

private final ExecutorService executor =
Executors.newCachedThreadPool(ThreadUtil.getThreadFactory("Credentials-Refresh-%d", true));

private ClientInterceptor defaultCredentialInterceptor;

private CredentialInterceptorCache() {}
Expand All @@ -64,26 +54,24 @@ private CredentialInterceptorCache() {}
*
* <ol>
* <li>Look up the credentials
* <li>If there are credentials, create a gRPC interceptor that gets OAuth2 security tokens and
* add that token as a header on all calls. <br>
* NOTE: {@link com.google.cloud.bigtable.grpc.io.RefreshingOAuth2CredentialsInterceptor}
* ensures that the token stays fresh. It does token lookups asynchronously so that the
* calls themselves take as little performance penalty as possible.
* <li>If there are credentials, create a gRPC interceptor that adds the credentials to {@link
* io.grpc.CallOptions}. <br>
* NOTE: {@link OAuth2Credentials} ensures that the token stays fresh. It does token lookups
* asynchronously so that the calls themselves take as little performance penalty as
* possible.
* <li>Cache the interceptor in step #2 if the {@link
* com.google.cloud.bigtable.config.CredentialOptions} uses <a
* href="https://developers.google.com/identity/protocols/application-default-credentials">
* default application credentials </a>
* </ol>
*
* @param credentialOptions Defines how credentials should be achieved
* @param retryOptions a {@link com.google.cloud.bigtable.config.RetryOptions} object.
* @return a HeaderInterceptor
* @return a ClientInterceptor
* @throws java.io.IOException if any.
* @throws java.security.GeneralSecurityException if any.
*/
public synchronized ClientInterceptor getCredentialsInterceptor(
CredentialOptions credentialOptions, RetryOptions retryOptions)
throws IOException, GeneralSecurityException {
CredentialOptions credentialOptions) throws IOException, GeneralSecurityException {
// Default credentials is the most likely CredentialType. It's also the only CredentialType
// that can be safely cached.
boolean isDefaultCredentials =
Expand All @@ -99,34 +87,11 @@ public synchronized ClientInterceptor getCredentialsInterceptor(
return null;
}

// Optimization for asyncOAuth refresh
if (credentials instanceof OAuth2Credentials) {
RefreshingOAuth2CredentialsInterceptor oauth2Interceptor =
new RefreshingOAuth2CredentialsInterceptor(executor, (OAuth2Credentials) credentials);

// The RefreshingOAuth2CredentialsInterceptor uses the credentials to get a security token
// that
// will live for a short time. That token is added on all calls by the gRPC interceptor to
// allow users to access secure resources.
//
// Perform that token lookup asynchronously. This permits other work to be done in
// parallel. The RefreshingOAuth2CredentialsInterceptor has internal locking that assures that
// the oauth2 token is loaded before the interceptor proceeds with any calls.
oauth2Interceptor.asyncRefresh();
if (isDefaultCredentials) {
defaultCredentialInterceptor = oauth2Interceptor;
}
return oauth2Interceptor;
}

// Normal path
ClientInterceptor jwtAuthInterceptor =
new ClientAuthInterceptor(credentials, MoreExecutors.directExecutor());
CredentialsInterceptor credentialsInterceptor = new CredentialsInterceptor(credentials);

if (isDefaultCredentials) {
defaultCredentialInterceptor = jwtAuthInterceptor;
defaultCredentialInterceptor = credentialsInterceptor;
}

return jwtAuthInterceptor;
return credentialsInterceptor;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2021 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.cloud.bigtable.grpc.io;

import com.google.auth.Credentials;
import io.grpc.CallCredentials;
import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
import io.grpc.ClientInterceptor;
import io.grpc.ForwardingClientCall;
import io.grpc.MethodDescriptor;
import io.grpc.auth.MoreCallCredentials;

public class CredentialsInterceptor implements ClientInterceptor {

private CallCredentials callCredentials;

public CredentialsInterceptor(Credentials credentials) {
this.callCredentials = MoreCallCredentials.from(credentials);
}

@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
MethodDescriptor<ReqT, RespT> methodDescriptor, CallOptions callOptions, Channel channel) {
return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(
channel.newCall(methodDescriptor, callOptions.withCallCredentials(callCredentials))) {};
}
}
Loading