def Run(self, args): """Clone GCP repo to current directory. Args: args: argparse.Namespace, the arguments this command is run with. Raises: ToolException: on project initialization errors. Returns: The path to the new git repo. """ # Ensure that we're logged in. c_store.Load() project_id = properties.VALUES.core.project.Get(required=True) project_repo = git.Git(project_id, args.src) path = project_repo.Clone(destination_path=args.dst or args.src) if path: log.status.write('Project [{prj}] repository [{repo}] was cloned to ' '[{path}].\n'.format(prj=project_id, path=path, repo=project_repo.GetName()))
def generate_login_token_from_gcloud_auth(scopes): """Genearete a down-coped access token with given scopes for IAM DB authentication from gcloud credentials. Args: scopes: scopes to be included in the down-scoped token. Returns: Down-scoped access token. """ cred = c_store.Load(allow_account_impersonation=True, use_google_auth=True) cred = _downscope_credential(cred, scopes) 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 token
def UpdateDockerCredentials(server): """Updates the docker config to have fresh credentials. This reads the current contents of Docker's keyring, and extends it with a fresh entry for the provided 'server', based on the active gcloud credential. If a credential exists for 'server' this replaces it. Args: server: The hostname of the registry for which we're freshening the credential. Raises: store.Error: There was an error loading the credentials. """ # Loading credentials will ensure that we're logged in. # And prompt/abort to 'run gcloud auth login' otherwise. cred = store.Load() # Ensure our credential has a valid access token, # which has the full duration available. store.Refresh(cred) if not cred.access_token: raise exceptions.Error('No access token could be obtained ' 'from the current credentials.') try: # Update the credentials stored by docker, passing the access token # as a password, and benign values as the email and username. _DockerLogin(server, _EMAIL, _USERNAME, cred.access_token) except exceptions.Error: # Fall back to the previous manual .dockercfg manipulation # in order to support gcloud app's docker-binaryless use case. # TODO(user) when app deploy is using Argo to take over builds, # remove this. _UpdateDockerConfig(server, _USERNAME, cred.access_token) log.warn( "'docker' was not discovered on the path. Credentials have been " "stored, but are not guaranteed to work with the 1.11 Docker client if" "an external credential store is configured.")
def Run(self, args): """Clone a GCP repository to the current directory. Args: args: argparse.Namespace, the arguments this command is run with. Returns: The path to the new git repository. """ # Ensure that we're logged in. c_store.Load() res = sourcerepo.ParseRepo(args.src) source_handler = sourcerepo.Source() repo = source_handler.GetRepo(res) if not repo: message = ('Repository "{src}" in project "{prj}" does not ' 'exist.\nList current repos with\n' '$ gcloud beta source repos list\n' 'or create with\n' '$ gcloud beta source repos create {src}'.format( src=args.src, prj=res.projectsId)) raise c_exc.InvalidArgumentException('REPOSITORY_NAME', message) if hasattr(repo, 'mirrorConfig') and repo.mirrorConfig: mirror_url = repo.mirrorConfig.url self.ActionIfMirror( project=res.projectsId, repo=args.src, mirror_url=mirror_url) # do the actual clone git_helper = git.Git(res.projectsId, args.src, uri=repo.url) path = git_helper.Clone( destination_path=args.dst or args.src, dry_run=args.dry_run, full_path=self.UseFullGcloudPath(args)) if path and not args.dry_run: log.status.write('Project [{prj}] repository [{repo}] was cloned ' 'to [{path}].\n'.format( prj=res.projectsId, path=path, repo=args.src))
def SetUpCreds(self): """Set up service account credentials.""" sa_creds = self.MakeServiceAccountCredentials() sa_creds.token_expiry = ( datetime.datetime.utcnow() + datetime.timedelta(hours=2)) store.Store(sa_creds, self.fake_account) store.Load(self.fake_account) creds_type = creds.CredentialType.FromCredentials(sa_creds) paths = config.Paths() with open(paths.LegacyCredentialsAdcPath(self.fake_account)) as f: adc_file = json.load(f) # Compare file contents to make sure credentials are set up correctly. self.assertEqual( json.loads("""\ { "client_email": "*****@*****.**", "client_id": "bar.apps.googleusercontent.com", "private_key": "-----BEGIN PRIVATE KEY-----\\nasdf\\n-----END PRIVATE KEY-----\\n", "private_key_id": "key-id", "type": "service_account" }"""), adc_file) self.assertEqual(creds.CredentialType.SERVICE_ACCOUNT, creds_type)
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: cred = c_store.Load(use_google_auth=True) c_store.RefreshIfExpireWithinWindow(cred, window=TOKEN_MIN_LIFETIME) url = sys.stdin.read().strip() 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': '******', } # 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
def testP12CryptoRequired(self): try: import OpenSSL # pylint: disable=g-import-not-at-top,unused-variable except ImportError: raise self.SkipTest('Needs PyOpenSSL installed.') p12_key_file = self._GetTestDataPathFor('service_account_key.p12') self.Run('auth activate-service-account {0} --key-file={1}'.format( _SERVICE_ACCOUNT_EMAIL, p12_key_file)) # Check that internally scopes and user agent is set. creds_dict = json.loads(store.Load().to_json()) self.assertEqual('google-cloud-sdk', creds_dict['_user_agent']) self.assertEqual('google-cloud-sdk', creds_dict['user_agent']) scopes = [ 'openid', 'https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/cloud-platform', 'https://www.googleapis.com/auth/appengine.admin', 'https://www.googleapis.com/auth/compute' ] self.assertEqual(' '.join(scopes), creds_dict['_scopes']) paths = config.Paths() expected_legacy_file = paths.LegacyCredentialsP12KeyPath( _SERVICE_ACCOUNT_EMAIL) self.AssertFileExists(expected_legacy_file) self.assertTrue( filecmp.cmp(p12_key_file, expected_legacy_file), 'original and saved P12 keys are different in {0} and {1}'.format( p12_key_file, expected_legacy_file)) self.AssertFileNotExists( paths.LegacyCredentialsAdcPath(_SERVICE_ACCOUNT_EMAIL)) self.AssertFileNotExists( paths.LegacyCredentialsBqPath(_SERVICE_ACCOUNT_EMAIL))
def Http(auth=True, creds=None, timeout='unset'): """Get an httplib2.Http client for working with the Google API. Args: auth: bool, True if the http client returned should be authorized. creds: oauth2client.client.Credentials, If auth is True and creds is not None, use those credentials to authorize the httplib2.Http client. timeout: double, The timeout in seconds to pass to httplib2. This is the socket level timeout. If timeout is None, timeout is infinite. If default argument 'unset' is given, a sensible default is selected. Returns: An authorized httplib2.Http client object, or a regular httplib2.Http object if no credentials are available. Raises: c_store.Error: If an error loading the credentials occurs. """ http_client = http.Http(timeout=timeout) authority_selector = properties.VALUES.auth.authority_selector.Get() authorization_token_file = ( properties.VALUES.auth.authorization_token_file.Get()) if authority_selector or authorization_token_file: http_client = _WrapRequestForIAMAuth(http_client, authority_selector, authorization_token_file) if auth: if not creds: creds = store.Load() http_client = creds.authorize(http_client) # Wrap the request method to put in our own error handling. http_client = http.Modifiers.WrapRequest( http_client, [], _HandleAuthError, client.AccessTokenRefreshError) return http_client
def MakeSecureChannel(target): """Creates grpc secure channel. Args: target: str, The server address, for example: bigtableadmin.googleapis.com:443. Returns: grpc.secure channel. """ credentials = cred_store.Load() # ssl_channel_credentials() loads root certificates from # `grpc/_adapter/credentials/roots.pem`. transport_creds = grpc.ssl_channel_credentials() custom_metadata_plugin = _MetadataPlugin(credentials) auth_creds = grpc.metadata_call_credentials(custom_metadata_plugin, name='google_creds') channel_creds = grpc.composite_channel_credentials(transport_creds, auth_creds) channel_args = (('grpc.primary_user_agent', http.MakeUserAgentString( properties.VALUES.metrics.command_name.Get())), ) return grpc.secure_channel(target, channel_creds, options=channel_args)
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.') 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.' .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): 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)) return accounts
def Run(self, args): """Run the helper command.""" if args.method != 'get': if args.ignore_unknown: return raise c_exc.ToolException( 'Unexpected method [{meth}]. "get" Expected.'.format( meth=args.method)) info = {} lines = sys.stdin.readlines() for line in lines: if _BLANK_LINE_RE.match(line): continue match = _KEYVAL_RE.match(line) if not match: raise c_exc.ToolException( 'Invalid input line format: [{format}].'.format( format=line.rstrip('\n'))) key, val = match.groups() info[key] = val.strip() if 'protocol' not in info: raise c_exc.ToolException('Required key "protocol" missing.') if 'host' not in info: raise c_exc.ToolException('Required key "host" missing.') if info.get('protocol') != 'https': raise c_exc.ToolException( 'Invalid protocol [{p}]. "https" expected.'.format( p=info.get('protocol'))) credentialed_domains = [ 'code.google.com', 'source.developers.google.com' ] extra = properties.VALUES.core.credentialed_hosted_repo_domains.Get() if extra: credentialed_domains.extend(extra.split(',')) if info.get('host') not in credentialed_domains: if args.ignore_unknown: return raise c_exc.ToolException( 'Unknown host [{host}].'.format(host=info.get('host'))) account = properties.VALUES.core.account.Get() try: cred = c_store.Load(account) c_store.Refresh(cred) except c_store.Error as e: sys.stderr.write( textwrap.dedent("""\ ERROR: {error} Run 'gcloud auth login' to log in. """.format(error=str(e)))) return self._CheckNetrc() sys.stdout.write( textwrap.dedent("""\ username={username} password={password} """).format(username=account, password=cred.access_token))
def Run(self, args): """Run the helper command.""" if args.method not in GitHelper.METHODS: if args.ignore_unknown: return raise c_exc.ToolException( 'Unexpected method [{meth}]. One of [{methods}] expected.'. format(meth=args.method, methods=', '.join(GitHelper.METHODS))) info = self._ParseInput() credentialed_domains = ['source.developers.google.com'] extra = properties.VALUES.core.credentialed_hosted_repo_domains.Get() if extra: credentialed_domains.extend(extra.split(',')) if info.get('host') not in credentialed_domains: if args.ignore_unknown: return raise c_exc.ToolException( 'Unknown host [{host}].'.format(host=info.get('host'))) if args.method == GitHelper.GET: account = properties.VALUES.core.account.Get() try: cred = c_store.Load(account) c_store.Refresh(cred) except c_store.Error as e: sys.stderr.write( textwrap.dedent("""\ ERROR: {error} Run 'gcloud auth login' to log in. """.format(error=str(e)))) return self._CheckNetrc() sys.stdout.write( textwrap.dedent("""\ username={username} password={password} """).format(username=account, password=cred.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 Run(self, args): """Create the .gcloud folder, if possible. Args: args: argparse.Namespace, the arguments this command is run with. Raises: ToolException: on project initialization errors. Returns: The path to the new gcloud workspace. """ # Ensure that we're logged in. creds = c_store.Load() is_new_directory = False try: workspace = workspaces.FromCWD() # Cannot re-init when in a workspace. current_project = workspace.GetProperty( properties.VALUES.core.project) if current_project != args.project: message = ( 'Directory [{root_directory}] is already initialized to project' ' [{project}].').format( root_directory=workspace.root_directory, project=current_project) else: message = ( 'Directory [{root_directory}] is already initialized.' ).format(root_directory=workspace.root_directory) raise c_exc.ToolException(message) except workspaces.NoContainingWorkspaceException: workspace_dir = os.path.join(os.getcwd(), args.project) message = ('Directory [{root_directory}] is not empty.').format( root_directory=workspace_dir) if os.path.exists(workspace_dir) and os.listdir(workspace_dir): raise c_exc.ToolException(message) else: files.MakeDir(workspace_dir) is_new_directory = True workspace = workspaces.Create(workspace_dir) workspace.SetProperty(properties.VALUES.core.project, args.project) if args.devshell_image: workspace.SetProperty(properties.VALUES.devshell.image, args.devshell_image) # Everything that can fail should happen within this next try: block. # If something fails, and the result is an empty directory that we just # created, we clean it up. try: source_client = source_v0.SourceV0(credentials=creds) try: response = source_client.repos.List( source_v0.SourceReposListRequest(projectId=args.project)) except apitools_base.HttpError: # Source API is down! Let's guess the repo. log.status.write( textwrap.dedent("""\ Unable to fetch repository URL. Guessing the URL, but if your project uses repo-sync then the cloned repository may be read-only. """)) try: workspace.CloneProjectRepository( args.project, workspaces.DEFAULT_REPOSITORY_ALIAS) except workspaces.CannotFetchRepositoryException as e: log.error(e) else: for repo in response.repos: try: workspace.CloneProjectRepository( args.project, repo.repoName, repo.cloneUrl) except workspaces.CannotFetchRepositoryException as e: log.error(e) finally: cleared_files = False if is_new_directory: dir_files = os.listdir(workspace_dir) if not dir_files or dir_files == [ config.Paths().CLOUDSDK_WORKSPACE_CONFIG_DIR_NAME ]: log.error(( 'Unable to initialize project [{project}], cleaning up' ' [{path}].').format(project=args.project, path=workspace_dir)) files.RmTree(workspace_dir) cleared_files = True if cleared_files: raise c_exc.ToolException( 'Unable to initialize project [{project}].'.format( project=args.project)) log.status.write( 'Project [{prj}] was initialized in [{path}].\n'.format( path=workspace.root_directory, prj=args.project)) return workspace
def MakeRequest(url, command_path): """Gets the request object for the given URL. If the URL is for cloud storage and we get a 403, this will try to load the active credentials and use them to authenticate the download. Args: url: str, The URL to download. command_path: the command path to include in the User-Agent header if the URL is HTTP Raises: AuthenticationError: If this download requires authentication and there are no credentials or the credentials do not have access. Returns: urllib2.Request, The request. """ headers = { b'Cache-Control': b'no-cache', b'User-Agent': http_encoding.Encode( http.MakeUserAgentString(command_path)) } timeout = TIMEOUT_IN_SEC if command_path == UPDATE_MANAGER_COMMAND_PATH: timeout = UPDATE_MANAGER_TIMEOUT_IN_SEC try: if url.startswith(ComponentInstaller.GCS_BROWSER_DL_URL): url = url.replace(ComponentInstaller.GCS_BROWSER_DL_URL, ComponentInstaller.GCS_API_DL_URL, 1) req = urllib.request.Request(url, headers=headers) return ComponentInstaller._RawRequest(req, timeout=timeout) except urllib.error.HTTPError as e: if e.code != 403 or not url.startswith(ComponentInstaller.GCS_API_DL_URL): raise e try: creds = store.Load() store.Refresh(creds) creds.apply(headers) except store.Error as e: # If we fail here, it is because there are no active credentials or the # credentials are bad. raise AuthenticationError( 'This component requires valid credentials to install.', e) try: # Retry the download using the credentials. req = urllib.request.Request(url, headers=headers) return ComponentInstaller._RawRequest(req, timeout=timeout) except urllib.error.HTTPError as e: if e.code != 403: raise e # If we fail again with a 403, that means we used the credentials, but # they didn't have access to the resource. raise AuthenticationError("""\ Account [{account}] does not have permission to install this component. Please ensure that this account should have access or run: $ gcloud config set account `ACCOUNT` to choose another account.""".format( account=properties.VALUES.core.account.Get()), e)
def Run(self, args): """Create the .gcloud folder, if possible. Args: args: argparse.Namespace, the arguments this command is run with. Raises: ToolException: on project initialization errors. Returns: The path to the new gcloud workspace. """ log.warn( '`gcloud init` will be changing soon. ' 'To clone git repo consider using `gcloud alpha source repo clone`' ' command.') # Ensure that we're logged in. c_store.Load() is_new_directory = False try: workspace = workspaces.FromCWD() # Cannot re-init when in a workspace. current_project = workspace.GetProperty( properties.VALUES.core.project) if current_project != args.project: message = ( 'Directory [{root_directory}] is already initialized to project' ' [{project}].').format( root_directory=workspace.root_directory, project=current_project) else: message = ( 'Directory [{root_directory}] is already initialized.' ).format(root_directory=workspace.root_directory) raise c_exc.ToolException(message) except workspaces.NoContainingWorkspaceException: workspace_dir = os.path.join(os.getcwd(), args.project) message = ('Directory [{root_directory}] is not empty.').format( root_directory=workspace_dir) if os.path.exists(workspace_dir) and os.listdir(workspace_dir): raise c_exc.ToolException(message) else: files.MakeDir(workspace_dir) is_new_directory = True workspace = workspaces.Create(workspace_dir) workspace.SetProperty(properties.VALUES.core.project, args.project) if args.devshell_image: workspace.SetProperty(properties.VALUES.devshell.image, args.devshell_image) # Everything that can fail should happen within this next try: block. # If something fails, and the result is an empty directory that we just # created, we clean it up. try: workspace.CloneProjectRepository( args.project, workspaces.DEFAULT_REPOSITORY_ALIAS) except workspaces.CannotFetchRepositoryException as e: log.error(e) finally: cleared_files = False if is_new_directory: dir_files = os.listdir(workspace_dir) if not dir_files or dir_files == [ config.Paths().CLOUDSDK_WORKSPACE_CONFIG_DIR_NAME ]: log.error(( 'Unable to initialize project [{project}], cleaning up' ' [{path}].').format(project=args.project, path=workspace_dir)) files.RmTree(workspace_dir) cleared_files = True if cleared_files: raise c_exc.ToolException( 'Unable to initialize project [{project}].'.format( project=args.project)) log.status.write( 'Project [{prj}] was initialized in [{path}].\n'.format( path=workspace.root_directory, prj=args.project)) return workspace
def Run(self, args): client = apis.GetClientInstance('storagetransfer', 'v1') messages = apis.GetMessagesModule('storagetransfer', 'v1') if args.creds_file: expanded_file_path = os.path.abspath(os.path.expanduser(args.creds_file)) with files.FileReader(expanded_file_path) as file_reader: try: parsed_creds_file = json.load(file_reader) account_email = parsed_creds_file['client_email'] is_service_account = parsed_creds_file['type'] == 'service_account' except (ValueError, KeyError) as e: log.error(e) raise ValueError('Invalid creds file format.' ' Run command with "--help" flag for more details.') prefixed_account_email = _get_iam_prefixed_email( account_email, is_service_account) else: account_email = properties.VALUES.core.account.Get() is_service_account = creds.IsServiceAccountCredentials(creds_store.Load()) prefixed_account_email = _get_iam_prefixed_email(account_email, is_service_account) project_id = properties.VALUES.core.project.Get() parsed_project_id = projects_util.ParseProject(project_id) project_iam_policy = projects_api.GetIamPolicy(parsed_project_id) existing_user_roles = _get_existing_transfer_roles_for_account( project_iam_policy, prefixed_account_email, EXPECTED_USER_ROLES) log.status.Print('User {} has roles:\n{}'.format(account_email, list(existing_user_roles))) missing_user_roles = EXPECTED_USER_ROLES - existing_user_roles log.status.Print('Missing roles:\n{}'.format(list(missing_user_roles))) all_missing_role_tuples = [ (prefixed_account_email, role) for role in missing_user_roles ] log.status.Print('***') transfer_p4sa_email = client.googleServiceAccounts.Get( messages.StoragetransferGoogleServiceAccountsGetRequest( projectId=project_id)).accountEmail prefixed_transfer_p4sa_email = _get_iam_prefixed_email( transfer_p4sa_email, is_service_account=True) existing_p4sa_roles = _get_existing_transfer_roles_for_account( project_iam_policy, prefixed_transfer_p4sa_email, EXPECTED_P4SA_ROLES) log.status.Print('Google-managed transfer account {} has roles:\n{}'.format( transfer_p4sa_email, list(existing_p4sa_roles))) missing_p4sa_roles = EXPECTED_P4SA_ROLES - existing_p4sa_roles log.status.Print('Missing roles:\n{}'.format(list(missing_p4sa_roles))) all_missing_role_tuples += [ (prefixed_transfer_p4sa_email, role) for role in missing_p4sa_roles ] if args.add_missing or all_missing_role_tuples: log.status.Print('***') if args.add_missing: if all_missing_role_tuples: log.status.Print('Adding roles:\n{}'.format(all_missing_role_tuples)) projects_api.AddIamPolicyBindings(parsed_project_id, all_missing_role_tuples) log.status.Print('***') # Source: # https://cloud.google.com/iam/docs/granting-changing-revoking-access log.status.Print( 'Done. Permissions typically take seconds to propagate, but,' ' in some cases, it can take up to seven minutes.') else: log.status.Print('No missing roles to add.') else: log.status.Print('Rerun with --add-missing to add missing roles.')
def password(self): cred = c_store.Load() return cred.access_token
def testSimpleGoogleAuthIntegration(self): creds = c_store.Load(use_google_auth=True) self.assertIsInstance(creds, devshell.DevShellCredentialsGoogleAuth) self.Run('sql flags list')
def Run(self, args): """Run the helper command.""" 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 = [ '.'+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 c_store.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 Http(cmd_path=None, trace_token=None, trace_email=None, trace_log=False, auth=True, creds=None, timeout='unset', log_http=False, authority_selector=None, authorization_token_file=None): """Get an httplib2.Http object for working with the Google API. Args: cmd_path: str, Path of command that will use the httplib2.Http object. trace_token: str, Token to be used to route service request traces. trace_email: str, username to which service request traces should be sent. trace_log: bool, Enable/disable server side logging of service requests. auth: bool, True if the http object returned should be authorized. creds: oauth2client.client.Credentials, If auth is True and creds is not None, use those credentials to authorize the httplib2.Http object. timeout: double, The timeout in seconds to pass to httplib2. This is the socket level timeout. If timeout is None, timeout is infinite. If default argument 'unset' is given, a sensible default is selected. log_http: bool, Enable/disable client side logging of service requests. authority_selector: str, The IAM authority selector to pass as a header, or None to not pass anything. Returns: An authorized httplib2.Http object, or a regular httplib2.Http object if no credentials are available. Raises: c_store.Error: If an error loading the credentials occurs. """ # Compared with setting the default timeout in the function signature (i.e. # timeout=300), this lets you test with short default timeouts by mocking # GetDefaultTimeout. effective_timeout = timeout if timeout != 'unset' else GetDefaultTimeout() # TODO(user): Have retry-once-if-denied logic, to allow client tools to not # worry about refreshing credentials. http = c_store._Http( # pylint:disable=protected-access timeout=effective_timeout, proxy_info=_GetHttpProxyInfo()) # Wrap first to dump any data added by other wrappers. if log_http: http = _WrapRequestForLogging(http) # Wrap the request method to put in our own user-agent, and trace reporting. gcloud_ua = MakeUserAgentString(cmd_path) http = _WrapRequestForUserAgentAndTracing(http, trace_token, trace_email, trace_log, gcloud_ua) if authority_selector or authorization_token_file: http = _WrapRequestForIAMAuth(http, authority_selector, authorization_token_file) if auth: if not creds: creds = c_store.Load() http = creds.authorize(http) # Wrap the request method to put in our own error handling. http = _WrapRequestForAuthErrHandling(http) return http
def testSimpleGoogleAuthIntegration(self): creds = c_store.Load(use_google_auth=True) self.assertIsInstance(creds, devshell.DevShellCredentialsGoogleAuth) # dns surface is on google-auth self.Run('dns managed-zones list')
def testSimpleIntegration(self): creds = c_store.Load() self.assertEqual(type(creds), devshell.DevshellCredentials) self.Run('sql flags list')
def testNoSerialize(self): creds = c_store.Load() # This should just do nothing. If the creds are actually serialized, # things blow up. c_store.Store(creds)
def Run(self, args): """Run the authentication command.""" 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,) if c_devshell.IsDevshellEnvironment(): message = """ 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? """ answer = console_io.PromptContinue(message=message) if not answer: return None elif c_gce.Metadata().connected: 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? """) answer = console_io.PromptContinue(message=message) if not answer: return None account = args.account if account and not args.force: try: creds = c_store.Load(account=account, scopes=scopes) except c_store.Error: creds = None if creds: # Account already has valid creds, just switch to it. return self.LoginAs(account, creds, args.project, args.activate, args.brief) # No valid creds, do the web flow. launch_browser = check_browser.ShouldLaunchBrowser(args.launch_browser) creds = auth_util.DoInstalledAppBrowserFlow(launch_browser, scopes) web_flow_account = creds.id_token['email'] if account and account.lower() != web_flow_account.lower(): raise auth_exceptions.WrongAccountError( 'You attempted to log in as account [{account}] but the received ' 'credentials were for account [{web_flow_account}].\n\n' 'Please check that your browser is logged in as account [{account}] ' 'and that you are using the correct browser profile.'.format( account=account, web_flow_account=web_flow_account)) account = web_flow_account # We got new creds, and they are for the correct user. c_store.Store(creds, account, scopes) return self.LoginAs(account, creds, args.project, args.activate, args.brief)
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) 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: exist_creds = c_store.Load(account=account, scopes=scopes) except creds_exceptions.Error: exist_creds = None if exist_creds: 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)