浏览代码

autoninja.py: disallow external account from corp machine

This disallows Googler to use non-google account with reclient and
siso from corp machine.

Bug: b/309720176
Change-Id: I8077eff8bbf47f579462e1fefa3609a5d492a013
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/5035266
Reviewed-by: Philipp Wollermann <philwo@chromium.org>
Reviewed-by: Josip Sokcevic <sokcevic@chromium.org>
Commit-Queue: Takuto Ikuta <tikuta@chromium.org>
Takuto Ikuta 1 年之前
父节点
当前提交
0d0f28a4fc
共有 5 个文件被更改,包括 133 次插入4 次删除
  1. 30 0
      .vpython3
  2. 1 1
      autoninja
  3. 1 1
      autoninja.bat
  4. 75 1
      autoninja.py
  5. 26 1
      tests/autoninja_test.py

+ 30 - 0
.vpython3

@@ -65,3 +65,33 @@ wheel: <
   name: "infra/python/wheels/certifi-py2_py3"
   name: "infra/python/wheels/certifi-py2_py3"
   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:
+#   tests/autoninja_test.py
+wheel: <
+  name: "infra/python/wheels/parameterized-py2_py3"
+  version: "version:0.8.1"
+>

+ 1 - 1
autoninja

@@ -20,7 +20,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.
-python3 "$(dirname -- "$0")/autoninja.py" "$@"
+vpython3 "$(dirname -- "$0")/autoninja.py" "$@"
 retval=$?
 retval=$?
 
 
 if [ "$retval" == "0" ] && [ "$NINJA_SUMMARIZE_BUILD" == "1" ]; then
 if [ "$retval" == "0" ] && [ "$NINJA_SUMMARIZE_BUILD" == "1" ]; then

+ 1 - 1
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
 :: should be consistent between autoninja.bat and the autoninja script used by
 :: git bash.
 :: git bash.
 
 
-@call %scriptdir%python-bin\python3.bat %scriptdir%autoninja.py "%%*"
+@call %scriptdir%\vpython3.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.

+ 75 - 1
autoninja.py

@@ -1,4 +1,4 @@
-#!/usr/bin/env python3
+#!/usr/bin/env vpython3
 # 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.
@@ -14,13 +14,19 @@ does handle import statements, but it can't handle conditional setting of build
 settings.
 settings.
 """
 """
 
 
+import json
 import multiprocessing
 import multiprocessing
 import os
 import os
 import platform
 import platform
 import re
 import re
 import shlex
 import shlex
+import shutil
 import subprocess
 import subprocess
 import sys
 import sys
+import warnings
+
+import google.auth
+from google.auth.transport.requests import AuthorizedSession
 
 
 import autosiso
 import autosiso
 import ninja
 import ninja
@@ -43,6 +49,62 @@ _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:
+        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):
 def _quote_for_cmd(arg):
     # First, escape the arg so that CommandLineToArgvW will parse it properly.
     # First, escape the arg so that CommandLineToArgvW will parse it properly.
     if arg == "" or " " in arg or '"' in arg:
     if arg == "" or " " in arg or '"' in arg:
@@ -209,6 +271,18 @@ def main(args):
                             use_goma = True
                             use_goma = True
                             break
                             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.
     # Strip -o/--offline so ninja doesn't see them.
     input_args = [arg for arg in input_args if arg not in ("-o", "--offline")]
     input_args = [arg for arg in input_args if arg not in ("-o", "--offline")]
 
 

+ 26 - 1
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.
 # Copyright (c) 2022 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.
@@ -12,6 +12,8 @@ import unittest
 import contextlib
 import contextlib
 from unittest import mock
 from unittest import mock
 
 
+from parameterized import parameterized
+
 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 sys.path.insert(0, ROOT_DIR)
 sys.path.insert(0, ROOT_DIR)
 
 
@@ -152,6 +154,29 @@ class AutoninjaTest(trial_dir.TestCase):
         self.assertIn('-C', args)
         self.assertIn('-C', args)
         self.assertEqual(args[args.index('-C') + 1], out_dir)
         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):
     def test_gn_lines(self):
         out_dir = os.path.join('out', 'dir')
         out_dir = os.path.join('out', 'dir')
         # Make sure nested import directives work. This is based on the
         # Make sure nested import directives work. This is based on the