• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2015, Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *    * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *    * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *
15  *    * Neither the name of Google Inc. nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 package com.google.auth.oauth2;
33 
34 import com.google.auth.http.HttpTransportFactory;
35 import java.io.File;
36 import java.io.FileInputStream;
37 import java.io.FileNotFoundException;
38 import java.io.IOException;
39 import java.io.InputStream;
40 import java.lang.reflect.Field;
41 import java.lang.reflect.InvocationTargetException;
42 import java.lang.reflect.Method;
43 import java.security.AccessControlException;
44 import java.util.Collections;
45 import java.util.Locale;
46 import java.util.logging.Level;
47 import java.util.logging.Logger;
48 
49 /**
50  * Provides the Application Default Credential from the environment.
51  *
52  * <p>An instance represents the per-process state used to get and cache the credential and allows
53  * overriding the state and environment for testing purposes.
54  */
55 class DefaultCredentialsProvider {
56   static final DefaultCredentialsProvider DEFAULT = new DefaultCredentialsProvider();
57   static final String CREDENTIAL_ENV_VAR = "GOOGLE_APPLICATION_CREDENTIALS";
58   static final String QUOTA_PROJECT_ENV_VAR = "GOOGLE_CLOUD_QUOTA_PROJECT";
59 
60   static final String WELL_KNOWN_CREDENTIALS_FILE = "application_default_credentials.json";
61   static final String CLOUDSDK_CONFIG_DIRECTORY = "gcloud";
62   static final String APP_ENGINE_SIGNAL_CLASS = "com.google.appengine.api.utils.SystemProperty";
63   static final String CLOUD_SHELL_ENV_VAR = "DEVSHELL_CLIENT_PORT";
64   static final String SKIP_APP_ENGINE_ENV_VAR = "GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE";
65   static final String SPECIFICATION_VERSION = System.getProperty("java.specification.version");
66   static final String GAE_RUNTIME_VERSION =
67       System.getProperty("com.google.appengine.runtime.version");
68   static final String RUNTIME_JETTY_LOGGER = System.getProperty("org.eclipse.jetty.util.log.class");
69   static final Logger LOGGER = Logger.getLogger(DefaultCredentialsProvider.class.getName());
70   static final String NO_GCE_CHECK_ENV_VAR = "NO_GCE_CHECK";
71   static final String GCE_METADATA_HOST_ENV_VAR = "GCE_METADATA_HOST";
72   static final String CLOUDSDK_CLIENT_ID =
73       "764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com";
74   static final String CLOUDSDK_CREDENTIALS_WARNING =
75       "You are authenticating using user credentials. "
76           + "For production, we recommend using service account credentials.\n\n"
77           + "To learn more about service account credentials, see "
78           + "http://cloud.google.com/docs/authentication/external/set-up-adc-on-cloud";
79 
80   static final String CLOUDSDK_MISSING_CREDENTIALS =
81       "Your default credentials were not found. To set up Application Default Credentials "
82           + "for your environment, see "
83           + "https://cloud.google.com/docs/authentication/external/set-up-adc.";
84   public static final String SUPPRESS_GCLOUD_CREDS_WARNING_ENV_VAR =
85       "SUPPRESS_GCLOUD_CREDS_WARNING";
86 
87   // These variables should only be accessed inside a synchronized block
88   private GoogleCredentials cachedCredentials = null;
89   private boolean checkedAppEngine = false;
90   private boolean checkedComputeEngine = false;
91 
DefaultCredentialsProvider()92   DefaultCredentialsProvider() {}
93 
94   /**
95    * Returns the Application Default Credentials.
96    *
97    * <p>Returns the Application Default Credentials which are used to identify and authorize the
98    * whole application. The following are searched (in order) to find the Application Default
99    * Credentials:
100    *
101    * <ol>
102    *   <li>Credentials file pointed to by the {@code GOOGLE_APPLICATION_CREDENTIALS} environment
103    *       variable
104    *   <li>Credentials provided by the Google Cloud SDK {@code gcloud auth application-default
105    *       login} command
106    *   <li>Google App Engine built-in credentials
107    *   <li>Google Cloud Shell built-in credentials
108    *   <li>Google Compute Engine built-in credentials
109    * </ol>
110    *
111    * @param transportFactory HTTP transport factory, creates the transport used to get access
112    *     tokens.
113    * @return the credentials instance.
114    * @throws IOException if the credentials cannot be created in the current environment.
115    */
getDefaultCredentials(HttpTransportFactory transportFactory)116   final GoogleCredentials getDefaultCredentials(HttpTransportFactory transportFactory)
117       throws IOException {
118     synchronized (this) {
119       if (cachedCredentials == null) {
120         cachedCredentials = getDefaultCredentialsUnsynchronized(transportFactory);
121       }
122       if (cachedCredentials != null) {
123         return cachedCredentials;
124       }
125     }
126 
127     throw new IOException(CLOUDSDK_MISSING_CREDENTIALS);
128   }
129 
getDefaultCredentialsUnsynchronized( HttpTransportFactory transportFactory)130   private final GoogleCredentials getDefaultCredentialsUnsynchronized(
131       HttpTransportFactory transportFactory) throws IOException {
132 
133     // First try the environment variable
134     GoogleCredentials credentials = null;
135     String credentialsPath = getEnv(CREDENTIAL_ENV_VAR);
136     if (credentialsPath != null && credentialsPath.length() > 0) {
137       LOGGER.log(
138           Level.FINE,
139           String.format("Attempting to load credentials from file: %s", credentialsPath));
140       InputStream credentialsStream = null;
141       try {
142         File credentialsFile = new File(credentialsPath);
143         if (!isFile(credentialsFile)) {
144           // Path will be put in the message from the catch block below
145           throw new IOException("File does not exist.");
146         }
147         credentialsStream = readStream(credentialsFile);
148         credentials = GoogleCredentials.fromStream(credentialsStream, transportFactory);
149       } catch (IOException e) {
150         // Although it is also the cause, the message of the caught exception can have very
151         // important information for diagnosing errors, so include its message in the
152         // outer exception message also.
153         throw new IOException(
154             String.format(
155                 "Error reading credential file from environment variable %s, value '%s': %s",
156                 CREDENTIAL_ENV_VAR, credentialsPath, e.getMessage()),
157             e);
158       } catch (AccessControlException expected) {
159         // Exception querying file system is expected on App-Engine
160       } finally {
161         if (credentialsStream != null) {
162           credentialsStream.close();
163         }
164       }
165     }
166 
167     // Then try the well-known file
168     if (credentials == null) {
169       File wellKnownFileLocation = getWellKnownCredentialsFile();
170       InputStream credentialsStream = null;
171       try {
172         if (isFile(wellKnownFileLocation)) {
173           LOGGER.log(
174               Level.FINE,
175               String.format(
176                   "Attempting to load credentials from well known file: %s",
177                   wellKnownFileLocation.getCanonicalPath()));
178           credentialsStream = readStream(wellKnownFileLocation);
179           credentials = GoogleCredentials.fromStream(credentialsStream, transportFactory);
180         }
181       } catch (IOException e) {
182         throw new IOException(
183             String.format(
184                 "Error reading credential file from location %s: %s",
185                 wellKnownFileLocation, e.getMessage()));
186       } catch (AccessControlException expected) {
187         // Exception querying file system is expected on App-Engine
188       } finally {
189         if (credentialsStream != null) {
190           credentialsStream.close();
191         }
192       }
193       warnAboutProblematicCredentials(credentials);
194     }
195 
196     // Then try GAE 7 standard environment
197     if (credentials == null && isOnGAEStandard7() && !skipAppEngineCredentialsCheck()) {
198       LOGGER.log(Level.FINE, "Attempting to load credentials from GAE 7 Standard");
199       credentials = tryGetAppEngineCredential();
200     }
201 
202     // Then try Cloud Shell.  This must be done BEFORE checking
203     // Compute Engine, as Cloud Shell runs on GCE VMs.
204     if (credentials == null) {
205       LOGGER.log(Level.FINE, "Attempting to load credentials from Cloud Shell");
206       credentials = tryGetCloudShellCredentials();
207     }
208 
209     // Then try Compute Engine and GAE 8 standard environment
210     if (credentials == null) {
211       LOGGER.log(Level.FINE, "Attempting to load credentials from GCE");
212       credentials = tryGetComputeCredentials(transportFactory);
213     }
214 
215     if (credentials != null) {
216       String quotaFromEnv = getEnv(QUOTA_PROJECT_ENV_VAR);
217 
218       if (quotaFromEnv != null && quotaFromEnv.trim().length() > 0) {
219         credentials = credentials.createWithQuotaProject(quotaFromEnv);
220       }
221     }
222 
223     return credentials;
224   }
225 
getWellKnownCredentialsFile()226   private final File getWellKnownCredentialsFile() {
227     return GoogleAuthUtils.getWellKnownCredentialsFile(this);
228   }
229 
warnAboutProblematicCredentials(GoogleCredentials credentials)230   private void warnAboutProblematicCredentials(GoogleCredentials credentials) {
231     if (credentials instanceof UserCredentials
232         && !Boolean.parseBoolean(getEnv(SUPPRESS_GCLOUD_CREDS_WARNING_ENV_VAR))
233         && ComputeEngineCredentials.checkStaticGceDetection(this)) {
234       LOGGER.log(Level.WARNING, CLOUDSDK_CREDENTIALS_WARNING);
235     }
236   }
237 
runningOnAppEngine()238   private boolean runningOnAppEngine() {
239     Class<?> systemPropertyClass = null;
240     try {
241       systemPropertyClass = forName(APP_ENGINE_SIGNAL_CLASS);
242     } catch (ClassNotFoundException expected) {
243       // SystemProperty will always be present on App Engine.
244       return false;
245     }
246     Exception cause;
247     Field environmentField;
248     try {
249       environmentField = systemPropertyClass.getField("environment");
250       Object environmentValue = environmentField.get(null);
251       Class<?> environmentType = environmentField.getType();
252       Method valueMethod = environmentType.getMethod("value");
253       Object environmentValueValue = valueMethod.invoke(environmentValue);
254       return (environmentValueValue != null);
255     } catch (NoSuchFieldException
256         | SecurityException
257         | IllegalArgumentException
258         | IllegalAccessException
259         | NoSuchMethodException
260         | InvocationTargetException exception) {
261       cause = exception;
262     }
263     throw new RuntimeException(
264         String.format(
265             "Unexpected error trying to determine if runnning on Google App Engine: %s",
266             cause.getMessage()),
267         cause);
268   }
269 
tryGetCloudShellCredentials()270   private GoogleCredentials tryGetCloudShellCredentials() {
271     String port = getEnv(CLOUD_SHELL_ENV_VAR);
272     if (port != null) {
273       return CloudShellCredentials.create(Integer.parseInt(port));
274     } else {
275       return null;
276     }
277   }
278 
tryGetAppEngineCredential()279   private GoogleCredentials tryGetAppEngineCredential() throws IOException {
280     // Checking for App Engine requires a class load, so check only once
281     if (checkedAppEngine) {
282       return null;
283     }
284     boolean onAppEngine = runningOnAppEngine();
285     checkedAppEngine = true;
286     if (!onAppEngine) {
287       return null;
288     }
289     return new AppEngineCredentials(
290         Collections.<String>emptyList(), Collections.<String>emptyList());
291   }
292 
tryGetComputeCredentials(HttpTransportFactory transportFactory)293   private final GoogleCredentials tryGetComputeCredentials(HttpTransportFactory transportFactory) {
294     // Checking compute engine requires a round-trip, so check only once
295     if (checkedComputeEngine) {
296       return null;
297     }
298     boolean runningOnComputeEngine = ComputeEngineCredentials.isOnGce(transportFactory, this);
299     checkedComputeEngine = true;
300     if (runningOnComputeEngine) {
301       return ComputeEngineCredentials.newBuilder()
302           .setHttpTransportFactory(transportFactory)
303           .build();
304     }
305     return null;
306   }
307 
308   // Skip app engine check if environment variable
309   // GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE = 1 or true
skipAppEngineCredentialsCheck()310   private boolean skipAppEngineCredentialsCheck() {
311     boolean skip = false; // do not skip by default
312     String value = getEnv(SKIP_APP_ENGINE_ENV_VAR);
313     if (value != null) {
314       skip = value.equalsIgnoreCase("true") || value.equals("1");
315     }
316     return skip;
317   }
318 
isOnGAEStandard7()319   protected boolean isOnGAEStandard7() {
320     return GAE_RUNTIME_VERSION != null
321         && (SPECIFICATION_VERSION.equals("1.7") || RUNTIME_JETTY_LOGGER == null);
322   }
323 
getOsName()324   String getOsName() {
325     return getProperty("os.name", "").toLowerCase(Locale.US);
326   }
327 
328   /*
329    * Start of methods to allow overriding in the test code to isolate from the environment.
330    */
331 
forName(String className)332   Class<?> forName(String className) throws ClassNotFoundException {
333     return Class.forName(className);
334   }
335 
getEnv(String name)336   String getEnv(String name) {
337     return System.getenv(name);
338   }
339 
getProperty(String property, String def)340   String getProperty(String property, String def) {
341     return System.getProperty(property, def);
342   }
343 
isFile(File file)344   boolean isFile(File file) {
345     return file.isFile();
346   }
347 
readStream(File file)348   InputStream readStream(File file) throws FileNotFoundException {
349     return new FileInputStream(file);
350   }
351 
352   /*
353    * End of methods to allow overriding in the test code to isolate from the environment.
354    */
355 }
356