feat: Add new gcloud commands, API clients, and third-party libraries across various services.

This commit is contained in:
2026-01-01 20:26:35 +01:00
parent 5e23cbece0
commit a19e592eb7
25221 changed files with 8324611 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
# -*- coding: utf-8 -*- #
# Copyright 2013 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Auth for the Google Cloud SDK."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.GA,
base.ReleaseTrack.BETA,
base.ReleaseTrack.ALPHA)
class Auth(base.Group):
# pylint: disable=line-too-long
"""Manage oauth2 credentials for the Google Cloud CLI.
The `gcloud auth` command group lets you grant and revoke authorization to
Google Cloud CLI (`gcloud` CLI) to access Google Cloud. Typically, when
scripting Google Cloud CLI tools for use on multiple machines, using
`gcloud auth activate-service-account` is recommended.
For information about authorization and credential types, see
[Authorizing the gcloud CLI](https://cloud.google.com/sdk/docs/authorizing).
For information about authorizing a service account, see
[Authorizing with a service account](https://cloud.google.com/sdk/docs/authorizing#service-account).
After running `gcloud auth` commands, you can run other commands with
`--account`=``ACCOUNT'' to authenticate the command with the credentials
of the specified account. For information about `--account` and other `gcloud`
CLI global flags, see the
[gcloud CLI overview](https://cloud.google.com/sdk/gcloud/reference).
See `$ gcloud topic client-certificate` to learn how to use Mutual TLS when using gcloud.
Mutual TLS can be used for [certificate based access](https://cloud.google.com/beyondcorp-enterprise/docs/securing-resources-with-certificate-based-access) with gcloud.
## EXAMPLES
To authenticate a user account with `gcloud` and minimal user output, run:
$ gcloud auth login --brief
To list all credentialed accounts and identify the current active account,
run:
$ gcloud auth list
To revoke credentials for a user account (like logging out), run:
$ gcloud auth revoke test@gmail.com
"""
# pylint: enable=line-too-long
category = base.IDENTITY_AND_SECURITY_CATEGORY
def Filter(self, context, args):
del context, args
base.DisableUserProjectQuota()

View File

@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*- #
# Copyright 2013 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The auth command gets tokens via oauth2."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.auth import refresh_token
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions as c_exc
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core.console import console_io
@base.Hidden
class ActivateRefreshToken(base.SilentCommand):
"""Get credentials via an existing refresh token.
Use an oauth2 refresh token to manufacture credentials for Google APIs. This
token must have been acquired via some legitimate means to work. The account
provided is only used locally to help the Cloud SDK keep track of the new
credentials, so you can activate, list, and revoke the credentials in the
future.
"""
@staticmethod
def Args(parser):
"""Set args for gcloud auth activate-refresh-token."""
parser.add_argument(
'account',
help='The account to associate with the refresh token.')
parser.add_argument(
'token', nargs='?',
help=('OAuth2 refresh token. If blank, prompt for value.'))
def Run(self, args):
"""Run the authentication command."""
token = args.token or console_io.PromptResponse('Refresh token: ')
if not token:
raise c_exc.InvalidArgumentException('token', 'No value provided.')
refresh_token.ActivateCredentials(args.account, token)
project = args.project
if project:
properties.PersistProperty(properties.VALUES.core.project, project)
log.status.Print('Activated refresh token credentials: [{0}]'
.format(args.account))

View File

@@ -0,0 +1,154 @@
# -*- coding: utf-8 -*- #
# Copyright 2013 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A simple auth command to bootstrap authentication with oauth2."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import json
from googlecloudsdk.api_lib.auth import service_account as auth_service_account
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions as c_exc
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.credentials import exceptions as creds_exceptions
from googlecloudsdk.core.credentials import store as c_store
from googlecloudsdk.core.util import encoding
from googlecloudsdk.core.util import files
class ActivateServiceAccount(base.SilentCommand):
r"""Authorize access to Google Cloud with a service account.
To allow `gcloud` (and other tools in Google Cloud CLI) to use service account
credentials to make requests, use this command to import these credentials
from a file that contains a private authorization key, and activate them for
use in `gcloud`. {command} serves the same function as `gcloud auth login`
but uses a service account rather than Google user credentials.
For more information on authorization and credential types, see:
[](https://cloud.google.com/sdk/docs/authorizing).
_Key File_
To obtain the key file for this command, use either the [Google Cloud
Console](https://console.cloud.google.com) or `gcloud iam
service-accounts keys create`. The key file can be .json (preferred) or
.p12 (legacy) format. In the case of legacy .p12 files, a separate password
might be required and is displayed in the Console when you create the key.
_Credentials_
Credentials will also be activated (similar to running
`gcloud config set account [ACCOUNT_NAME]`).
If a project is specified using the `--project` flag, the project is set in
active configuration, which is the same as running
`gcloud config set project [PROJECT_NAME]`. Any previously active credentials,
will be retained (though no longer default) and can be
displayed by running `gcloud auth list`.
If you want to delete previous credentials, see `gcloud auth revoke`.
_Note:_ Service accounts use client quotas for tracking usage.
## EXAMPLES
To authorize `gcloud` to access Google Cloud using an existing
service account while also specifying a project, run:
$ {command} SERVICE_ACCOUNT@DOMAIN.COM \
--key-file=/path/key.json --project=PROJECT_ID
"""
@staticmethod
def Args(parser):
"""Set args for serviceauth."""
parser.add_argument('account', nargs='?',
help='E-mail address of the service account.')
parser.add_argument('--key-file',
help=('Path to the private key file.'),
required=True)
group = parser.add_mutually_exclusive_group()
group.add_argument('--password-file',
help=('Path to a file containing the password for the '
'service account private key '
'(only for a .p12 file).'))
group.add_argument('--prompt-for-password', action='store_true',
help=('Prompt for the password for the service account '
'private key (only for a .p12 file).'))
def Run(self, args):
"""Create service account credentials."""
file_content, is_json = _IsJsonFile(args.key_file)
if is_json:
cred = auth_service_account.CredentialsFromAdcDictGoogleAuth(
file_content)
if args.password_file or args.prompt_for_password:
raise c_exc.InvalidArgumentException(
'--password-file',
'A .json service account key does not require a password.')
account = cred.service_account_email
if args.account and args.account != account:
raise c_exc.InvalidArgumentException(
'ACCOUNT',
'The given account name does not match the account name in the key '
'file. This argument can be omitted when using .json keys.')
else:
account = args.account
if not account:
raise c_exc.RequiredArgumentException(
'ACCOUNT', 'An account is required when using .p12 keys')
password = None
if args.password_file:
try:
password = files.ReadFileContents(args.password_file).strip()
except files.Error as e:
raise c_exc.UnknownArgumentException('--password-file', e)
elif args.prompt_for_password:
password = console_io.PromptPassword('Password: ')
cred = auth_service_account.CredentialsFromP12Key(
file_content, account, password=password)
try:
c_store.ActivateCredentials(account, cred)
except creds_exceptions.TokenRefreshError as e:
log.file_only_logger.exception(e)
raise
project = args.project
if project:
properties.PersistProperty(properties.VALUES.core.project, project)
log.status.Print('Activated service account credentials for: [{0}]'
.format(account))
def _IsJsonFile(filename):
"""Check and validate if given filename is proper json file."""
content = console_io.ReadFromFileOrStdin(filename, binary=True)
try:
return json.loads(encoding.Decode(content)), True
except ValueError as e:
if filename.endswith('.json'):
raise auth_service_account.BadCredentialFileException(
'Could not read json file {0}: {1}'.format(filename, e))
return content, False

View File

@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*- #
# Copyright 2016 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Auth for Application Default Credentials."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.GA, base.ReleaseTrack.BETA,
base.ReleaseTrack.ALPHA)
class ApplicationDefault(base.Group):
"""Manage your active Application Default Credentials.
Application Default Credentials (ADC) provide a method to get credentials used
in calling Google APIs. The {command} command group allows you to manage
active credentials on your machine that are used for local application
development.
These credentials are only used by Google client libraries in your own
application.
For more information about ADC and how it works, see [Authenticating as a
service account](https://cloud.google.com/docs/authentication/production).
## EXAMPLES
To use your own user credentials for your application to access an API, run:
$ {command} login
This will take you through a web flow to acquire new user credentials.
To create a service account and have your application use it for API access,
run:
$ gcloud iam service-accounts create my-account
$ gcloud iam service-accounts keys create key.json
--iam-account=my-account@my-project.iam.gserviceaccount.com
$ export GOOGLE_APPLICATION_CREDENTIALS=key.json
$ ./my_application.sh
"""

View File

@@ -0,0 +1,285 @@
# -*- coding: utf-8 -*- #
# Copyright 2016 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A command to install Application Default Credentials using a user account."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import textwrap
from googlecloudsdk.api_lib.auth import util as auth_util
from googlecloudsdk.calliope import actions
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions as c_exc
from googlecloudsdk.command_lib.auth import auth_util as command_auth_util
from googlecloudsdk.command_lib.auth import flags
from googlecloudsdk.command_lib.auth import workforce_login_config as workforce_login_config_util
from googlecloudsdk.core import config
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.credentials import creds as creds_module
from googlecloudsdk.core.credentials import gce as c_gce
from googlecloudsdk.core.credentials import store as c_store
from googlecloudsdk.core.util import files
@base.UniverseCompatible
class Login(base.Command):
r"""Acquire new user credentials to use for Application Default Credentials.
Obtains user access credentials via a web flow and puts them in the
well-known location for Application Default Credentials (ADC).
This command is useful when you are developing code that would normally
use a service account but need to run the code in a local development
environment where it's easier to provide user credentials. The credentials
will apply to all API calls that make use of the Application Default
Credentials client library. Do not set the `GOOGLE_APPLICATION_CREDENTIALS`
environment variable if you want to use the credentials generated by this
command in your local development. This command tries to find a quota
project from gcloud's context and write it to ADC so that Google client
libraries can use it for billing and quota. Alternatively, you can use
the `--client-id-file` flag. In this case, the project owning the client ID
will be used for billing and quota. You can create the client ID file
at https://console.cloud.google.com/apis/credentials.
This command has no effect on the user account(s) set up by the
`gcloud auth login` command.
Any credentials previously generated by
`gcloud auth application-default login` will be overwritten.
"""
detailed_help = {
'EXAMPLES':
"""\
If you want your local application to temporarily use your own user
credentials for API access, run:
$ {command}
If you'd like to login by passing in a file containing your own client
id, run:
$ {command} --client-id-file=clientid.json
"""
}
@staticmethod
def Args(parser):
"""Set args for gcloud auth application-default login."""
parser.add_argument(
'--client-id-file',
help='A file containing your own client id to use to login. If '
'--client-id-file is specified, the quota project will not be '
'written to ADC.')
parser.add_argument(
'--scopes',
type=arg_parsers.ArgList(min_length=1),
metavar='SCOPE',
help='The names of the scopes to authorize for. By default '
'{0} scopes are used. '
'The list of possible scopes can be found at: '
'[](https://developers.google.com/identity/protocols/googlescopes). '
'To add scopes for applications outside of Google Cloud Platform, '
'such as Google Drive, [create an OAuth Client ID]'
'(https://support.google.com/cloud/answer/6158849) and provide it by '
'using the --client-id-file flag. '
.format(', '.join(auth_util.DEFAULT_SCOPES)))
parser.add_argument(
'--login-config',
help='Path to the login configuration file (workforce pool, '
'generated by the Cloud Console or '
'`gcloud iam workforce-pools create-login-config`)',
action=actions.StoreProperty(properties.VALUES.auth.login_config_file))
parser.add_argument(
'account',
nargs='?',
help=(
'User account used for authorization. When the account specified'
' has valid credentials in the local credential store these'
' credentials will be re-used. Otherwise new ones will be fetched'
' and replace any stored credential.'
' This caching behavior is only available for user credentials.'
),
)
flags.AddQuotaProjectFlags(parser)
flags.AddRemoteLoginFlags(parser, for_adc=True)
parser.display_info.AddFormat('none')
def Run(self, args):
"""Run the authentication command."""
# TODO(b/203102970): Remove this condition check after the bug is resolved
if properties.VALUES.auth.access_token_file.Get():
raise c_store.FlowError(
'auth/access_token_file or --access-token-file was set which is not '
'compatible with this command. Please unset the property and rerun '
'this command.'
)
if c_gce.Metadata().connected:
message = textwrap.dedent("""
You are running on a Google Compute Engine virtual machine.
The service credentials associated with this virtual machine
will automatically be used by Application Default
Credentials, so it is not necessary to use this command.
If you decide to proceed anyway, your user credentials may be visible
to others with access to this virtual machine. Are you sure you want
to authenticate with your personal account?
""")
console_io.PromptContinue(
message=message, throw_if_unattended=True, cancel_on_no=True)
command_auth_util.PromptIfADCEnvVarIsSet()
if args.client_id_file and not args.launch_browser:
raise c_exc.InvalidArgumentException(
'--no-launch-browser',
'`--no-launch-browser` flow no longer works with the '
'`--client-id-file`. Please replace `--no-launch-browser` with '
'`--no-browser`.'
)
# Currently the original scopes are not stored in the ADC. If they were, a
# change in scopes could also be used to determine a cache hit.
if args.account and not args.scopes:
if ShouldUseCachedCredentials(args.account):
log.warning(
'\nValid credentials already exist for {}. To force refresh the'
' existing credentials, omit the "ACCOUNT" positional field.'
.format(args.account)
)
return
scopes = args.scopes or auth_util.DEFAULT_SCOPES
target_impersonation_principal, delegates, target_scopes = None, None, None
impersonation_service_accounts = (
properties.VALUES.auth.impersonate_service_account.Get()
)
if impersonation_service_accounts:
(target_impersonation_principal, delegates
) = c_store.ParseImpersonationAccounts(impersonation_service_accounts)
# Only set target_scopes if user explicitly asked for something, if not
# we will let auth and client library decide on the scopes.
target_scopes = args.scopes or None
# Source credential just needs the scope needed to call the IAM API for
# impersonation. Asking user to consent any other scope is unnecessary.
scopes = [auth_util.CLOUD_PLATFORM_SCOPE]
flow_params = dict(
no_launch_browser=not args.launch_browser,
no_browser=not args.browser,
remote_bootstrap=args.remote_bootstrap,
)
# 1. Try the 3PI web flow with --no-browser:
# This could be a 3PI flow initiated via --no-browser.
# If provider_name is present, then this is the 3PI flow.
# We can start the flow as is as the remote_bootstrap value will be used.
if args.remote_bootstrap and 'provider_name' in args.remote_bootstrap:
auth_util.DoInstalledAppBrowserFlowGoogleAuth(
config.CLOUDSDK_EXTERNAL_ACCOUNT_SCOPES,
auth_proxy_redirect_uri=(
'https://sdk.cloud.google/applicationdefaultauthcode.html'
),
**flow_params
)
return
# 2. Try the 3PI web flow with a login configuration file.
login_config_file = workforce_login_config_util.GetWorkforceLoginConfig()
if login_config_file:
if args.client_id_file:
raise c_exc.ConflictingArgumentsException(
'--client-id-file is not currently supported for third party login '
'flows. ')
if args.scopes:
raise c_exc.ConflictingArgumentsException(
'--scopes is not currently supported for third party login flows.')
creds = workforce_login_config_util.DoWorkforceHeadfulLogin(
login_config_file,
True,
**flow_params
)
else:
# 3. Try the 1P web flow.
properties.VALUES.auth.client_id.Set(
auth_util.DEFAULT_CREDENTIALS_DEFAULT_CLIENT_ID)
properties.VALUES.auth.client_secret.Set(
auth_util.DEFAULT_CREDENTIALS_DEFAULT_CLIENT_SECRET)
if auth_util.CLOUD_PLATFORM_SCOPE not in scopes:
raise c_exc.InvalidArgumentException(
'--scopes',
'{} scope is required but not requested. Please include it in the'
' --scopes flag.'.format(auth_util.CLOUD_PLATFORM_SCOPE),
)
creds = auth_util.DoInstalledAppBrowserFlowGoogleAuth(
scopes,
client_id_file=args.client_id_file,
auth_proxy_redirect_uri=(
'https://sdk.cloud.google.com/applicationdefaultauthcode.html'
),
**flow_params
)
if not creds:
return
if args.account and hasattr(creds, 'with_account'):
_ = command_auth_util.ExtractAndValidateAccount(args.account, creds)
creds = creds.with_account(args.account)
if not target_impersonation_principal:
if args.IsSpecified('client_id_file'):
command_auth_util.DumpADC(creds, quota_project_disabled=False)
elif args.disable_quota_project:
command_auth_util.DumpADC(creds, quota_project_disabled=True)
else:
command_auth_util.DumpADCOptionalQuotaProject(creds)
else:
# TODO(b/184049366): Supports quota project with impersonated creds.
command_auth_util.DumpImpersonatedServiceAccountToADC(
creds,
target_principal=target_impersonation_principal,
delegates=delegates,
scopes=target_scopes)
return creds
def ShouldUseCachedCredentials(account):
"""Skip login if the existing ADC was provisioned for the requested account."""
try:
file_path = config.ADCFilePath()
data = files.ReadFileContents(file_path)
except files.Error:
return False
try:
credential = creds_module.FromJsonGoogleAuth(data)
except creds_module.UnknownCredentialsType:
return False
except creds_module.InvalidCredentialsError:
return False
cred_type = creds_module.CredentialTypeGoogleAuth.FromCredentials(credential)
if cred_type == creds_module.CredentialTypeGoogleAuth.USER_ACCOUNT:
if credential.account != account:
return False
return True

View File

@@ -0,0 +1,235 @@
# -*- coding: utf-8 -*- #
# Copyright 2016 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A command that prints an access token for Application Default Credentials.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from google.auth import credentials
from google.auth import exceptions as google_auth_exceptions
from google.auth import impersonated_credentials
from google.oauth2 import credentials as google_auth_creds
from googlecloudsdk.api_lib.auth import util as auth_util
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions as c_exc
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core import requests
from googlecloudsdk.core.credentials import creds as c_creds
from googlecloudsdk.core.credentials import exceptions as creds_exceptions
from googlecloudsdk.core.credentials import google_auth_credentials as c_google_auth
from googlecloudsdk.core.credentials import store as c_store
import six
_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
@base.UniverseCompatible
class PrintAccessToken(base.Command):
r"""Print an access token for your current Application Default Credentials.
{command} generates and prints an access token for the current
Application Default Credential (ADC). The
[ADC](https://google.aip.dev/auth/4110) can be specified either by using
`gcloud auth application-default login`,
`gcloud auth login --cred-file=/path/to/cred/file --update-adc`, or by
setting the `GOOGLE_APPLICATION_CREDENTIALS` environment variable.
The access token generated by {command} is useful for manually testing
APIs via curl or similar tools.
In order to print details of the access token, such as the associated account
and the token's expiration time in seconds, run:
$ curl -H "Content-Type: application/x-www-form-urlencoded" \
-d "access_token=$(gcloud auth application-default print-access-token)" \
https://www.googleapis.com/oauth2/v1/tokeninfo
Note that token itself may not be enough to access some services.
If you use the token with curl or similar tools, you may see
permission errors similar to "Your application has authenticated using end
user credentials from the Google Cloud SDK or Google Cloud Shell".
If it happens, you may need to provide a quota project in the
"X-Goog-User-Project" header. For example,
$ curl -H "X-Goog-User-Project: your-project" \
-H \
"Authorization: Bearer $(gcloud auth application-default \
print-access-token)" foo.googleapis.com
The identity that granted the token must have the serviceusage.services.use
permission on the provided project. See
https://cloud.google.com/apis/docs/system-parameters for more
information.
"""
@staticmethod
def Args(parser):
parser.add_argument(
'--lifetime',
type=arg_parsers.Duration(upper_bound='43200s'),
help=(
'Access token lifetime. The default access token lifetime is 3600'
' seconds, but you can use this flag to reduce the lifetime or'
' extend it up to 43200 seconds (12 hours). The org policy'
' constraint'
' `constraints/iam.allowServiceAccountCredentialLifetimeExtension`'
' must be set if you want to extend the lifetime beyond 3600'
' seconds. Note that this flag is for service account impersonation'
' only, so it only works when either'
' `--impersonate-service-account` flag or'
' `auth/impersonate_service_account` property is set.'
),
)
parser.add_argument(
'--scopes',
type=arg_parsers.ArgList(min_length=1),
metavar='SCOPE',
help='The scopes to authorize for. This flag is supported for user '
'accounts and service accounts only. '
'The list of possible scopes can be found at: '
'[](https://developers.google.com/identity/protocols/googlescopes).\n\n'
'For end-user accounts, the provided '
'scopes must be from [{0}], or the scopes previously specified through '
'`gcloud auth application-default login --scopes`.'.format(', '.join(
map('`{}`'.format, auth_util.DEFAULT_SCOPES))))
parser.display_info.AddFormat('value(token)')
def Run(self, args):
"""Run the helper command."""
# Create the ADC cred.
try:
creds, _ = c_creds.GetGoogleAuthDefault().default(
scopes=args.scopes or [auth_util.CLOUD_PLATFORM_SCOPE])
except google_auth_exceptions.DefaultCredentialsError as e:
log.debug(e, exc_info=True)
raise c_exc.ToolException(six.text_type(e))
# Check if impersonation is used. There are two scenarios:
# (1) the ADC file is not an impersonated credential json file, and either
# --impersonate-service-account flag or auth/impersonate_service_account
# property is set. In this case, we will create a new impersonated cred,
# using the ADC cred as the source cred. The impersonated service account
# value will be parsed and used as target principal and delegates.
# (2) the ADC file is impersonated cred json file, then the ADC cred
# created above is an impersonated cred. If users provided impersonated
# service account via flag or property, we also need to parse the value to
# target principal and delegates, and override them in the cred.
is_adc_cred_impersonated = c_creds.IsImpersonatedAccountCredentials(creds)
impersonation_service_accounts = (
properties.VALUES.auth.impersonate_service_account.Get()
)
is_impersonation_used = is_adc_cred_impersonated or (
impersonation_service_accounts is not None
)
if args.lifetime and not is_impersonation_used:
raise c_exc.InvalidArgumentException(
'--lifetime',
(
'Lifetime flag is for service account impersonation only. It must'
' be used together with either --impersonate-service-account flag'
' or auth/impersonate_service_account property, or the'
' application default credential json file must have'
' `impersonated_service_account` type.'
),
)
if impersonation_service_accounts:
(target_principal, delegates) = c_store.ParseImpersonationAccounts(
impersonation_service_accounts
)
# Add scopes to the ADC cred. We don't do so for impersonated cred since
# for scenario (1): the ADC cred is the source cred, the scopes should be
# added to the impersonated cred not the source cred
# for scenario (2): the ADC cred is the impersonated cred, we already added
# the scope during the cred creation
if args.scopes and not is_impersonation_used:
cred_type = c_creds.CredentialTypeGoogleAuth.FromCredentials(creds)
if cred_type not in [
c_creds.CredentialTypeGoogleAuth.USER_ACCOUNT,
c_creds.CredentialTypeGoogleAuth.SERVICE_ACCOUNT
]:
# TODO(b/223649175): Add support for other credential types(e.g GCE).
log.warning(
'`--scopes` flag may not work as expected and will be ignored '
'for account type {}.'.format(cred_type.key)
)
scopes = args.scopes + [auth_util.OPENID, auth_util.USER_EMAIL_SCOPE]
# non user account credential types
# pylint:disable=protected-access
if isinstance(creds, credentials.Scoped):
creds = creds.with_scopes(scopes)
else:
creds._scopes = scopes
# Make adjustments to the ADC cred.
# - if it's user credentials, convert it so that it can handle reauth
# during refresh.
# - if it's impersonated cred, override the target principal and delegates
# if impersonated service account flag/property is provided, and set
# lifetime if lifetime flag is provided.
if isinstance(creds, google_auth_creds.Credentials):
creds = c_google_auth.Credentials.FromGoogleAuthUserCredentials(
creds)
if is_adc_cred_impersonated:
if impersonation_service_accounts:
creds._target_principal = target_principal # pylint: disable=protected-access
creds._delegates = delegates # pylint: disable=protected-access
if args.lifetime:
creds._lifetime = args.lifetime # pylint: disable=protected-access
# The token URI needs to be overridden in case
# context aware access is enabled.
# pylint: disable=protected-access
creds._token_uri = c_creds.GetDefaultTokenUri()
# pylint: enable=protected-access
# Refresh the ADC cred.
req = requests.GoogleAuthRequest()
try:
with c_store.HandleGoogleAuthCredentialsRefreshError(for_adc=True):
creds.refresh(req)
except creds_exceptions.TokenRefreshError as e:
if args.scopes:
raise c_exc.InvalidArgumentException(
'--scopes',
'Invalid scopes value. Please make sure the scopes are from [{0}], '
'or the scopes previously specified through '
'`gcloud auth application-default login --scopes`.'
.format(', '.join(map('`{}`'.format, auth_util.DEFAULT_SCOPES))))
else:
raise e
# If impersonation is not needed, or the ADC cred is already an impersonated
# cred, since we already did refresh above, we can return the cred.
# Otherwise, we need to create an impersonated cred using the ADC cred as
# the source cred and the provided target principal, delegates and lifetime.
# We then do a refresh and return the cred.
if not is_impersonation_used or is_adc_cred_impersonated:
return creds
impersonated_creds = impersonated_credentials.Credentials(
source_credentials=creds,
target_principal=target_principal,
delegates=delegates,
target_scopes=args.scopes or [auth_util.CLOUD_PLATFORM_SCOPE],
lifetime=args.lifetime or _DEFAULT_TOKEN_LIFETIME_SECS,
)
impersonated_creds.refresh(req)
return impersonated_creds

View File

@@ -0,0 +1,88 @@
# -*- coding: utf-8 -*- #
# Copyright 2016 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Revoke credentials being used by Application Default Credentials."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import os
from google.oauth2 import credentials as google_auth_creds
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions as c_exc
from googlecloudsdk.core import config
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.credentials import creds as c_creds
from googlecloudsdk.core.credentials import google_auth_credentials as c_google_auth
from googlecloudsdk.core.credentials import store as c_store
class Revoke(base.SilentCommand):
"""Revoke previously generated Application Default Credentials.
Revokes Application Default Credentials that have been previously generated by
`{parent_command} login` and deletes the local credential file.
This does not affect any credentials set up through other means,
such as credentials referenced by the Application Default Credentials
environment variable or service account credentials that are active on
a Google Compute Engine virtual machine.
"""
@staticmethod
def Args(parser):
pass
def Run(self, args):
"""Revoke Application Default Credentials."""
cred_file = config.ADCFilePath()
if not os.path.isfile(cred_file):
log.status.Print('Application Default Credentials have not been set up, '
'nothing to revoke.')
return
creds, _ = c_creds.GetGoogleAuthDefault().load_credentials_from_file(
cred_file)
if not (c_creds.IsUserAccountCredentials(creds) or
c_creds.IsExternalAccountCredentials(creds) or
c_creds.IsExternalAccountUserCredentials(creds) or
c_creds.IsImpersonatedAccountCredentials(creds)):
raise c_exc.BadFileException(
'The given credential file is a service account credential, and '
'cannot be revoked.')
if isinstance(creds, google_auth_creds.Credentials):
creds = c_google_auth.Credentials.FromGoogleAuthUserCredentials(
creds)
console_io.PromptContinue(
'You are about to revoke the credentials stored in: [{file}]'.format(
file=cred_file),
throw_if_unattended=True,
cancel_on_no=True)
try:
c_store.RevokeCredentials(creds)
os.remove(cred_file)
log.status.Print('Credentials revoked.')
except c_store.RevokeError:
os.remove(cred_file)
log.warning(
'The credentials stored in: [{file}] are not revocable from the '
'server but have been deleted from the file system.'.format(
file=cred_file))

View File

@@ -0,0 +1,58 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Update or add a quota project in application default credentials json."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.auth import auth_util
from googlecloudsdk.command_lib.resource_manager import completers
class SetQuotaProject(base.SilentCommand):
"""Update or add a quota project in application default credentials (ADC).
A quota project is a Google Cloud Project that will be used for billing
and quota limits.
Before running this command, an ADC must already be generated using
$ gcloud auth application-default login.
The quota project defined in the ADC will be used by the Google client
libraries.
The existing application default credentials must have the
`serviceusage.services.use` permission on the given project.
## EXAMPLES
To update the quota project in application default credentials to
`my-quota-project`, run:
$ {command} my-quota-project
"""
@staticmethod
def Args(parser):
base.Argument(
'quota_project_id',
metavar='QUOTA_PROJECT_ID',
completer=completers.ProjectCompleter,
help='Quota project ID to add to application default credentials. If '
'a quota project already exists, it will be updated.').AddToParser(
parser)
def Run(self, args):
return auth_util.AddQuotaProjectToADC(args.quota_project_id)

View File

@@ -0,0 +1,190 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Register gcloud as a Docker credential helper."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import json
from googlecloudsdk.calliope import base
from googlecloudsdk.core import exceptions
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.docker import credential_utils as cred_utils
from googlecloudsdk.core.util import files as file_utils
class ConfigureDockerError(exceptions.Error):
"""General command error class."""
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
@base.UniverseCompatible
class ConfigureDocker(base.Command):
# pylint: disable=line-too-long
r"""Register `gcloud` as a Docker credential helper.
{command} adds the Docker `credHelper` entry to Docker's configuration file,
or creates the file if it doesn't exist. This will register `gcloud` as the
credential helper for all Google-supported Docker registries. If the Docker
configuration already contains a `credHelper` entry, it will be overwritten.
Note: `docker` and `gcloud` need to be on the same system `PATH` to work
correctly.
Note: This command will not work for `docker` installed via Snap, as the
`docker` snap package does not currently provide an interface for credential
helpers.
For more details on Docker registries, see
[](https://docs.docker.com/registry/).
For more details on how to authenticate to Google Container Registry using
this command, see
[](https://cloud.google.com/container-registry/docs/advanced-authentication#gcloud-helper).
For more details on Google Container Registry's standalone credential helpers,
see [](https://github.com/GoogleCloudPlatform/docker-credential-gcr).
For more details on Docker credential helpers, see
[](https://docs.docker.com/engine/reference/commandline/login/#credential-helpers).
## EXAMPLES
To configure docker authentication after logging into gcloud, run:
$ {command}
To configure docker authentication with Container Registry, e.g., `gcr.io`,
run:
$ {command} gcr.io
"""
# pylint: enable=line-too-long
def DockerCredentialGcloudExists(self):
return file_utils.SearchForExecutableOnPath(
'docker-credential-gcloud') or file_utils.SearchForExecutableOnPath(
'docker-credential-gcloud.cmd')
def DockerExists(self):
return file_utils.SearchForExecutableOnPath(
'docker') or file_utils.SearchForExecutableOnPath('docker.exe')
@staticmethod
def Args(parser):
"""Set args for configure-docker."""
parser.add_argument(
'registries',
nargs='?',
help='The comma-separated list of registries to configure the credential'
' helper for. Container Registry is a service for storing private '
'container images. For available registries, see '
'[](https://cloud.google.com/container-registry/docs/pushing-and-pulling#add-registry).'
)
parser.add_argument(
'--include-artifact-registry',
action='store_true',
help='Whether to include all Artifact Registry domains.',
hidden=True)
def Run(self, args):
"""Run the configure-docker command."""
if not self.DockerCredentialGcloudExists():
log.warning('`docker-credential-gcloud` not in system PATH.\n'
'gcloud\'s Docker credential helper can be configured but '
'it will not work until this is corrected.')
current_config = cred_utils.Configuration.ReadFromDisk()
if self.DockerExists():
if not current_config.SupportsRegistryHelpers():
raise ConfigureDockerError(
'Invalid Docker version: The version of your Docker client is '
'[{}]; version [{}] or higher is required to support Docker '
'credential helpers.'.format(
current_config.DockerVersion(),
cred_utils.MIN_DOCKER_CONFIG_HELPER_VERSION))
else:
log.warning(
'`docker` not in system PATH.\n'
'`docker` and `docker-credential-gcloud` need to be in the same PATH '
'in order to work correctly together.\n'
'gcloud\'s Docker credential helper can be configured but '
'it will not work until this is corrected.')
current_helpers = current_config.GetRegisteredCredentialHelpers()
current_helper_map = {}
if current_helpers:
log.warning('Your config file at [{0}] contains these credential helper '
'entries:\n\n{1}'.format(
current_config.path,
json.dumps(current_helpers, indent=2)))
current_helper_map = current_helpers[cred_utils.CREDENTIAL_HELPER_KEY]
# Use the value from the argument, otherwise the default list.
if args.registries:
log.status.Print('Adding credentials for: {0}'.format(args.registries))
registries_list = args.registries.split(',')
if properties.VALUES.artifacts.allow_unrecognized_registry.GetBool():
new_helpers = cred_utils.GetGcloudCredentialHelperConfig(
registries_list
)
else:
registries = filter(self.CheckValidRegistry, registries_list)
new_helpers = cred_utils.GetGcloudCredentialHelperConfig(registries)
else:
# If include-artifact-registry is set, add all GCR and AR repos, otherwise
# just GCR repos.
if args.include_artifact_registry:
log.status.Print('Adding credentials for all GCR and AR repositories.')
else:
log.status.Print('Adding credentials for all GCR repositories.')
log.warning('A long list of credential helpers may cause delays running '
'\'docker build\'. We recommend passing the registry name to '
'configure only the registry you are using.')
new_helpers = cred_utils.GetGcloudCredentialHelperConfig(
None, args.include_artifact_registry)
# Merge in the new settings so that existing configs are preserved.
merged_helper_map = current_helper_map.copy()
merged_helper_map.update(new_helpers[cred_utils.CREDENTIAL_HELPER_KEY])
if current_helper_map == merged_helper_map:
log.status.Print(
'gcloud credential helpers already registered correctly.')
return
merged_helpers = {cred_utils.CREDENTIAL_HELPER_KEY: merged_helper_map}
console_io.PromptContinue(
message='After update, the following will be written to your Docker '
'config file located at [{0}]:\n {1}'.format(
current_config.path, json.dumps(merged_helpers, indent=2)),
cancel_on_no=True)
current_config.RegisterCredentialHelpers(merged_helper_map)
log.status.Print('Docker configuration file updated.')
def CheckValidRegistry(self, registry):
if registry not in cred_utils.SupportedRegistries():
log.warning('{0} is not a supported registry'.format(registry))
return False
return True

View File

@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*- #
# Copyright 2015 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command to describe credentials."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import json
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.auth import exceptions
from googlecloudsdk.core.credentials import creds
from googlecloudsdk.core.credentials import store
@base.Hidden
class Describe(base.DescribeCommand):
"""Describes credentials.
Returns internal details about specified credentials.
## EXAMPLES
To describe existing credentials, run:
$ {command} ACCOUNT_NAME
"""
@staticmethod
def Args(parser):
"""Adds args for this command."""
parser.add_argument(
'account_name',
help='Name of the account to describe')
def Run(self, args):
credential = store.Load(args.account_name, use_google_auth=True)
if not credential:
raise exceptions.CredentialsNotFound(
'The credentials for account [{0}] do not exist.'
.format(args.account_name))
if creds.IsGoogleAuthCredentials(credential):
json_cred = creds.SerializeCredsGoogleAuth(credential)
return json.loads(json_cred)
return json.loads(credential.to_json())

View File

@@ -0,0 +1,98 @@
# -*- coding: utf-8 -*- #
# Copyright 2013 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A docker credential helper that provides credentials for GCR registries."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import sys
from googlecloudsdk.calliope import base
from googlecloudsdk.core import exceptions
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core.credentials import creds as c_creds
from googlecloudsdk.core.credentials import exceptions as creds_exceptions
from googlecloudsdk.core.credentials import store as c_store
from googlecloudsdk.core.docker import credential_utils
TOKEN_MIN_LIFETIME = '3300s' # 55 minutes
@base.Hidden
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.GA)
class DockerHelper(base.Command):
"""A Docker credential helper to provide access to GCR repositories."""
GET = 'get'
LIST = 'list'
@staticmethod
def Args(parser):
parser.add_argument('method', help='The docker credential helper method.')
# Docker expects the result in json format.
parser.display_info.AddFormat('json')
def Run(self, args):
"""Run the helper command."""
if args.method == DockerHelper.LIST:
return {
# This tells Docker that the secret will be an access token, not a
# username/password.
# Docker normally expects a prefixed 'https://' for auth configs.
('https://' + url): '_dcgcloud_token'
for url in credential_utils.DefaultAuthenticatedRegistries()
}
elif args.method == DockerHelper.GET:
# docker credential helper protocol expects that error is printed to
# stdout.
try:
cred = c_store.Load(use_google_auth=True)
except creds_exceptions.NoActiveAccountException:
log.Print('You do not currently have an active account selected. '
'See https://cloud.google.com/sdk/docs/authorizing for more '
'information.')
sys.exit(1)
c_store.RefreshIfExpireWithinWindow(cred, window=TOKEN_MIN_LIFETIME)
url = sys.stdin.read().strip()
if not properties.VALUES.artifacts.allow_unrecognized_registry.GetBool():
if (url.replace('https://', '',
1) not in credential_utils.SupportedRegistries()):
raise exceptions.Error(
'Repository url [{url}] is not supported'.format(url=url))
# Putting an actual username in the response doesn't work. Docker will
# then prompt for a password instead of using the access token.
token = (
cred.token
if c_creds.IsGoogleAuthCredentials(cred) else cred.access_token)
return {
'Secret': token,
'Username': '_dcgcloud_token',
}
# Don't print anything if we are not supporting the given action.
# The credential helper protocol also support 'store' and 'erase' actions
# that don't apply here. The full spec can be found here:
# https://github.com/docker/docker-credential-helpers#development
args.GetDisplayInfo().AddFormat('none')
return None

View File

@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The enterprise-certificate-config command group for the auth CLI."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(
base.ReleaseTrack.GA, base.ReleaseTrack.BETA, base.ReleaseTrack.ALPHA
)
class EnterpriseCertificateConfig(base.Group):
"""Manage enterprise certificate configurations.
Currently only create is implemented.
The {command} group lets you create enterprise certificate configurations.
This configuration will be used by gcloud to communicate with the
enterprise-certificate-proxy.
See more details at `gcloud topic client-certificate`.
"""

View File

@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The enterprise-certificate-config create command group for the auth CLI."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(
base.ReleaseTrack.GA, base.ReleaseTrack.BETA, base.ReleaseTrack.ALPHA
)
class EnterpriseCertificateConfig(base.Group):
"""Create enterprise certificate configurations.
The {command} group lets you create enterprise certificate configurations.
This configuration will be used by gcloud to communicate with the
enterprise-certificate-proxy.
See more details at `gcloud topic client-certificate`.
"""

View File

@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command to create an enterprise-certificate configuration file for Linux."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import textwrap
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.auth import enterprise_certificate_config
from googlecloudsdk.command_lib.auth.flags import AddCommonEnterpriseCertConfigFlags
class CreateLinuxConfig(base.CreateCommand):
"""Create an enterprise-certificate configuration file for Linux.
This command creates a configuration file used by gcloud to use the
enterprise-certificate-proxy component for mTLS.
"""
detailed_help = {
'EXAMPLES': textwrap.dedent(
"""\
To create a credential configuration run:
$ {command} --module=$PATH_TO_PKCS11_MODULE --slot=$PKCS11_SLOT_ID --label=$PKCS11_OBJECT_LABEL --user-pin=$PKCS11_USER_PIN
"""
),
}
@classmethod
def Args(cls, parser):
AddCommonEnterpriseCertConfigFlags(parser)
parser.add_argument(
'--module',
help='The full file path to the PKCS #11 module.',
required=True,
)
parser.add_argument(
'--slot',
help='The PKCS #11 slot containing the target credentials.',
required=True,
)
parser.add_argument(
'--label',
help='The PKCS #11 label for the target credentials. '
+ 'The certificate, public key, and private key MUST have '
+ 'the same label. enterprise-certificate-proxy will use all '
+ 'three objects.',
required=True,
)
parser.add_argument(
'--user-pin',
help='The user pin used to login to the PKCS #11 module. '
+ 'If there is no user pin leave this field empty.',
)
def Run(self, args):
enterprise_certificate_config.create_config(
enterprise_certificate_config.ConfigType.PKCS11, **vars(args)
)

View File

@@ -0,0 +1,68 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command to create an enterprise-certificate configuration file for MacOS."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import textwrap
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.auth import enterprise_certificate_config
from googlecloudsdk.command_lib.auth.flags import AddCommonEnterpriseCertConfigFlags
@base.DefaultUniverseOnly
class CreateMacOSConfig(base.CreateCommand):
"""Create an enterprise-certificate configuration file for MacOS.
This command creates a configuration file used by gcloud to use the
enterprise-certificate-proxy component for mTLS.
"""
detailed_help = {
'EXAMPLES': textwrap.dedent(
"""\
To create a credential configuration run:
$ {command} --issuer=$CERT_ISSUER
"""
),
}
@classmethod
def Args(cls, parser):
AddCommonEnterpriseCertConfigFlags(parser)
parser.add_argument(
'--issuer',
help='The certificate issuer.',
required=True,
)
parser.add_argument(
'--keychain-type',
default='all',
help=(
'Specify the target keychain(s) for certificate lookup.'
'Accepted values are "login", "system", or "all". If omitted,'
'defaults to "all". Use "all" to include custom keychains.'
),
required=False,
)
def Run(self, args):
enterprise_certificate_config.create_config(
enterprise_certificate_config.ConfigType.KEYCHAIN, **vars(args)
)

View File

@@ -0,0 +1,69 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command to create an enterprise-certificate configuration file for Windows."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import textwrap
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.auth import enterprise_certificate_config
from googlecloudsdk.command_lib.auth.flags import AddCommonEnterpriseCertConfigFlags
class CreateWindowsConfig(base.CreateCommand):
"""Create an enterprise-certificate configuration file for Windows.
This command creates a configuration file used by gcloud to use the
enterprise-certificate-proxy component for mTLS.
"""
detailed_help = {
'EXAMPLES': textwrap.dedent(
"""\
To create a credential configuration run:
$ {command} --issuer=$CERT_ISSUER --store=$STORE --provider=$PROVIDER
"""
),
}
@classmethod
def Args(cls, parser):
AddCommonEnterpriseCertConfigFlags(parser)
parser.add_argument(
'--issuer',
help='The certificate issuer.',
required=True,
)
parser.add_argument(
'--store',
help='The Windows secure store.',
required=True,
)
parser.add_argument(
'--provider',
help='The Windows secure store provider.',
required=True,
)
def Run(self, args):
enterprise_certificate_config.create_config(
enterprise_certificate_config.ConfigType.MYSTORE, **vars(args)
)

View File

@@ -0,0 +1,237 @@
# -*- coding: utf-8 -*- #
# Copyright 2013 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A git credential helper that provides Google git repository passwords.
Reads a session from stdin that looks a lot like:
protocol=https
host=code.google.com
And writes out a session to stdout that looks a lot like:
username=me
password=secret
Errors will be reported on stderr.
Note that spaces may be part of key names so, for example, "protocol" must not
be proceeded by leading spaces.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import os
import re
import subprocess
import sys
import textwrap
from googlecloudsdk.api_lib.auth import exceptions as auth_exceptions
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions as c_exc
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core.credentials import creds as c_creds
from googlecloudsdk.core.credentials import exceptions as creds_exceptions
from googlecloudsdk.core.credentials import store as c_store
from googlecloudsdk.core.util import files
from googlecloudsdk.core.util import platforms
from oauth2client import client
import six
_KEYVAL_RE = re.compile(r'(.+)=(.*)')
_BLANK_LINE_RE = re.compile(r'^ *$')
@base.Hidden
@base.DefaultUniverseOnly
class GitHelper(base.Command):
"""A git credential helper to provide access to Google git repositories."""
GET = 'get'
STORE = 'store'
METHODS = [GET, STORE]
GOOGLESOURCE = 'googlesource.com'
@staticmethod
def Args(parser):
parser.add_argument('method',
help='The git credential helper method.')
parser.add_argument('--ignore-unknown',
action='store_true',
help=('Produce no output and exit with 0 when given '
'an unknown method (e.g. store) or host.'))
@c_exc.RaiseErrorInsteadOf(auth_exceptions.AuthenticationError, client.Error)
def Run(self, args):
"""Run the helper command."""
# Disable self signed jwt for this command.
properties.VALUES.auth.service_account_use_self_signed_jwt.Set(False)
if args.method not in GitHelper.METHODS:
if args.ignore_unknown:
return
raise auth_exceptions.GitCredentialHelperError(
'Unexpected method [{meth}]. One of [{methods}] expected.'
.format(meth=args.method, methods=', '.join(GitHelper.METHODS)))
info = self._ParseInput()
credentialed_domains = [
'source.developers.google.com',
GitHelper.GOOGLESOURCE, # Requires a different username value.
]
credentialed_domains_suffix = [
'.sourcemanager.dev',
'.blueoryx.dev',
'.developerconnect.dev',
'.'+GitHelper.GOOGLESOURCE,
]
extra = properties.VALUES.core.credentialed_hosted_repo_domains.Get()
if extra:
credentialed_domains.extend(extra.split(','))
host = info.get('host')
def _ValidateHost(host):
if host in credentialed_domains:
return True
for suffix in credentialed_domains_suffix:
if host.endswith(suffix):
return True
return False
if not _ValidateHost(host):
if not args.ignore_unknown:
raise auth_exceptions.GitCredentialHelperError(
'Unknown host [{host}].'.format(host=host))
return
if args.method == GitHelper.GET:
account = properties.VALUES.core.account.Get()
try:
cred = c_store.Load(account, use_google_auth=True)
c_store.Refresh(cred)
except creds_exceptions.Error as e:
sys.stderr.write(textwrap.dedent("""\
ERROR: {error}
Run 'gcloud auth login' to log in.
""".format(error=six.text_type(e))))
return
self._CheckNetrc()
# For googlesource.com, any username beginning with "git-" is accepted
# and the identity of the user is extracted from the token server-side.
if (host == GitHelper.GOOGLESOURCE
or host.endswith('.'+GitHelper.GOOGLESOURCE)):
sent_account = 'git-account'
else:
sent_account = account
if c_creds.IsOauth2ClientCredentials(cred):
access_token = cred.access_token
else:
access_token = cred.token
sys.stdout.write(
textwrap.dedent("""\
username={username}
password={password}
""").format(username=sent_account, password=access_token))
elif args.method == GitHelper.STORE:
# On OSX, there is an additional credential helper that gets called before
# ours does. When we return a token, it gets cached there. Git continues
# to get it from there first until it expires. That command then fails,
# and the token is deleted, but it does not retry the operation. The next
# command gets a new token from us and it starts working again, for an
# hour. This erases our credential from the other cache whenever 'store'
# is called on us. Because they are called first, the token will already
# be stored there, and so we can successfully erase it to prevent caching.
if (platforms.OperatingSystem.Current() ==
platforms.OperatingSystem.MACOSX):
log.debug('Clearing OSX credential cache.')
try:
input_string = 'protocol={protocol}\nhost={host}\n\n'.format(
protocol=info.get('protocol'), host=info.get('host'))
log.debug('Calling erase with input:\n%s', input_string)
p = subprocess.Popen(['git-credential-osxkeychain', 'erase'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(out, err) = p.communicate(input_string)
if p.returncode:
log.debug(
'Failed to clear OSX keychain:\nstdout: {%s}\nstderr: {%s}',
out, err)
# pylint:disable=broad-except, This can fail and should only be done as
# best effort.
except Exception as e:
log.debug('Failed to clear OSX keychain', exc_info=True)
def _ParseInput(self):
"""Parse the fields from stdin.
Returns:
{str: str}, The parsed parameters given on stdin.
"""
info = {}
for line in sys.stdin:
if _BLANK_LINE_RE.match(line):
continue
match = _KEYVAL_RE.match(line)
if not match:
raise auth_exceptions.GitCredentialHelperError(
'Invalid input line format: [{format}].'
.format(format=line.rstrip('\n')))
key, val = match.groups()
info[key] = val.strip()
if 'protocol' not in info:
raise auth_exceptions.GitCredentialHelperError(
'Required key "protocol" missing.')
if 'host' not in info:
raise auth_exceptions.GitCredentialHelperError(
'Required key "host" missing.')
if info.get('protocol') != 'https':
raise auth_exceptions.GitCredentialHelperError(
'Invalid protocol [{p}]. "https" expected.'
.format(p=info.get('protocol')))
return info
def _CheckNetrc(self):
"""Warn on stderr if ~/.netrc contains redundant credentials."""
def Check(p):
"""Warn about other credential helpers that will be ignored."""
if not os.path.exists(p):
return
try:
data = files.ReadFileContents(p)
if 'source.developers.google.com' in data:
sys.stderr.write(textwrap.dedent("""\
You have credentials for your Google repository in [{path}]. This repository's
git credential helper is set correctly, so the credentials in [{path}] will not
be used, but you may want to remove them to avoid confusion.
""".format(path=p)))
# pylint:disable=broad-except, If something went wrong, forget about it.
except Exception:
pass
Check(files.ExpandHomeDir(os.path.join('~', '.netrc')))
Check(files.ExpandHomeDir(os.path.join('~', '_netrc')))

View File

@@ -0,0 +1,129 @@
# -*- coding: utf-8 -*- #
# Copyright 2013 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command to list the available accounts."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core.credentials import store as c_store
class List(base.ListCommand):
# pylint: disable=g-docstring-has-escape
"""Lists credentialed accounts.
Lists accounts whose credentials have been obtained using `gcloud init`,
`gcloud auth login` and `gcloud auth activate-service-account`, and shows
which account is active. The active account is used by gcloud and other Google
Cloud CLI tools to access Google Cloud Platform. While there is no limit on
the number of accounts with stored credentials, there is only one active
account.
## EXAMPLES
To set an existing account to be the current active account, run:
$ gcloud config set core/account your-email-account@gmail.com
If you don't have an existing account, create one using:
$ gcloud init
To list the active account name:
$ gcloud auth list --filter=status:ACTIVE --format="value(account)"
To list the inactive account names with prefix `test`:
$ gcloud auth list --filter="-status:ACTIVE account:test*" \
--format="value(account)"
"""
@staticmethod
def Args(parser):
base.URI_FLAG.RemoveFromParser(parser)
parser.add_argument(
'--filter-account',
help="""\
List only credentials for one account. Use
--filter="account~_PATTERN_" to select accounts that match
_PATTERN_.""")
def Run(self, args):
"""Run the 'gcloud auth list' command to list the accounts.
Args:
args: an argparse namespace. All the arguments that were provided to this
command invocation (i.e. group and command arguments combined).
Returns:
[googlecloudsdk.core.credentials.store.AcctInfo] or
[googlecloudsdk.core.credentials.store.AcctInfoWithUniverseDomain]: A
list of AcctInfo objects if all accounts are from googleapis.com,
otherwise a list of AcctInfoWithUniverseDomain objects.
"""
account_info_list = c_store.AllAccountsWithUniverseDomains()
if args.filter_account:
account_info_list = [
account_info
for account_info in account_info_list
if account_info.account == args.filter_account
]
# If any of the accounts are non-GDU, then we should output the universe
# domain column.
show_universe_domain = False
for account_info in account_info_list:
if (
account_info.universe_domain
!= properties.VALUES.core.universe_domain.default
):
show_universe_domain = True
break
if show_universe_domain:
# Use the format with UNIVERSE_DOMAIN column.
args.GetDisplayInfo().AddFormat(
c_store.ACCOUNT_TABLE_WITH_UNIVERSE_DOMAIN_FORMAT
)
else:
# Convert to AcctInfo list, which doesn't contain universe domain info.
account_info_list = [
c_store.AcctInfo(account_info.account, account_info.status)
for account_info in account_info_list
]
# Use the format without UNIVERSE_DOMAIN column.
args.GetDisplayInfo().AddFormat(c_store.ACCOUNT_TABLE_FORMAT)
return account_info_list
def Epilog(self, resources_were_displayed):
if resources_were_displayed:
log.status.Print("""
To set the active account, run:
$ gcloud config set account `ACCOUNT`
""")
else:
log.status.Print("""
No credentialed accounts.
To login, run:
$ gcloud auth login `ACCOUNT`
""")

View File

@@ -0,0 +1,492 @@
# -*- coding: utf-8 -*- #
# Copyright 2013 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The auth command gets tokens via oauth2."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import textwrap
from googlecloudsdk.api_lib.auth import external_account as auth_external_account
from googlecloudsdk.api_lib.auth import service_account as auth_service_account
from googlecloudsdk.api_lib.auth import util as auth_util
from googlecloudsdk.calliope import actions
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions as calliope_exceptions
from googlecloudsdk.command_lib.auth import auth_util as command_auth_util
from googlecloudsdk.command_lib.auth import flags as auth_flags
from googlecloudsdk.command_lib.auth import workforce_login_config as workforce_login_config_util
from googlecloudsdk.core import config
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.credentials import creds as c_creds
from googlecloudsdk.core.credentials import devshell as c_devshell
from googlecloudsdk.core.credentials import exceptions as creds_exceptions
from googlecloudsdk.core.credentials import gce as c_gce
from googlecloudsdk.core.credentials import store as c_store
def ShouldContinueLogin(cred_config):
"""Prompts users if they try to login in managed environments.
Args:
cred_config: Json object loaded from the file specified in --cred-file.
Returns:
True if users want to continue the login command.
"""
if c_devshell.IsDevshellEnvironment():
message = textwrap.dedent("""
You are already authenticated with gcloud when running
inside the Cloud Shell and so do not need to run this
command. Do you wish to proceed anyway?
""")
return console_io.PromptContinue(message=message)
elif (c_gce.Metadata().connected and
not auth_external_account.IsExternalAccountConfig(cred_config)):
message = textwrap.dedent("""
You are running on a Google Compute Engine virtual machine.
It is recommended that you use service accounts for authentication.
You can run:
$ gcloud config set account `ACCOUNT`
to switch accounts if necessary.
Your credentials may be visible to others with access to this
virtual machine. Are you sure you want to authenticate with
your personal account?
""")
return console_io.PromptContinue(message=message)
else:
return True
def GetScopes(args):
scopes = config.CLOUDSDK_SCOPES
# Add REAUTH scope in case the user has 2fact activated.
# This scope is only used here and when refreshing the access token.
scopes += (config.REAUTH_SCOPE,)
if args.enable_gdrive_access:
scopes += (auth_util.GOOGLE_DRIVE_SCOPE,)
return scopes
def ShouldUseCachedCredentials(args, scopes):
"""If the login should use the locally cached account."""
if (not args.account) or args.force:
return False
try:
creds = c_store.Load(account=args.account, scopes=scopes)
except creds_exceptions.Error:
return False
if not creds:
return False
# Account already has valid creds, just switch to it.
log.warning('Re-using locally stored credentials for [{}]. '
'To fetch new credentials, re-run the command with the '
'`--force` flag.'.format(args.account))
return True
@base.UniverseCompatible
class Login(base.Command):
"""Authorize gcloud to access the Cloud Platform with Google user credentials.
Obtains access credentials for your user account via a web-based authorization
flow. When this command completes successfully, it sets the active account
in the current configuration to the account specified. If no configuration
exists, it creates a configuration named default.
If valid credentials for an account are already available from a prior
authorization, the account is set to active without rerunning the flow.
Use `gcloud auth list` to view credentialed accounts.
If you'd rather authorize without a web browser but still interact with
the command line, use the `--no-browser` flag. To authorize without
a web browser and non-interactively, create a service account with the
appropriate scopes using the
[Google Cloud Console](https://console.cloud.google.com) and use
`gcloud auth activate-service-account` with the corresponding JSON key file.
In addition to Google user credentials, authorization using workload identity
federation, workforce identity federation, or service account keys is also
supported.
For authorization with external accounts or service accounts, the
`--cred-file` flag must be specified with the path
to the workload identity credential configuration file or service account key
file (JSON).
Login with workload and workforce identity federation is also supported in
both gsutil and bq. This command is the recommended way of using external
accounts.
For more information on workload identity federation, see:
[](https://cloud.google.com/iam/docs/workload-identity-federation).
For more information on workforce identity federation, see:
[](https://cloud.google.com/iam/docs/workforce-identity-federation).
For more information on authorization and credential types, see:
[](https://cloud.google.com/sdk/docs/authorizing).
## EXAMPLES
To obtain access credentials for your user account, run:
$ {command}
To obtain access credentials using workload or workforce identity federation,
run:
$ {command} --cred-file=/path/to/configuration/file
To obtain access credentials using a browser-based sign-in flow with workforce
identity federation, run:
$ {command} --login-config=/path/to/configuration/file
"""
_remote_login_with_auth_proxy = False
@staticmethod
def Args(parser):
"""Set args for gcloud auth."""
parser.add_argument(
'--activate',
action='store_true',
default=True,
help='Set the new account to active.',
)
# --do-not-activate for (hidden) backwards compatibility.
parser.add_argument(
'--do-not-activate',
action='store_false',
dest='activate',
hidden=True,
help='THIS ARGUMENT NEEDS HELP TEXT.',
)
parser.add_argument(
'--brief', action='store_true', help='Minimal user output.'
)
parser.add_argument(
'--force',
action='store_true',
help=(
'Re-run the web authorization flow even if the given account has '
'valid credentials.'
),
)
parser.add_argument(
'--enable-gdrive-access',
action='store_true',
help='Enable Google Drive access.',
)
parser.add_argument(
'--update-adc',
action='store_true',
default=False,
help=(
'Write the obtained credentials to the well-known location for '
'Application Default Credentials (ADC). Run '
'$ gcloud auth application-default --help to learn more about ADC. '
'Any credentials previously generated by '
'`gcloud auth application-default login` will be overwritten.'
),
)
parser.add_argument(
'--add-quota-project-to-adc',
action='store_true',
default=False,
hidden=True,
help=(
"Read the project from gcloud's context and write to application "
'default credentials as the quota project. Use this flag only '
'when --update-adc is specified.'
),
)
parser.add_argument(
'account',
nargs='?',
help=(
'User account used for authorization. When the account specified '
'has valid credentials in the local credential store these '
'credentials will be re-used, otherwise a new credential will be '
'fetched. If you need to fetch a new credential for an account '
'with valid credentials stored, run the command with the --force '
'flag.'
)
)
parser.add_argument(
'--cred-file',
help=(
'Path to the external account configuration file (workload identity'
' pool, generated by the Cloud Console or `gcloud iam'
' workload-identity-pools create-cred-config`) or service account'
' credential key file (JSON).'
),
)
parser.add_argument(
'--login-config',
help=(
'Path to the workforce identity federation login configuration'
' file which can be generated using the `gcloud iam'
' workforce-pools create-login-config` command.'
),
action=actions.StoreProperty(properties.VALUES.auth.login_config_file),
)
auth_flags.AddRemoteLoginFlags(parser)
parser.display_info.AddFormat('none')
def Run(self, args):
"""Run the authentication command."""
if args.cred_file:
if args.login_config:
raise calliope_exceptions.ConflictingArgumentsException(
'--login-config cannot be specified with --cred-file.')
cred_file = auth_util.GetCredentialsConfigFromFile(args.cred_file)
else:
cred_file = None
scopes = GetScopes(args)
if not ShouldContinueLogin(cred_file):
return None
if args.cred_file:
return LoginWithCredFileConfig(cred_file, scopes, args.project,
args.activate, args.brief, args.update_adc,
args.add_quota_project_to_adc,
args.account)
if ShouldUseCachedCredentials(args, scopes):
creds = c_store.Load(account=args.account, scopes=scopes)
return LoginAs(args.account, creds, args.project, args.activate,
args.brief, args.update_adc, args.add_quota_project_to_adc)
# No valid creds, do the web flow.
flow_params = dict(
no_launch_browser=not args.launch_browser,
no_browser=not args.browser,
remote_bootstrap=args.remote_bootstrap)
# 1. Try the 3PI web flow with --no-browser:
# This could be a 3PI flow initiated via --no-browser.
# If provider_name is present, then this is the 3PI flow.
# We can start the flow as is as the remote_bootstrap value will be used.
if args.remote_bootstrap and 'provider_name' in args.remote_bootstrap:
creds = auth_util.DoInstalledAppBrowserFlowGoogleAuth(
config.CLOUDSDK_EXTERNAL_ACCOUNT_SCOPES,
auth_proxy_redirect_uri='https://sdk.cloud.google/authcode.html',
**flow_params
)
universe_domain_property = properties.VALUES.core.universe_domain
if (
creds
and hasattr(creds, 'universe_domain')
and creds.universe_domain != universe_domain_property.Get()
):
# Get the account and handle universe domain conflict.
account = auth_external_account.GetExternalAccountId(creds)
auth_util.HandleUniverseDomainConflict(creds.universe_domain, account)
return
# 2. Try the 3PI web flow with a login configuration file.
login_config_file = workforce_login_config_util.GetWorkforceLoginConfig()
if login_config_file:
if args.update_adc:
raise calliope_exceptions.ConflictingArgumentsException(
'--update-adc cannot be used in a third party login flow. '
'Please use `gcloud auth application-default login`.')
if args.add_quota_project_to_adc:
raise calliope_exceptions.ConflictingArgumentsException(
'--add-quota-project-to-adc cannot be used in a third party login '
'flow.')
creds = workforce_login_config_util.DoWorkforceHeadfulLogin(
login_config_file,
**flow_params)
account = auth_external_account.GetExternalAccountId(creds)
c_store.Store(creds, account, config.CLOUDSDK_EXTERNAL_ACCOUNT_SCOPES)
return LoginAs(account, creds, args.project, args.activate, args.brief,
args.update_adc, args.add_quota_project_to_adc)
# 3. Try the 1P web flow.
creds = auth_util.DoInstalledAppBrowserFlowGoogleAuth(
scopes,
auth_proxy_redirect_uri='https://sdk.cloud.google.com/authcode.html',
**flow_params)
if not creds:
return
account = command_auth_util.ExtractAndValidateAccount(args.account, creds)
# We got new creds, and they are for the correct user.
c_store.Store(creds, account, scopes)
return LoginAs(account, creds, args.project, args.activate, args.brief,
args.update_adc, args.add_quota_project_to_adc)
def LoginWithCredFileConfig(cred_config, scopes, project, activate, brief,
update_adc, add_quota_project_to_adc, args_account):
"""Login with the provided configuration loaded from --cred-file.
Args:
cred_config (Mapping): The configuration dictionary representing the
credentials. This is loaded from the --cred-file argument.
scopes (Tuple[str]): The default OAuth scopes to use.
project (Optional[str]): The optional project ID to activate / persist.
activate (bool): Whether to set the new account associated with the
credentials to active.
brief (bool): Whether to use minimal user output.
update_adc (bool): Whether to write the obtained credentials to the
well-known location for Application Default Credentials (ADC).
add_quota_project_to_adc (bool): Whether to add the quota project to the
application default credentials file.
args_account (Optional[str]): The optional ACCOUNT argument. When provided,
this should match the account ID on the authenticated credentials.
Returns:
google.auth.credentials.Credentials: The authenticated stored credentials.
Raises:
calliope_exceptions.ConflictingArgumentsException: If conflicting arguments
are provided.
calliope_exceptions.InvalidArgumentException: If invalid arguments are
provided.
"""
# Remove reauth scope (only applicable to 1P user accounts).
scopes = tuple(x for x in scopes if x != config.REAUTH_SCOPE)
# Reject unsupported arguments.
if add_quota_project_to_adc:
raise calliope_exceptions.ConflictingArgumentsException(
'[--add-quota-project-to-adc] cannot be specified with --cred-file')
if auth_external_account.IsExternalAccountConfig(cred_config):
creds = auth_external_account.CredentialsFromAdcDictGoogleAuth(cred_config)
account = auth_external_account.GetExternalAccountId(creds)
# Check interactive mode, if true, inject the _tokeninfo_username.
# TODO(b/258323440): Once the SDK can handle the retrieving token info, we
# don't need this injection any more.
if hasattr(creds, 'interactive') and creds.interactive:
setattr(creds, '_tokeninfo_username', account)
elif auth_service_account.IsServiceAccountConfig(cred_config):
creds = auth_service_account.CredentialsFromAdcDictGoogleAuth(cred_config)
account = creds.service_account_email
else:
raise calliope_exceptions.InvalidArgumentException(
'--cred-file',
'Only external account or service account JSON credential file types '
'are supported.')
if args_account and args_account != account:
raise calliope_exceptions.InvalidArgumentException(
'ACCOUNT',
'The given account name does not match the account name in the '
'credential file. This argument can be omitted when using '
'credential files.')
# Check if account already exists in storage.
try:
# prevent_refresh must be set to True. We don't actually want to
# use the existing credentials, but check for their presence. If a
# refresh occurs but the credentials are no longer valid, this
# will cause gcloud to crash.
exist_creds = c_store.Load(
account=account, scopes=scopes, prevent_refresh=True)
except creds_exceptions.Error:
exist_creds = None
if exist_creds and exist_creds.universe_domain == creds.universe_domain:
message = textwrap.dedent("""
You are already authenticated with '%s'.
Do you wish to proceed and overwrite existing credentials?
""")
answer = console_io.PromptContinue(message=message % account, default=True)
if not answer:
return None
# Store credentials and activate if --activate is true.
c_store.Store(creds, account, scopes=scopes)
return LoginAs(account, creds, project, activate, brief, update_adc, False)
def LoginAs(account, creds, project, activate, brief, update_adc,
add_quota_project_to_adc):
"""Logs in with valid credentials."""
if hasattr(creds, 'universe_domain'):
auth_util.HandleUniverseDomainConflict(creds.universe_domain, account)
_ValidateADCFlags(update_adc, add_quota_project_to_adc)
if update_adc:
_UpdateADC(creds, add_quota_project_to_adc)
if not activate:
return creds
properties.PersistProperty(properties.VALUES.core.account, account)
if project:
properties.PersistProperty(properties.VALUES.core.project, project)
if not brief:
if c_creds.IsExternalAccountCredentials(creds):
confirmation_msg = (
'Authenticated with external account credentials for: [{0}].'.format(
account))
elif c_creds.IsExternalAccountUserCredentials(creds):
confirmation_msg = (
'Authenticated with external account user credentials for: '
'[{0}].'.format(account))
elif c_creds.IsServiceAccountCredentials(creds):
confirmation_msg = (
'Authenticated with service account credentials for: [{0}].'.format(
account))
elif c_creds.IsExternalAccountAuthorizedUserCredentials(creds):
confirmation_msg = (
'Authenticated with external account authorized user credentials '
'for: [{0}].'.format(account))
else:
confirmation_msg = 'You are now logged in as [{0}].'.format(account)
log.status.write(
'\n{confirmation_msg}\n'
'Your current project is [{project}]. You can change this setting '
'by running:\n $ gcloud config set project PROJECT_ID\n'.format(
confirmation_msg=confirmation_msg,
project=properties.VALUES.core.project.Get()))
return creds
def _UpdateADC(creds, add_quota_project_to_adc):
"""Updates the ADC json with the credentials creds."""
old_adc_json = command_auth_util.GetADCAsJson()
command_auth_util.WriteGcloudCredentialsToADC(creds, add_quota_project_to_adc)
new_adc_json = command_auth_util.GetADCAsJson()
if new_adc_json and new_adc_json != old_adc_json:
adc_msg = '\nApplication Default Credentials (ADC) were updated.'
quota_project = command_auth_util.GetQuotaProjectFromADC()
if quota_project:
adc_msg = adc_msg + (
"\n'{}' is added to ADC as the quota project.\nTo "
'just update the quota project in ADC, use $gcloud auth '
'application-default set-quota-project.'.format(quota_project))
log.status.Print(adc_msg)
def _ValidateADCFlags(update_adc, add_quota_project_to_adc):
if not update_adc and add_quota_project_to_adc:
raise calliope_exceptions.InvalidArgumentException(
'--add-quota-project-to-adc',
'--add-quota-project-to-adc cannot be specified without specifying '
'--update-adc.'
)

View File

@@ -0,0 +1,263 @@
# -*- coding: utf-8 -*- #
# Copyright 2013 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A command that prints access tokens."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import textwrap
import time
from google.auth import credentials
from google.auth import exceptions as google_auth_exceptions
from googlecloudsdk.api_lib.auth import exceptions as auth_exceptions
from googlecloudsdk.api_lib.auth import util as auth_util
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions as c_exc
from googlecloudsdk.core import config
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core.credentials import creds as c_creds
from googlecloudsdk.core.credentials import store as c_store
from oauth2client import client
# bq needs this as part of its auth flow to support the Drive scope.
_TRUSTED_SCOPES = list(config.CLOUDSDK_SCOPES) + [auth_util.GOOGLE_DRIVE_SCOPE]
_MTLS_WARNING_INTERVAL_SECONDS = 24 * 60 * 60 # Seconds in a day
_MTLS_WARNING_LAST_SHOWN_CONFIG_KEY = 'mtls_warning_last_shown'
class FakeCredentials(object):
"""An access token container.
oauth2client and google-auth are both supported by gcloud as the auth library.
credentials in oauth2client store the access token in the "access_token"
filed. google-auth stores it in the "token" filed. We use this fake
credentials class to unify them.
"""
def __init__(self, token):
self.token = token
def _ShowMTLSWarningOnceDaily():
"""Checks if mTLS warning should be shown and shows it if needed.
The warning is shown at most once per day.
"""
current_time = time.time()
should_show_warning = True
active_config_store = config.GetConfigStore()
if active_config_store:
last_shown_str = active_config_store.Get(
_MTLS_WARNING_LAST_SHOWN_CONFIG_KEY
)
if last_shown_str:
try:
last_shown = float(last_shown_str)
if (current_time - last_shown) < _MTLS_WARNING_INTERVAL_SECONDS:
should_show_warning = False
except ValueError:
# If the stored value is not a valid float,
# treat it as if the warning has never been shown.
log.debug(
'Could not parse float from [%s] for %s.',
last_shown_str,
_MTLS_WARNING_LAST_SHOWN_CONFIG_KEY,
)
if should_show_warning:
log.warning(textwrap.dedent("""\
Warning: This access token is for the gcloud CLI tool itself and is \
bound to its specific client ID, which requires Certificate Based Access (CBA). \
This means it can only be used with CBA-enabled API endpoints.
If you need a token for your own application code or scripts, you \
should use Application Default Credentials (ADC). Obtain an ADC \
token by running:
1. gcloud auth application-default login (if you haven't already)
2. gcloud auth application-default print-access-token
ADC tokens are intended for local development and do not have the \
same CBA restrictions."""))
# Update the config store with the current timestamp
if active_config_store:
active_config_store.Set(
_MTLS_WARNING_LAST_SHOWN_CONFIG_KEY, str(current_time)
)
@base.UniverseCompatible
class AccessToken(base.Command):
"""Print an access token for the specified account."""
detailed_help = {
'DESCRIPTION': """\
{description}
See [RFC6749](https://tools.ietf.org/html/rfc6749) for more
information about access tokens.
Note that token itself may not be enough to access some services.
If you use the token with curl or similar tools, you may see
permission errors similar to "API has not been used in
project 32555940559 before or it is disabled.". If it happens, you may
need to provide a quota project in the "X-Goog-User-Project" header.
For example,
$ curl -H "X-Goog-User-Project: your-project" -H "Authorization: Bearer $(gcloud auth print-access-token)" foo.googleapis.com
The identity that granted the token must have the
serviceusage.services.use permission on the provided project. See
https://cloud.google.com/apis/docs/system-parameters for more
information.
""",
'EXAMPLES': """\
To print access tokens:
$ {command}
""",
}
@staticmethod
def Args(parser):
parser.add_argument(
'account',
nargs='?',
help=(
'Account to get the access token for. If not specified, '
'the current active account will be used.'
),
)
parser.add_argument(
'--lifetime',
type=arg_parsers.Duration(upper_bound='43200s'),
help=(
'Access token lifetime. The default access token '
'lifetime is 3600 seconds, but you can use this flag to reduce '
'the lifetime or extend it up to 43200 seconds (12 hours). The '
'org policy constraint '
'`constraints/iam.allowServiceAccountCredentialLifetimeExtension`'
' must be set if you want to extend the lifetime beyond 3600 '
'seconds. Note that this flag is for service account '
'impersonation only, so it must be used together with the '
'`--impersonate-service-account` flag.'
),
)
parser.add_argument(
'--scopes',
hidden=True,
type=arg_parsers.ArgList(min_length=1),
metavar='SCOPE',
help=(
'The scopes to authorize for. This flag is supported for user'
' accounts and service accounts only. The list of possible scopes'
' can be found at:'
' https://developers.google.com/identity/protocols/googlescopes.\n\nFor'
' end-user accounts the provided scopes must from [{0}]'.format(
_TRUSTED_SCOPES
)
),
)
parser.display_info.AddFormat('value(token)')
@c_exc.RaiseErrorInsteadOf(
auth_exceptions.AuthenticationError,
client.Error,
google_auth_exceptions.GoogleAuthError,
)
def Run(self, args):
"""Run the helper command."""
if (
properties.VALUES.context_aware.use_client_certificate.GetBool()
and properties.IsInternalUserCheck()
):
_ShowMTLSWarningOnceDaily()
if args.lifetime and not args.impersonate_service_account:
raise c_exc.InvalidArgumentException(
'--lifetime',
'Lifetime flag is for service account impersonation only. It must be '
'used together with the --impersonate-service-account flag.',
)
# Do not auto cache the custom scoped access token. Otherwise, it'll
# affect other gcloud CLIs that depends on cloud-platform scopes.
cache_only_rapt = True if args.scopes else False
cred = c_store.Load(
args.account,
allow_account_impersonation=True,
use_google_auth=True,
cache_only_rapt=cache_only_rapt,
)
# c_store.Load already refreshed the cred, so we don't need to refresh the
# cred unless we need to alter the cred in the code below, for example,
# changing the scopes.
should_refresh_again = False
if args.scopes:
# refresh again due to altered scopes
should_refresh_again = True
cred_type = c_creds.CredentialTypeGoogleAuth.FromCredentials(cred)
if cred_type not in [
c_creds.CredentialTypeGoogleAuth.USER_ACCOUNT,
c_creds.CredentialTypeGoogleAuth.SERVICE_ACCOUNT,
]:
# TODO(b/223649175): Add support for other credential types(e.g GCE).
log.warning(
'`--scopes` flag may not work as expected and will be ignored '
'for account type {}.'.format(cred_type.key)
)
scopes = args.scopes + [auth_util.OPENID, auth_util.USER_EMAIL_SCOPE]
# non user account credential types
if isinstance(cred, credentials.Scoped):
cred = cred.with_scopes(scopes)
else:
requested_scopes = set(args.scopes)
trusted_scopes = set(_TRUSTED_SCOPES)
if not requested_scopes.issubset(trusted_scopes):
raise c_exc.InvalidArgumentException(
'--scopes',
'Invalid scopes value. Please make sure the scopes are from [{0}]'
.format(config.CLOUDSDK_SCOPES),
)
# pylint:disable=protected-access
cred._scopes = scopes
if c_creds.IsImpersonatedAccountCredentials(cred) and args.lifetime:
# refresh again due to altered lifetime
should_refresh_again = True
cred._lifetime = args.lifetime # pylint: disable=protected-access
if should_refresh_again:
c_store.Refresh(cred)
if c_creds.IsOauth2ClientCredentials(cred):
token = cred.access_token
else:
token = cred.token
if not token:
raise auth_exceptions.InvalidCredentialsError(
'No access token could be obtained from the current credentials.'
)
return FakeCredentials(token)

View File

@@ -0,0 +1,134 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A command that prints identity token.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.auth import exceptions as auth_exceptions
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions as c_exc
from googlecloudsdk.command_lib.auth import auth_util
from googlecloudsdk.command_lib.auth import flags
from googlecloudsdk.command_lib.config import config_helper
from googlecloudsdk.core import config
from googlecloudsdk.core.credentials import store as c_store
from oauth2client import client
def _Run(args):
"""Run the print_identity_token command."""
do_impersonation = args.IsSpecified('impersonate_service_account')
cred = c_store.Load(
args.account,
allow_account_impersonation=do_impersonation,
use_google_auth=True)
is_impersonated_account = auth_util.IsImpersonationCredential(cred)
if args.audiences:
if not auth_util.ValidIdTokenCredential(cred):
raise auth_exceptions.WrongAccountTypeError(
'Invalid account type for `--audiences`. '
'Requires valid service account.')
# TODO(b/170394261): Avoid changing constant values.
config.CLOUDSDK_CLIENT_ID = args.audiences
if args.IsSpecified('token_format') or args.IsSpecified('include_license'):
if not auth_util.IsGceAccountCredentials(cred):
raise auth_exceptions.WrongAccountTypeError(
'Invalid account type for `--token-format` or `--include-license`. '
'Requires a valid GCE service account.')
if args.token_format == 'standard':
if args.include_license:
raise auth_exceptions.GCEIdentityTokenError(
'`--include-license` can only be specified when '
'`--token-format=full`.')
if args.IsSpecified('include_email'):
if not auth_util.IsImpersonationCredential(cred):
raise auth_exceptions.WrongAccountTypeError(
'Invalid account type for `--include-email`. '
'Requires an impersonate service account.')
c_store._RefreshGoogleAuthIdToken( # pylint: disable=protected-access
cred,
is_impersonated_credential=is_impersonated_account,
include_email=args.include_email,
gce_token_format=args.token_format,
gce_include_license=args.include_license,
refresh_user_account_credentials=True,
)
credential = config_helper.Credential(cred)
if not credential.id_token:
raise auth_exceptions.InvalidIdentityTokenError(
'No identity token can be obtained from the current credentials.')
return credential
@base.UniverseCompatible
class IdentityToken(base.Command):
"""Print an identity token for the specified account."""
detailed_help = {
'DESCRIPTION':
"""\
{description}
""",
'EXAMPLES':
"""\
To print identity tokens:
$ {command}
To print identity token for account 'foo@example.com' whose audience
is 'https://service-hash-uc.a.run.app', run:
$ {command} foo@example.com --audiences="https://service-hash-uc.a.run.app"
To print identity token for an impersonated service account 'my-account@example.iam.gserviceaccount.com'
whose audience is 'https://service-hash-uc.a.run.app', run:
$ {command} --impersonate-service-account="my-account@example.iam.gserviceaccount.com" --audiences="https://service-hash-uc.a.run.app"
To print identity token of a Compute Engine instance, which includes
project and instance details as well as license codes for images
associated with the instance, run:
$ {command} --token-format=full --include-license
To print identity token for an impersonated service account
'my-account@example.iam.gserviceaccount.com', which includes the email
address of the service account, run:
$ {command} --impersonate-service-account="my-account@example.iam.gserviceaccount.com" --include-email
""",
}
@staticmethod
def Args(parser):
flags.AddAccountArg(parser)
flags.AddAudienceArg(parser)
flags.AddGCESpecificArgs(parser)
flags.AddIncludeEmailArg(parser)
parser.display_info.AddFormat('value(id_token)')
@c_exc.RaiseErrorInsteadOf(auth_exceptions.AuthenticationError, client.Error)
def Run(self, args):
"""Run the print_identity_token command."""
credential = _Run(args)
return credential

View File

@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*- #
# Copyright 2013 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A hidden command that prints access tokens."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.auth import refresh_token
from googlecloudsdk.calliope import base
@base.Hidden
class PrintRefreshToken(base.Command):
"""A command that prints the refresh token for the current account."""
@staticmethod
def Args(parser):
parser.add_argument(
'account', nargs='?',
help=('The account to get the refresh token for. Leave empty for the '
'active account.'))
parser.display_info.AddFormat('value(refresh_token)')
def Run(self, args):
"""Run the helper command."""
return {
'refresh_token': refresh_token.GetForAccount(args.account)
}

View File

@@ -0,0 +1,166 @@
# -*- coding: utf-8 -*- #
# Copyright 2013 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Revoke credentials being used by the CloudSDK."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import os
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions as c_exc
from googlecloudsdk.command_lib.auth import auth_util
from googlecloudsdk.core import config
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core.credentials import creds as c_creds
from googlecloudsdk.core.credentials import exceptions as creds_exceptions
from googlecloudsdk.core.credentials import store as c_store
from googlecloudsdk.core.resource import resource_printer
class Revoke(base.Command):
"""Revoke access credentials for an account.
Revokes credentials for the specified user accounts, service accounts or
external accounts (workload identity pools).
When given a user account, this command revokes the user account token on the
server. If the revocation is successful, or if the token has already been
revoked, this command removes the credential from the local machine.
When given a service account, this command does not revoke the service account
token on the server because service account tokens are not revocable. Instead,
it will print a warning and remove the credential from the local machine. When
used with a service account, this command has only a local effect and the key
associated with the service account is not deleted. This can be done by
executing `gcloud iam service-accounts keys delete` after `revoke`.
When given an external account (workload identity pool), whether impersonated
or not, the command does not revoke the corresponding token on the server
because these tokens are not revocable. The underlying external credentials
(OIDC, AWS, etc.) used to generate these access tokens have to be revoked too,
but gcloud has no control over that. Instead, it will print a warning and
remove the credential from the local machine.
If no account is specified, this command revokes credentials for the currently
active account, effectively logging out of that account. If --all is given,
the behaviors described above apply individually to each account in the list.
You can revoke credentials when you want to prevent gcloud and other Google
Cloud CLI tools from using the specified account. You do not need to revoke
credentials to switch between accounts.
"""
@staticmethod
def Args(parser):
parser.add_argument('accounts', nargs='*',
help='Accounts whose credentials are to be revoked.')
parser.add_argument('--all', action='store_true',
help='Revoke credentials for all accounts.')
parser.display_info.AddFormat('list[title="Revoked credentials:"]')
def Run(self, args):
"""Revoke credentials and update active account."""
accounts = args.accounts or []
if isinstance(accounts, str):
accounts = [accounts]
available_accounts = c_store.AvailableAccounts()
unknown_accounts = set(accounts) - set(available_accounts)
if unknown_accounts:
raise c_exc.UnknownArgumentException(
'accounts', ' '.join(unknown_accounts))
if args.all:
accounts = available_accounts
active_account = properties.VALUES.core.account.Get()
if not accounts and active_account:
accounts = [active_account]
if not accounts:
raise c_exc.InvalidArgumentException(
'accounts', 'No credentials available to revoke.')
creds_revoked = False
for account in accounts:
if active_account == account:
properties.PersistProperty(properties.VALUES.core.account, None)
# External account and external account user credentials cannot be
# revoked.
# Detect these type of credentials to show a more user friendly message
# on revocation calls.
# Note that impersonated external account credentials will appear like
# service accounts. These will end with gserviceaccount.com and will be
# handled the same way service account credentials are handled.
try:
creds = c_store.Load(
account, prevent_refresh=True, use_google_auth=True)
except creds_exceptions.Error:
# Ignore all errors. These will be properly handled in the subsequent
# Revoke call.
creds = None
if not c_store.Revoke(account):
if account.endswith('.gserviceaccount.com'):
log.warning(
'[{}] appears to be a service account. Service account tokens '
'cannot be revoked, but they will expire automatically. To '
'prevent use of the service account token earlier than the '
'expiration, delete or disable the parent service account. To '
'explicitly delete the key associated with the service account '
'use `gcloud iam service-accounts keys delete` instead`.'.format(
account))
elif c_creds.IsExternalAccountCredentials(creds):
log.warning(
'[{}] appears to be an external account. External account '
'tokens cannot be revoked, but they will expire automatically.'
.format(account))
elif c_creds.IsExternalAccountUserCredentials(
creds) or c_creds.IsExternalAccountAuthorizedUserCredentials(creds):
log.warning(
'[{}] appears to be an external account user. External account '
'user tokens cannot be revoked, but they will expire '
'automatically.'.format(account))
else:
log.warning(
'[{}] already inactive (previously revoked?)'.format(account))
else:
creds_revoked = True
_WarnIfRevokeAndADCExists(creds_revoked)
return accounts
def Epilog(self, unused_results_were_displayed):
accounts = c_store.AllAccounts()
printer = resource_printer.Printer(
c_store.ACCOUNT_TABLE_FORMAT, out=log.status
)
printer.Print(accounts)
def _WarnIfRevokeAndADCExists(creds_revoked):
if (creds_revoked and os.path.isfile(config.ADCFilePath()) and
auth_util.ADCIsUserAccount()):
log.warning(
'You also have Application Default Credentials (ADC) set up. If you '
'want to revoke your Application Default Credentials as well, use '
'the `gcloud auth application-default revoke` command.\n\nFor '
'information about ADC credentials and gcloud CLI credentials, see '
'https://cloud.google.com/docs/authentication/external/'
'credential-types\n')