Skip to content
Permalink
Browse files

Fix expired access token bug in the proxy (#217)

#129 migrated `GoogleCredential`
to `GoogleCredentialsBundle` and introduced a subtle bug. I don't fully
understand why but there are times when the access token is null but
`credentials.refresh()` is not called, resulting in NullPointerException
when `credentials.getAccessToken().getTokenValue()` is called.

Since the new GoogleCredentials class supports `shouldRefresh()`, we now
just rely on it to make sure that we always get a value access token.
  • Loading branch information...
jianglai committed Aug 8, 2019
1 parent a68b1a1 commit c7478fc52b574275368ac0b52b2f397c24efba05
@@ -39,7 +39,6 @@

public String projectId;
public List<String> gcpScopes;
public int accessTokenRefreshBeforeExpirationSeconds;
public int serverCertificateCacheSeconds;
public Gcs gcs;
public Kms kms;
@@ -23,7 +23,6 @@
import com.google.api.services.cloudkms.v1.CloudKMS;
import com.google.api.services.cloudkms.v1.model.DecryptRequest;
import com.google.api.services.storage.Storage;
import com.google.auth.oauth2.AccessToken;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.common.flogger.LoggerConfig;
import com.google.monitoring.metrics.MetricReporter;
@@ -49,7 +48,6 @@
import java.io.IOException;
import java.util.Arrays;
import java.util.Base64;
import java.util.Date;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutorService;
@@ -223,19 +221,10 @@ static GoogleCredentialsBundle provideCredential(ProxyConfig config) {
GoogleCredentialsBundle credentialsBundle, ProxyConfig config) {
return () -> {
GoogleCredentials credentials = credentialsBundle.getGoogleCredentials();
AccessToken accessToken = credentials.getAccessToken();
Date nextExpirationTime =
new Date(
System.currentTimeMillis() + config.accessTokenRefreshBeforeExpirationSeconds * 1000);
// If we never obtained an access token, the expiration time is null.
if (accessToken == null
// If we have an access token, make sure to refresh it ahead of time.
|| accessToken.getExpirationTime().before(nextExpirationTime)) {
try {
credentials.refresh();
} catch (IOException e) {
throw new RuntimeException("Cannot refresh access token.", e);
}
try {
credentials.refreshIfExpired();
} catch (IOException e) {
throw new RuntimeException("Cannot refresh access token.", e);
}
return credentials.getAccessToken().getTokenValue();
};
@@ -20,25 +20,6 @@ gcpScopes:
# to authenticate.
- https://www.googleapis.com/auth/userinfo.email

# Refresh the access token 5 minutes before it expires.
#
# Depending on how the credential is obtained, its renewal behavior is
# different. A credential backed by a private key (like the ADC obtained
# locally) will get a different token when #refreshToken() is called. On GCE,
# the credential is just a wrapper around tokens sent from the metadata server,
# which is valid from 3599 seconds to 1699 seconds (this is no documentation on
# this, I got this number by logging in a GCE VM, calling curl on the metatdata
# server every minute, and check the expiration time of the response). Calling
# refreshToken() does *not* get a new token. The token is only refreshed by
# metadata server itself (every 3599 - 1699 = 1900 seconds).
#
# We refresh the token 5 minutes before it expires, which should work in both
# cases. This is better than caching the token for a pre-defined period, because
# even right after #refreshToken() is called on the client side, tokens obtained
# from GCE metadata server may not be valid for the entirety of 3599 seconds.

accessTokenRefreshBeforeExpirationSeconds: 300

# Server certificate is cached for 30 minutes.
#
# Encrypted server server certificate and private keys are stored on GCS. They

0 comments on commit c7478fc

Please sign in to comment.
You can’t perform that action at this time.