|
42 | 42 | import com.google.api.client.util.GenericData; |
43 | 43 | import com.google.auth.http.HttpTransportFactory; |
44 | 44 | import com.google.common.base.MoreObjects; |
45 | | - |
46 | 45 | import java.io.IOException; |
47 | 46 | import java.io.InputStream; |
48 | 47 | import java.io.ObjectInputStream; |
| 48 | +import java.net.SocketTimeoutException; |
49 | 49 | import java.net.UnknownHostException; |
50 | 50 | import java.util.Date; |
51 | 51 | import java.util.Objects; |
| 52 | +import java.util.logging.Level; |
| 53 | +import java.util.logging.Logger; |
52 | 54 |
|
53 | 55 | /** |
54 | 56 | * OAuth2 credentials representing the built-in service account for a Google Compute Engine VM. |
|
57 | 59 | */ |
58 | 60 | public class ComputeEngineCredentials extends GoogleCredentials { |
59 | 61 |
|
60 | | - static final String TOKEN_SERVER_ENCODED_URL = |
61 | | - "http://metadata/computeMetadata/v1/instance/service-accounts/default/token"; |
62 | | - static final String METADATA_SERVER_URL = "http://metadata.google.internal"; |
| 62 | + private static final Logger LOGGER = Logger.getLogger(ComputeEngineCredentials.class.getName()); |
| 63 | + |
| 64 | + // Note: the explicit IP address is used to avoid name server resolution issues. |
| 65 | + static final String DEFAULT_METADATA_SERVER_URL = "http://169.254.169.254"; |
| 66 | + |
| 67 | + // Note: the explicit `timeout` and `tries` below is a workaround. The underlying |
| 68 | + // issue is that resolving an unknown host on some networks will take |
| 69 | + // 20-30 seconds; making this timeout short fixes the issue, but |
| 70 | + // could lead to false negatives in the event that we are on GCE, but |
| 71 | + // the metadata resolution was particularly slow. The latter case is |
| 72 | + // "unlikely" since the expected 4-nines time is about 0.5 seconds. |
| 73 | + // This allows us to limit the total ping maximum timeout to 1.5 seconds |
| 74 | + // for developer desktop scenarios. |
| 75 | + static final int MAX_COMPUTE_PING_TRIES = 3; |
| 76 | + static final int COMPUTE_PING_CONNECTION_TIMEOUT_MS = 500; |
63 | 77 |
|
64 | 78 | private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. "; |
65 | 79 | private static final long serialVersionUID = -4113476462526554235L; |
@@ -92,7 +106,7 @@ public ComputeEngineCredentials(HttpTransportFactory transportFactory) { |
92 | 106 | */ |
93 | 107 | @Override |
94 | 108 | public AccessToken refreshAccessToken() throws IOException { |
95 | | - GenericUrl tokenUrl = new GenericUrl(TOKEN_SERVER_ENCODED_URL); |
| 109 | + GenericUrl tokenUrl = new GenericUrl(getTokenServerEncodedUrl()); |
96 | 110 | HttpRequest request = |
97 | 111 | transportFactory.create().createRequestFactory().buildGetRequest(tokenUrl); |
98 | 112 | JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY); |
@@ -133,27 +147,59 @@ public AccessToken refreshAccessToken() throws IOException { |
133 | 147 | return new AccessToken(accessToken, new Date(expiresAtMilliseconds)); |
134 | 148 | } |
135 | 149 |
|
136 | | - /** |
137 | | - * Return whether code is running on Google Compute Engine. |
138 | | - */ |
139 | | - static boolean runningOnComputeEngine(HttpTransportFactory transportFactory) { |
140 | | - try { |
141 | | - GenericUrl tokenUrl = new GenericUrl(METADATA_SERVER_URL); |
142 | | - HttpRequest request = |
143 | | - transportFactory.create().createRequestFactory().buildGetRequest(tokenUrl); |
144 | | - HttpResponse response = request.execute(); |
145 | | - // Internet providers can return a generic response to all requests, so it is necessary |
146 | | - // to check that metadata header is present also. |
147 | | - HttpHeaders headers = response.getHeaders(); |
148 | | - if (OAuth2Utils.headersContainValue(headers, "Metadata-Flavor", "Google")) { |
149 | | - return true; |
| 150 | + /** Return whether code is running on Google Compute Engine. */ |
| 151 | + static boolean runningOnComputeEngine( |
| 152 | + HttpTransportFactory transportFactory, DefaultCredentialsProvider provider) { |
| 153 | + // If the environment has requested that we do no GCE checks, return immediately. |
| 154 | + if (Boolean.parseBoolean(provider.getEnv(DefaultCredentialsProvider.NO_GCE_CHECK_ENV_VAR))) { |
| 155 | + return false; |
| 156 | + } |
| 157 | + |
| 158 | + GenericUrl tokenUrl = new GenericUrl(getMetadataServerUrl(provider)); |
| 159 | + for (int i = 1; i <= MAX_COMPUTE_PING_TRIES; ++i) { |
| 160 | + try { |
| 161 | + HttpRequest request = |
| 162 | + transportFactory.create().createRequestFactory().buildGetRequest(tokenUrl); |
| 163 | + request.setConnectTimeout(COMPUTE_PING_CONNECTION_TIMEOUT_MS); |
| 164 | + HttpResponse response = request.execute(); |
| 165 | + try { |
| 166 | + // Internet providers can return a generic response to all requests, so it is necessary |
| 167 | + // to check that metadata header is present also. |
| 168 | + HttpHeaders headers = response.getHeaders(); |
| 169 | + return OAuth2Utils.headersContainValue(headers, "Metadata-Flavor", "Google"); |
| 170 | + } finally { |
| 171 | + response.disconnect(); |
| 172 | + } |
| 173 | + } catch (SocketTimeoutException expected) { |
| 174 | + // Ignore logging timeouts which is the expected failure mode in non GCE environments. |
| 175 | + } catch (IOException e) { |
| 176 | + LOGGER.log( |
| 177 | + Level.WARNING, "Failed to detect whether we are running on Google Compute Engine.", e); |
150 | 178 | } |
151 | | - } catch (IOException expected) { |
152 | | - // ignore |
153 | 179 | } |
154 | 180 | return false; |
155 | 181 | } |
156 | 182 |
|
| 183 | + public static String getMetadataServerUrl(DefaultCredentialsProvider provider) { |
| 184 | + String metadataServerAddress = provider.getEnv(DefaultCredentialsProvider.GCE_METADATA_HOST_ENV_VAR); |
| 185 | + if (metadataServerAddress != null) { |
| 186 | + return "http://" + metadataServerAddress; |
| 187 | + } |
| 188 | + return DEFAULT_METADATA_SERVER_URL; |
| 189 | + } |
| 190 | + |
| 191 | + public static String getMetadataServerUrl() { |
| 192 | + return getMetadataServerUrl(DefaultCredentialsProvider.DEFAULT); |
| 193 | + } |
| 194 | + |
| 195 | + public static String getTokenServerEncodedUrl(DefaultCredentialsProvider provider) { |
| 196 | + return getMetadataServerUrl(provider) + "/computeMetadata/v1/instance/service-accounts/default/token"; |
| 197 | + } |
| 198 | + |
| 199 | + public static String getTokenServerEncodedUrl() { |
| 200 | + return getTokenServerEncodedUrl(DefaultCredentialsProvider.DEFAULT); |
| 201 | + } |
| 202 | + |
157 | 203 | @Override |
158 | 204 | public int hashCode() { |
159 | 205 | return Objects.hash(transportFactoryClassName); |
|
0 commit comments