Skip to content

Commit 40f19cc

Browse files
fix: do not use the GAE APIs on gen2+ runtimes (#807)
* fix: do not use the GAE APIs on gen2+ runtimes Currently, this library uses the App Engine API in all environments if it can be imported successfully. This assumption made sense when the API was only available on gen1, but this is no longer the case. See https://github.com/GoogleCloudPlatform/appengine-python-standard In order to comply with AIP-4115, we must treat GAE gen2+ as a "compute engine equivalent environment" even if the GAE APIs are importable. In other words, google.auth.default() must never return an app_engine.Credental on GAE gen2+.Currently, this library uses the App Engine API in all environments if it can be imported successfully. This assumption made sense when the API was only available on gen1, but this is no longer the case. See https://github.com/GoogleCloudPlatform/appengine-python-standard In order to comply with AIP-4115, we must treat GAE gen2+ as a "compute engine equivalent environment" even if the GAE APIs are importable. In other words, google.auth.default() should not return an app_engine.Credental on GAE gen2+. * blacken Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com>
1 parent bdc4d52 commit 40f19cc

File tree

5 files changed

+119
-8
lines changed

5 files changed

+119
-8
lines changed

packages/google-auth/google/auth/_default.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,11 @@ def _get_explicit_environ_credentials():
230230

231231
def _get_gae_credentials():
232232
"""Gets Google App Engine App Identity credentials and project ID."""
233+
# If not GAE gen1, prefer the metadata service even if the GAE APIs are
234+
# available as per https://google.aip.dev/auth/4115.
235+
if os.environ.get(environment_vars.LEGACY_APPENGINE_RUNTIME) != "python27":
236+
return None, None
237+
233238
# While this library is normally bundled with app_engine, there are
234239
# some cases where it's not available, so we tolerate ImportError.
235240
try:

packages/google-auth/google/auth/environment_vars.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@
6060
The default value is false. Users have to explicitly set this value to true
6161
in order to use client certificate to establish a mutual TLS channel."""
6262

63+
LEGACY_APPENGINE_RUNTIME = "APPENGINE_RUNTIME"
64+
"""Gen1 environment variable defining the App Engine Runtime.
65+
66+
Used to distinguish between GAE gen1 and GAE gen2+.
67+
"""
68+
6369
# AWS environment variables used with AWS workload identity pools to retrieve
6470
# AWS security credentials and the AWS region needed to create a serialized
6571
# signed requests to the AWS STS GetCalledIdentity API that can be exchanged

packages/google-auth/tests/test__default.py

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,9 @@ def app_identity(monkeypatch):
447447
yield app_identity_module
448448

449449

450-
def test__get_gae_credentials(app_identity):
450+
@mock.patch.dict(os.environ)
451+
def test__get_gae_credentials_gen1(app_identity):
452+
os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python27"
451453
app_identity.get_application_id.return_value = mock.sentinel.project
452454

453455
credentials, project_id = _default._get_gae_credentials()
@@ -456,18 +458,65 @@ def test__get_gae_credentials(app_identity):
456458
assert project_id == mock.sentinel.project
457459

458460

461+
@mock.patch.dict(os.environ)
462+
def test__get_gae_credentials_gen2():
463+
os.environ["GAE_RUNTIME"] = "python37"
464+
credentials, project_id = _default._get_gae_credentials()
465+
assert credentials is None
466+
assert project_id is None
467+
468+
469+
@mock.patch.dict(os.environ)
470+
def test__get_gae_credentials_gen2_backwards_compat():
471+
# compat helpers may copy GAE_RUNTIME to APPENGINE_RUNTIME
472+
# for backwards compatibility with code that relies on it
473+
os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python37"
474+
os.environ["GAE_RUNTIME"] = "python37"
475+
credentials, project_id = _default._get_gae_credentials()
476+
assert credentials is None
477+
assert project_id is None
478+
479+
480+
def test__get_gae_credentials_env_unset():
481+
assert environment_vars.LEGACY_APPENGINE_RUNTIME not in os.environ
482+
assert "GAE_RUNTIME" not in os.environ
483+
credentials, project_id = _default._get_gae_credentials()
484+
assert credentials is None
485+
assert project_id is None
486+
487+
488+
@mock.patch.dict(os.environ)
459489
def test__get_gae_credentials_no_app_engine():
490+
# test both with and without LEGACY_APPENGINE_RUNTIME setting
491+
assert environment_vars.LEGACY_APPENGINE_RUNTIME not in os.environ
492+
460493
import sys
461494

462-
with mock.patch.dict("sys.modules"):
463-
sys.modules["google.auth.app_engine"] = None
495+
with mock.patch.dict(sys.modules, {"google.auth.app_engine": None}):
496+
credentials, project_id = _default._get_gae_credentials()
497+
assert credentials is None
498+
assert project_id is None
499+
500+
os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python27"
464501
credentials, project_id = _default._get_gae_credentials()
465502
assert credentials is None
466503
assert project_id is None
467504

468505

506+
@mock.patch.dict(os.environ)
507+
@mock.patch.object(app_engine, "app_identity", new=None)
469508
def test__get_gae_credentials_no_apis():
470-
assert _default._get_gae_credentials() == (None, None)
509+
# test both with and without LEGACY_APPENGINE_RUNTIME setting
510+
assert environment_vars.LEGACY_APPENGINE_RUNTIME not in os.environ
511+
512+
credentials, project_id = _default._get_gae_credentials()
513+
assert credentials is None
514+
assert project_id is None
515+
516+
os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python27"
517+
credentials, project_id = _default._get_gae_credentials()
518+
assert credentials is None
519+
assert project_id is None
471520

472521

473522
@mock.patch(

packages/google-auth/tests/test_app_engine.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ def test_get_project_id(app_identity):
5252
assert app_engine.get_project_id() == mock.sentinel.project
5353

5454

55+
@mock.patch.object(app_engine, "app_identity", new=None)
5556
def test_get_project_id_missing_apis():
5657
with pytest.raises(EnvironmentError) as excinfo:
5758
assert app_engine.get_project_id()
@@ -86,6 +87,7 @@ def test_sign(self, app_identity):
8687

8788

8889
class TestCredentials(object):
90+
@mock.patch.object(app_engine, "app_identity", new=None)
8991
def test_missing_apis(self):
9092
with pytest.raises(EnvironmentError) as excinfo:
9193
app_engine.Credentials()

packages/google-auth/tests_async/test__default_async.py

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,9 @@ def app_identity(monkeypatch):
284284
yield app_identity_module
285285

286286

287-
def test__get_gae_credentials(app_identity):
287+
@mock.patch.dict(os.environ)
288+
def test__get_gae_credentials_gen1(app_identity):
289+
os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python27"
288290
app_identity.get_application_id.return_value = mock.sentinel.project
289291

290292
credentials, project_id = _default._get_gae_credentials()
@@ -293,18 +295,65 @@ def test__get_gae_credentials(app_identity):
293295
assert project_id == mock.sentinel.project
294296

295297

298+
@mock.patch.dict(os.environ)
299+
def test__get_gae_credentials_gen2():
300+
os.environ["GAE_RUNTIME"] = "python37"
301+
credentials, project_id = _default._get_gae_credentials()
302+
assert credentials is None
303+
assert project_id is None
304+
305+
306+
@mock.patch.dict(os.environ)
307+
def test__get_gae_credentials_gen2_backwards_compat():
308+
# compat helpers may copy GAE_RUNTIME to APPENGINE_RUNTIME
309+
# for backwards compatibility with code that relies on it
310+
os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python37"
311+
os.environ["GAE_RUNTIME"] = "python37"
312+
credentials, project_id = _default._get_gae_credentials()
313+
assert credentials is None
314+
assert project_id is None
315+
316+
317+
def test__get_gae_credentials_env_unset():
318+
assert environment_vars.LEGACY_APPENGINE_RUNTIME not in os.environ
319+
assert "GAE_RUNTIME" not in os.environ
320+
credentials, project_id = _default._get_gae_credentials()
321+
assert credentials is None
322+
assert project_id is None
323+
324+
325+
@mock.patch.dict(os.environ)
296326
def test__get_gae_credentials_no_app_engine():
327+
# test both with and without LEGACY_APPENGINE_RUNTIME setting
328+
assert environment_vars.LEGACY_APPENGINE_RUNTIME not in os.environ
329+
297330
import sys
298331

299-
with mock.patch.dict("sys.modules"):
300-
sys.modules["google.auth.app_engine"] = None
332+
with mock.patch.dict(sys.modules, {"google.auth.app_engine": None}):
333+
credentials, project_id = _default._get_gae_credentials()
334+
assert credentials is None
335+
assert project_id is None
336+
337+
os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python27"
301338
credentials, project_id = _default._get_gae_credentials()
302339
assert credentials is None
303340
assert project_id is None
304341

305342

343+
@mock.patch.dict(os.environ)
344+
@mock.patch.object(app_engine, "app_identity", new=None)
306345
def test__get_gae_credentials_no_apis():
307-
assert _default._get_gae_credentials() == (None, None)
346+
# test both with and without LEGACY_APPENGINE_RUNTIME setting
347+
assert environment_vars.LEGACY_APPENGINE_RUNTIME not in os.environ
348+
349+
credentials, project_id = _default._get_gae_credentials()
350+
assert credentials is None
351+
assert project_id is None
352+
353+
os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python27"
354+
credentials, project_id = _default._get_gae_credentials()
355+
assert credentials is None
356+
assert project_id is None
308357

309358

310359
@mock.patch(

0 commit comments

Comments
 (0)