diff --git a/.vpython3 b/.vpython3 index ffff0935a..3426ec4bf 100644 --- a/.vpython3 +++ b/.vpython3 @@ -65,3 +65,33 @@ wheel: < name: "infra/python/wheels/certifi-py2_py3" version: "version:2021.5.30" > + +# Used by: +# autoninja.py +wheel: < + name: "infra/python/wheels/google-auth-py3" + version: "version:2.16.2" +> +wheel: < + name: "infra/python/wheels/cachetools-py3" + version: "version:4.2.2" +> +wheel: < + name: "infra/python/wheels/pyasn1_modules-py2_py3" + version: "version:0.2.8" +> +wheel: < + name: "infra/python/wheels/rsa-py3" + version: "version:4.7.2" +> +wheel: < + name: "infra/python/wheels/pyasn1-py2_py3" + version: "version:0.4.8" +> + +# Used by: +# tests/autoninja_test.py +wheel: < + name: "infra/python/wheels/parameterized-py2_py3" + version: "version:0.8.1" +> \ No newline at end of file diff --git a/autoninja b/autoninja index 4ee2421f8..fb7d02661 100755 --- a/autoninja +++ b/autoninja @@ -20,7 +20,7 @@ fi # Execute whatever is printed by autoninja.py. # Also print it to reassure that the right settings are being used. -python3 "$(dirname -- "$0")/autoninja.py" "$@" +vpython3 "$(dirname -- "$0")/autoninja.py" "$@" retval=$? if [ "$retval" == "0" ] && [ "$NINJA_SUMMARIZE_BUILD" == "1" ]; then diff --git a/autoninja.bat b/autoninja.bat index 03fba6476..dcfff54dc 100755 --- a/autoninja.bat +++ b/autoninja.bat @@ -30,7 +30,7 @@ if "%NINJA_SUMMARIZE_BUILD%" == "1" set "NINJA_STATUS=[%%r processes, %%f/%%t @ :: should be consistent between autoninja.bat and the autoninja script used by :: git bash. -@call %scriptdir%python-bin\python3.bat %scriptdir%autoninja.py "%%*" +@call %scriptdir%\vpython3.bat %scriptdir%autoninja.py "%%*" @if errorlevel 1 goto buildfailure :: Use call to invoke python script here, because we use python via python3.bat. diff --git a/autoninja.py b/autoninja.py index 417fa977e..79d403c39 100755 --- a/autoninja.py +++ b/autoninja.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env vpython3 # Copyright (c) 2017 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -14,13 +14,19 @@ does handle import statements, but it can't handle conditional setting of build settings. """ +import json import multiprocessing import os import platform import re import shlex +import shutil import subprocess import sys +import warnings + +import google.auth +from google.auth.transport.requests import AuthorizedSession import autosiso import ninja @@ -43,6 +49,62 @@ _UNSAFE_FOR_CMD = set("^<>&|()%") _ALL_META_CHARS = _UNSAFE_FOR_CMD.union(set('"')) +def _adc_account(): + """Returns account used to authenticate with GCP application default credentials.""" + + try: + # Suppress warnings from google.auth.default. + # https://github.com/googleapis/google-auth-library-python/issues/271 + warnings.filterwarnings( + "ignore", + "Your application has authenticated using end user credentials from" + " Google Cloud SDK without a quota project.", + ) + credentials, _ = google.auth.default( + scopes=["https://www.googleapis.com/auth/userinfo.email"]) + except google.auth.exceptions.DefaultCredentialsError: + # Application Default Crendetials is not configured. + return None + finally: + warnings.resetwarnings() + + with AuthorizedSession(credentials) as session: + response = session.get("https://www.googleapis.com/oauth2/v1/userinfo") + return response.json().get("email") + + +def _gcloud_auth_account(): + """Returns active account authenticated with `gcloud auth login`.""" + if shutil.which("gcloud") is None: + return None + + accounts = json.loads( + subprocess.check_output("gcloud auth list --format=json", + shell=True, + text=True)) + for account in accounts: + if account["status"] == "ACTIVE": + return account["account"] + return None + + +def _is_google_corp_machine(): + """This assumes that corp machine has gcert binary in known location.""" + return shutil.which("gcert") is not None + + +def _is_google_corp_machine_using_external_account(): + if not _is_google_corp_machine(): + return False + + account = _adc_account() + if account and not account.endswith("@google.com"): + return True + + account = _gcloud_auth_account() + return account and not account.endswith("@google.com") + + def _quote_for_cmd(arg): # First, escape the arg so that CommandLineToArgvW will parse it properly. if arg == "" or " " in arg or '"' in arg: @@ -209,6 +271,18 @@ def main(args): use_goma = True break + if use_remoteexec or use_siso: + if _is_google_corp_machine_using_external_account(): + print( + "You can't use a non-@google.com account (%s and/or %s) on a" + " corp machine.\n" + "Please login via `gcloud auth login --update-adc` with your" + " @google.com account instead.\n" % + (_adc_account(), _gcloud_auth_account()), + file=sys.stderr, + ) + return 1 + # Strip -o/--offline so ninja doesn't see them. input_args = [arg for arg in input_args if arg not in ("-o", "--offline")] diff --git a/tests/autoninja_test.py b/tests/autoninja_test.py index 8bf6eac7f..5a1719045 100755 --- a/tests/autoninja_test.py +++ b/tests/autoninja_test.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env vpython3 # Copyright (c) 2022 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -12,6 +12,8 @@ import unittest import contextlib from unittest import mock +from parameterized import parameterized + ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, ROOT_DIR) @@ -152,6 +154,29 @@ class AutoninjaTest(trial_dir.TestCase): self.assertIn('-C', args) self.assertEqual(args[args.index('-C') + 1], out_dir) + @parameterized.expand([ + ("non corp machine", False, None, None, False), + ("non corp adc account", True, "foo@chromium.org", None, True), + ("corp adc account", True, "foo@google.com", None, False), + ("non corp gcloud auth account", True, None, "foo@chromium.org", True), + ("corp gcloud auth account", True, None, "foo@google.com", False), + ]) + def test_is_corp_machine_using_external_account(self, _, is_corp, + adc_account, + gcloud_auth_account, + expected): + with mock.patch('autoninja._is_google_corp_machine', + return_value=is_corp), mock.patch( + 'autoninja._adc_account', + return_value=adc_account), mock.patch( + 'autoninja._gcloud_auth_account', + return_value=gcloud_auth_account): + self.assertEqual( + bool( + # pylint: disable=line-too-long + autoninja._is_google_corp_machine_using_external_account()), + expected) + def test_gn_lines(self): out_dir = os.path.join('out', 'dir') # Make sure nested import directives work. This is based on the