Browse Source

autoninja: check RBE project, not account

Account check would become too slow.
We'll check RBE project to use instead.

On corp machine, our policy to use @google.com account
and rbe-chrome-untrusted to build chromium/chrome.
We don't allow rbe-chromium-untrusted with @chromium.org
on corp machine.

On non-corp machine, user couldn't use rbe-chrome-untrusted
because it's @google.com only, and corp security policy
doesn't allow @google.com account on non-corp machine.

Bug: b/364318216
Change-Id: I0f3a19e105b050aef6a62e1b25b45b1722382a34
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/5848450
Reviewed-by: Scott Lee <ddoman@chromium.org>
Reviewed-by: Michael Savigny <msavigny@google.com>
Commit-Queue: Fumitoshi Ukai <ukai@google.com>
Reviewed-by: Junji Watanabe <jwata@google.com>
Reviewed-by: Philipp Wollermann <philwo@google.com>
Fumitoshi Ukai 11 months ago
parent
commit
61fad561d6
6 changed files with 95 additions and 183 deletions
  1. 0 3
      .gitignore
  2. 0 23
      .vpython3
  3. 1 1
      autoninja
  4. 2 2
      autoninja.bat
  5. 84 119
      autoninja.py
  6. 8 35
      tests/autoninja_test.py

+ 0 - 3
.gitignore

@@ -96,6 +96,3 @@ testing_support/google_appengine
 
 
 # Ignore the file that logs Python 2 scripts run during presubmits.
 # Ignore the file that logs Python 2 scripts run during presubmits.
 /python2_usage.txt
 /python2_usage.txt
-
-# Ignore the internal data used by autoninja.
-/.autoninja*

+ 0 - 23
.vpython3

@@ -75,29 +75,6 @@ wheel: <
   version: "version:2021.5.30"
   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:
 # Used by:
 #   tests/autoninja_test.py
 #   tests/autoninja_test.py
 wheel: <
 wheel: <

+ 1 - 1
autoninja

@@ -16,7 +16,7 @@ fi
 
 
 # Execute whatever is printed by autoninja.py.
 # Execute whatever is printed by autoninja.py.
 # Also print it to reassure that the right settings are being used.
 # Also print it to reassure that the right settings are being used.
-vpython3 "$(dirname -- "$0")/autoninja.py" "$@"
+python3 "$(dirname -- "$0")/autoninja.py" "$@"
 retval=$?
 retval=$?
 
 
 if [ "$retval" == "0" ] && [ "$NINJA_SUMMARIZE_BUILD" == "1" ]; then
 if [ "$retval" == "0" ] && [ "$NINJA_SUMMARIZE_BUILD" == "1" ]; then

+ 2 - 2
autoninja.bat

@@ -9,7 +9,7 @@ set scriptdir=%~dp0
 
 
 if "%*" == "/?" (
 if "%*" == "/?" (
   rem Handle "autoninja /?" which will otherwise give help on the "call" command
   rem Handle "autoninja /?" which will otherwise give help on the "call" command
-  @call python3.bat %~dp0\ninja.py --help
+  @call %scriptdir%python-bin\python3.bat %~dp0\ninja.py --help
   exit /b
   exit /b
 )
 )
 
 
@@ -20,7 +20,7 @@ if "%*" == "/?" (
 if "%NINJA_SUMMARIZE_BUILD%" == "1" set "NINJA_STATUS=[%%r processes, %%f/%%t @ %%o/s : %%es ] "
 if "%NINJA_SUMMARIZE_BUILD%" == "1" set "NINJA_STATUS=[%%r processes, %%f/%%t @ %%o/s : %%es ] "
 
 
 :: Execute autoninja.py and pass all arguments to it.
 :: Execute autoninja.py and pass all arguments to it.
-@call %scriptdir%\vpython3.bat %scriptdir%autoninja.py "%%*"
+@call %scriptdir%python-bin\python3.bat %scriptdir%autoninja.py "%%*"
 @if errorlevel 1 goto buildfailure
 @if errorlevel 1 goto buildfailure
 
 
 :: Use call to invoke python script here, because we use python via python3.bat.
 :: Use call to invoke python script here, because we use python via python3.bat.

+ 84 - 119
autoninja.py

@@ -1,4 +1,4 @@
-#!/usr/bin/env vpython3
+#!/usr/bin/env python3
 # Copyright (c) 2017 The Chromium Authors. All rights reserved.
 # Copyright (c) 2017 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 # found in the LICENSE file.
@@ -16,12 +16,10 @@ settings.
 
 
 import uuid
 import uuid
 import logging
 import logging
-import json
 import multiprocessing
 import multiprocessing
 import os
 import os
 import platform
 import platform
 import re
 import re
-import shelve
 import shlex
 import shlex
 import shutil
 import shutil
 import subprocess
 import subprocess
@@ -29,10 +27,8 @@ import sys
 import time
 import time
 import warnings
 import warnings
 
 
-import google.auth
-from google.auth.transport.requests import AuthorizedSession
-
 import build_telemetry
 import build_telemetry
+import gclient_paths
 import gclient_utils
 import gclient_utils
 import gn_helper
 import gn_helper
 import ninja
 import ninja
@@ -57,114 +53,46 @@ _UNSAFE_FOR_CMD = set("^<>&|()%")
 _ALL_META_CHARS = _UNSAFE_FOR_CMD.union(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:
-        try:
-            response = session.get(
-                "https://www.googleapis.com/oauth2/v1/userinfo")
-        except Exception:
-            # Ignore exception.
-            return None
-
-    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 _luci_auth_account():
-    """Returns active account authenticated with `luci-auth login -scopes-context`."""
-    if shutil.which("luci-auth") is None:
-        return None
-
-    # First line returned should be "Logged in as account@domain.com."
-    # Extract the account@domain.com from that line.
-    try:
-        info = subprocess.check_output("luci-auth info -scopes-context",
-                                       shell=True,
-                                       stderr=subprocess.STDOUT,
-                                       text=True).split('\n')[0]
-        if info.startswith("Logged in as "):
-            return info[len("Logged in as "):-1]
-    except subprocess.CalledProcessError:
-        return None
-    return None
-
-
 def _is_google_corp_machine():
 def _is_google_corp_machine():
     """This assumes that corp machine has gcert binary in known location."""
     """This assumes that corp machine has gcert binary in known location."""
     return shutil.which("gcert") is not None
     return shutil.which("gcert") is not None
 
 
 
 
-def _is_google_corp_machine_using_external_account():
-    if os.environ.get("AUTONINJA_SKIP_EXTERNAL_ACCOUNT_CHECK") == "1":
-        print(
-            "WARNING: AUTONINJA_SKIP_EXTERNAL_ACCOUNT_CHECK env var is set.\n"
-            "This is only for some infra, do not set this in personal"
-            " development machine.",
-            file=sys.stderr)
-        return False
-
-    if not _is_google_corp_machine():
-        return False
-
-    with shelve.open(os.path.join(_SCRIPT_DIR, ".autoninja")) as db:
-        last_false = db.get("last_false")
-        now = time.time()
-        if last_false is not None and now < last_false + 12 * 60 * 60:
-            # Do not check account if it is checked in last 12 hours.
-            return False
-
-        account = _adc_account()
-        if account and not account.endswith("@google.com"):
-            return True
-
-        account = _luci_auth_account()
-        if account and not account.endswith("@google.com"):
-            return True
-
-        account = _gcloud_auth_account()
-        if not account:
-            db["last_false"] = now
-            return False
-
-        # Handle service account and google account as internal account.
-        if not (account.endswith("@google.com")
-                or account.endswith("gserviceaccount.com")):
-            return True
-
-        db["last_false"] = now
-        return False
+def _reclient_rbe_project():
+    """Returns RBE project used by reclient."""
+    instance = os.environ.get('RBE_instance')
+    if instance:
+        m = re.match(instance, 'projects/([^/]*)/instances/.*')
+        if m:
+            return m[1]
+    reproxy_cfg_path = reclient_helper.find_reclient_cfg()
+    if not reproxy_cfg_path:
+        return ""
+    with open(reproxy_cfg_path) as f:
+        for line in f:
+            m = re.match('instance\s*=\s*projects/([^/]*)/instances/.*', line)
+            if m:
+                return m[1]
+    return ""
+
+
+def _siso_rbe_project():
+    """Returns RBE project used by siso."""
+    siso_project = os.environ.get('SISO_PROJECT')
+    if siso_project:
+        return siso_project
+    root_dir = gclient_paths.GetPrimarySolutionPath()
+    if not root_dir:
+        return ""
+    sisoenv_path = os.path.join(root_dir, 'build/config/siso/.sisoenv')
+    if not os.path.exists(sisoenv_path):
+        return ""
+    with open(sisoenv_path) as f:
+        for line in f:
+            m = re.match('SISO_PROJECT=\s*(\S*)\s*', line)
+            if m:
+                return m[1]
+    return ""
 
 
 
 
 def _quote_for_cmd(arg):
 def _quote_for_cmd(arg):
@@ -200,6 +128,7 @@ def _main_inner(input_args, build_id, should_collect_logs=False):
     offline = False
     offline = False
     output_dir = "."
     output_dir = "."
     summarize_build = os.environ.get("NINJA_SUMMARIZE_BUILD") == "1"
     summarize_build = os.environ.get("NINJA_SUMMARIZE_BUILD") == "1"
+    project = None
 
 
     # Ninja uses getopt_long, which allow to intermix non-option arguments.
     # Ninja uses getopt_long, which allow to intermix non-option arguments.
     # To leave non supported parameters untouched, we do not use getopt.
     # To leave non supported parameters untouched, we do not use getopt.
@@ -217,6 +146,12 @@ def _main_inner(input_args, build_id, should_collect_logs=False):
             output_dir = arg[2:]
             output_dir = arg[2:]
         elif arg in ("-o", "--offline"):
         elif arg in ("-o", "--offline"):
             offline = True
             offline = True
+        elif arg in ("--project", "-project"):
+            project = input_args[index + 2]
+        elif arg.startswith("--project="):
+            project = arg[len("--project="):]
+        elif arg.startswith("-project="):
+            project = arg[len("-project="):]
         elif arg in ("-h", "--help"):
         elif arg in ("-h", "--help"):
             print(
             print(
                 "autoninja: Use -o/--offline to temporary disable remote execution.",
                 "autoninja: Use -o/--offline to temporary disable remote execution.",
@@ -262,16 +197,46 @@ def _main_inner(input_args, build_id, should_collect_logs=False):
             use_reclient = use_remoteexec
             use_reclient = use_remoteexec
 
 
         if use_remoteexec:
         if use_remoteexec:
-            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
+            if use_reclient:
+                project = _reclient_rbe_project()
+                if not project:
+                    print(
+                        "Can't detect RBE project to use.\n"
+                        "Did you setup properly?\n",
+                        file=sys.stderr,
+                    )
+                    return 1
+            elif use_siso and project is None:
+                # siso runs locally if empty project is given
+                # even if use_remoteexec=true is set.
+                project = _siso_rbe_project()
+
+            if _is_google_corp_machine():
+                # user may login on non-@google.com account on corp,
+                # but need to use @google.com and rbe-chrome-untrusted
+                # on corp machine.
+                if project == 'rbe-chromium-untrusted':
+                    print(
+                        "You can't use rbe-chromium-untrusted on corp "
+                        "machine.\n"
+                        "Please use rbe-chrome-untrusted and @google.com "
+                        "account instead to build chromium.\n",
+                        file=sys.stderr,
+                    )
+                    return 1
+            else:
+                # only @google.com is allowed to use rbe-chrome-untrusted
+                # and use @google.com on non-corp machine is not allowed
+                # by corp security policy.
+                if project == 'rbe-chrome-untrusted':
+                    print(
+                        "You can't use rbe-chrome-untrusted on non-corp "
+                        "machine.\n"
+                        "Plase use rbe-chromium-untrusted and non-@google.com "
+                        "account instead to build chromium.",
+                        file=sys.stderr,
+                    )
+                    return 1
 
 
         if gclient_utils.IsEnvCog():
         if gclient_utils.IsEnvCog():
             if not use_remoteexec or use_reclient or not use_siso:
             if not use_remoteexec or use_reclient or not use_siso:

+ 8 - 35
tests/autoninja_test.py

@@ -85,6 +85,10 @@ class AutoninjaTest(trial_dir.TestCase):
             write(os.path.join('buildtools', 'reclient_cfgs', 'reproxy.cfg'),
             write(os.path.join('buildtools', 'reclient_cfgs', 'reproxy.cfg'),
                   'RBE_v=2')
                   'RBE_v=2')
             write(os.path.join('buildtools', 'reclient', 'version.txt'), '0.0')
             write(os.path.join('buildtools', 'reclient', 'version.txt'), '0.0')
+            write(
+                os.path.join('buildtools', 'reclient_cfgs', 'reproxy.cfg'),
+                'instance=projects/rbe-chromium-untrusted-test/'
+                'instances/default_instance')
             autoninja.main(['autoninja.py', '-C', out_dir])
             autoninja.main(['autoninja.py', '-C', out_dir])
             run_ninja.assert_called_once()
             run_ninja.assert_called_once()
             args = run_ninja.call_args.args[0]
             args = run_ninja.call_args.args[0]
@@ -104,6 +108,8 @@ class AutoninjaTest(trial_dir.TestCase):
         with mock.patch('siso.main', return_value=0) as siso_main:
         with mock.patch('siso.main', return_value=0) as siso_main:
             out_dir = os.path.join('out', 'dir')
             out_dir = os.path.join('out', 'dir')
             write(os.path.join(out_dir, 'args.gn'), 'use_siso=true')
             write(os.path.join(out_dir, 'args.gn'), 'use_siso=true')
+            write(os.path.join('build', 'config', 'siso', '.sisoenv'),
+                  'SISO_PROJECT=rbe-chromium-untrusted-test')
             autoninja.main(['autoninja.py', '-C', out_dir])
             autoninja.main(['autoninja.py', '-C', out_dir])
             siso_main.assert_called_once()
             siso_main.assert_called_once()
             args = siso_main.call_args.args[0]
             args = siso_main.call_args.args[0]
@@ -129,6 +135,8 @@ class AutoninjaTest(trial_dir.TestCase):
                       'use_siso=true\nuse_remoteexec=true')
                       'use_siso=true\nuse_remoteexec=true')
                 write(
                 write(
                     os.path.join('buildtools', 'reclient_cfgs', 'reproxy.cfg'),
                     os.path.join('buildtools', 'reclient_cfgs', 'reproxy.cfg'),
+                    'instance=projects/rbe-chromium-untrusted-test/'
+                    'instances/default_instance\n'
                     'RBE_v=2')
                     'RBE_v=2')
                 write(os.path.join('buildtools', 'reclient', 'version.txt'),
                 write(os.path.join('buildtools', 'reclient', 'version.txt'),
                       '0.0')
                       '0.0')
@@ -143,41 +151,6 @@ class AutoninjaTest(trial_dir.TestCase):
             ['siso', 'ninja', '-project=', '-reapi_instance=', '-C', out_dir])
             ['siso', 'ninja', '-project=', '-reapi_instance=', '-C', out_dir])
         self.assertEqual(reclient_helper_calls[0][1], 'autosiso')
         self.assertEqual(reclient_helper_calls[0][1], 'autosiso')
 
 
-    @parameterized.expand([
-        ("non corp machine", False, None, None, None, False),
-        ("non corp adc account", True, "foo@chromium.org", None, None, True),
-        ("corp adc account", True, "foo@google.com", None, None, False),
-        ("non corp gcloud auth account", True, None, "foo@chromium.org", None,
-         True),
-        ("corp gcloud auth account", True, None, "foo@google.com", None, False),
-        ("non corp luci auth account", True, None, None, "foo@chromium.org",
-         True),
-        ("corp luci auth account", True, None, None, "foo@google.com", False),
-    ])
-    def test_is_corp_machine_using_external_account(self, _, is_corp,
-                                                    adc_account,
-                                                    gcloud_auth_account,
-                                                    luci_auth_account,
-                                                    expected):
-        for shelve_file in glob.glob(
-                os.path.join(autoninja._SCRIPT_DIR, ".autoninja*")):
-            # Clear cache.
-            os.remove(shelve_file)
-
-        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), mock.patch(
-                                    'autoninja._luci_auth_account',
-                                    return_value=luci_auth_account):
-            self.assertEqual(
-                bool(
-                    # pylint: disable=line-too-long
-                    autoninja._is_google_corp_machine_using_external_account()),
-                expected)
-
     @mock.patch('sys.platform', 'win32')
     @mock.patch('sys.platform', 'win32')
     def test_print_cmd_windows(self):
     def test_print_cmd_windows(self):
         args = [
         args = [