blob: ea168a5c47e6ef4b3f33516fa9f1b2d5d1697cce [file]
#!/usr/bin/env fuchsia-vendored-python
# Copyright 2026 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Determines the appropriate build authentication method.
It chooses from three outcomes:
1. "loas" : corporate gcert/LOAS credentials and accessible SrcFS helper.
2. "oauth" : local gcloud Application Default Credentials (ADC).
3. "machine" : bot/VM using GCE VM Metadata.
"""
import argparse
import enum
import os
import pathlib
import shutil
import subprocess
import sys
from typing import TextIO
from build.auth import gcloud
class AuthType(str, enum.Enum):
"""The supported authentication types."""
LOAS = "loas"
OAUTH = "oauth"
MACHINE = "machine"
def __str__(self) -> str:
return self.value
@property
def is_user_based(self) -> bool:
"""Returns True if the authentication type is user-based (LOAS or OAuth)."""
return self in (AuthType.LOAS, AuthType.OAUTH)
BAZEL_CRED_HELPER = pathlib.Path(
"/google/src/head/depot/google3/devtools/blaze/bazel/credhelper/credhelper"
)
_SCRIPT_NAME = pathlib.Path(__file__).name
def msg(text: str, file: TextIO | None = None) -> None:
"""Print a self-identifying message to a stream."""
if file is None:
file = sys.stderr
print(f"[{_SCRIPT_NAME}] {text}", file=file)
def is_executable(path: pathlib.Path) -> bool:
"""Checks if a path exists and is executable."""
return path.exists() and os.access(path, os.X_OK)
def is_infra_env(env: dict[str, str]) -> bool:
"""Checks if the environment is a bot/infrastructure environment."""
return "BUILDBUCKET_ID" in env or "SWARMING_TASK_ID" in env
def has_gcloud_or_adc(env: dict[str, str]) -> bool:
"""Checks if local gcloud ADC credentials or gcloud binary are present."""
home = env.get("HOME", "")
adc_file = pathlib.Path(home) / gcloud.ADC_SUBPATH if home else None
has_gcloud = shutil.which("gcloud", path=env.get("PATH")) is not None
return bool((adc_file and adc_file.exists()) or has_gcloud)
def check_loas_type(env: dict[str, str], script_path: pathlib.Path) -> str:
"""Runs check_loas_restrictions.sh and returns its output."""
try:
output = subprocess.check_output(
[str(script_path)],
text=True,
stderr=subprocess.DEVNULL,
env=env,
)
lines = output.strip().splitlines()
return lines[-1] if lines else ""
except (subprocess.CalledProcessError, FileNotFoundError):
return ""
def select_auth_method(
env: dict[str, str],
check_loas_script: pathlib.Path,
cred_helper: pathlib.Path = BAZEL_CRED_HELPER,
verbose: bool = False,
) -> AuthType:
"""Select the correct authentication method based on the environment.
Args:
env: Environment variables to inspect.
check_loas_script: Path to check_loas_restrictions.sh.
cred_helper: Path to the Bazel credential helper.
verbose: Print detailed diagnostic reasoning to stderr.
Returns:
The selected AuthType.
Raises:
ValueError: If no valid authentication method is detected.
"""
def vmsg(text: str) -> None:
if verbose:
msg(text)
# 1. Infrastructure environment
if is_infra_env(env):
vmsg(
f"Detected infrastructure/bot environment (BUILDBUCKET_ID="
f"{env.get('BUILDBUCKET_ID', '')}, SWARMING_TASK_ID="
f"{env.get('SWARMING_TASK_ID', '')}). Selecting machine credentials."
)
return AuthType.MACHINE
has_local_creds = has_gcloud_or_adc(env)
# 2. Determine LOAS restriction type
loas_type = check_loas_type(env, check_loas_script)
vmsg(f"LOAS certificate type check returned: {loas_type}")
# Case 1: Ideal corporate flow (unrestricted LOAS + accessible credhelper)
if loas_type == "unrestricted" and is_executable(cred_helper):
vmsg(
"Unrestricted LOAS certificate and accessible Bazel credential helper detected. Selecting LOAS."
)
return AuthType.LOAS
# Case 2: Fallback to local gcloud OAuth/ADC if available
if has_local_creds:
if loas_type == "unrestricted":
msg(
f"Warning: Bazel credential helper on SrcFS is not accessible: {cred_helper}"
)
msg("Gracefully falling back to OAuth (local gcloud/ADC) mode.")
else:
vmsg(
"Local gcloud ADC or gcloud installation detected. Selecting OAuth."
)
return AuthType.OAUTH
# Case 3: Completely unauthenticated
raise ValueError(
"We could not find a valid corporate LOAS credential, and local "
"gcloud Application Default Credentials (ADC) are not configured.\n"
"To configure RBE and authenticate successfully, please run: 'fx rbe auth'"
)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="Print diagnostic reasoning to stderr.",
)
args = parser.parse_args()
# Determine script directories
script_dir = pathlib.Path(__file__).parent.resolve()
check_loas_script = script_dir / "check_loas_restrictions.sh"
try:
auth_method = select_auth_method(
env=dict(os.environ),
check_loas_script=check_loas_script,
verbose=args.verbose,
)
print(auth_method)
return 0
except ValueError as e:
msg(str(e))
return 1
if __name__ == "__main__":
sys.exit(main())