1# -*- coding: utf-8 -*- 2# Copyright 2011 Google Inc. All Rights Reserved. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15"""Implementation of config command for creating a gsutil configuration file.""" 16 17from __future__ import absolute_import 18 19import datetime 20from httplib import ResponseNotReady 21import json 22import multiprocessing 23import os 24import platform 25import signal 26import socket 27import stat 28import sys 29import textwrap 30import time 31import webbrowser 32 33import boto 34from boto.provider import Provider 35from httplib2 import ServerNotFoundError 36from oauth2client.client import HAS_CRYPTO 37 38import gslib 39from gslib.command import Command 40from gslib.commands.compose import MAX_COMPONENT_COUNT 41from gslib.cred_types import CredTypes 42from gslib.exception import AbortException 43from gslib.exception import CommandException 44from gslib.hashing_helper import CHECK_HASH_ALWAYS 45from gslib.hashing_helper import CHECK_HASH_IF_FAST_ELSE_FAIL 46from gslib.hashing_helper import CHECK_HASH_IF_FAST_ELSE_SKIP 47from gslib.hashing_helper import CHECK_HASH_NEVER 48from gslib.sig_handling import RegisterSignalHandler 49from gslib.util import EIGHT_MIB 50from gslib.util import IS_WINDOWS 51 52 53_SYNOPSIS = """ 54 gsutil [-D] config [-a] [-b] [-e] [-f] [-o <file>] [-r] [-s <scope>] [-w] 55""" 56 57_DETAILED_HELP_TEXT = (""" 58<B>SYNOPSIS</B> 59""" + _SYNOPSIS + """ 60 61 62<B>DESCRIPTION</B> 63 The gsutil config command obtains access credentials for Google Cloud 64 Storage and writes a boto/gsutil configuration file containing the obtained 65 credentials along with a number of other configuration-controllable values. 66 67 Unless specified otherwise (see OPTIONS), the configuration file is written 68 to ~/.boto (i.e., the file .boto under the user's home directory). If the 69 default file already exists, an attempt is made to rename the existing file 70 to ~/.boto.bak; if that attempt fails the command will exit. A different 71 destination file can be specified with the -o option (see OPTIONS). 72 73 Because the boto configuration file contains your credentials you should 74 keep its file permissions set so no one but you has read access. (The file 75 is created read-only when you run gsutil config.) 76 77 78<B>CREDENTIALS</B> 79 By default gsutil config obtains OAuth2 credentials, and writes them 80 to the [Credentials] section of the configuration file. The -r, -w, 81 -f options (see OPTIONS below) cause gsutil config to request a token 82 with restricted scope; the resulting token will be restricted to read-only 83 operations, read-write operations, or all operations (including acl get/set, 84 defacl get/set, and logging get/'set on'/'set off' operations). In 85 addition, -s <scope> can be used to request additional (non-Google-Storage) 86 scopes. 87 88 If you want to use credentials based on access key and secret (the older 89 authentication method before OAuth2 was supported) instead of OAuth2, 90 see help about the -a option in the OPTIONS section. 91 92 If you wish to use gsutil with other providers (or to copy data back and 93 forth between multiple providers) you can edit their credentials into the 94 [Credentials] section after creating the initial configuration file. 95 96 97<B>CONFIGURING SERVICE ACCOUNT CREDENTIALS</B> 98 You can configure credentials for service accounts using the gsutil config -e 99 option. Service accounts are useful for authenticating on behalf of a service 100 or application (as opposed to a user). 101 102 When you run gsutil config -e, you will be prompted for your service account 103 email address and the path to your private key file. To get these data, visit 104 the `Google Developers Console <https://cloud.google.com/console#/project>`_, 105 click on the project you are using, then click "APIs & auth", then click 106 "Credentials", then click "Create new Client ID"; on the pop-up dialog box 107 select "Service account" and click "Create Client ID". This will download 108 a private key file, which you should move to somewhere 109 accessible from the machine where you run gsutil. Make sure to set its 110 protection so only the users you want to be able to authenticate have 111 access. 112 113 Note that your service account will NOT be considered an Owner for the 114 purposes of API access (see "gsutil help creds" for more information about 115 this). See https://developers.google.com/accounts/docs/OAuth2ServiceAccount 116 for further information on service account authentication. 117 118 119<B>CONFIGURATION FILE SELECTION PROCEDURE</B> 120 By default, gsutil will look for the configuration file in /etc/boto.cfg and 121 ~/.boto. You can override this choice by setting the BOTO_CONFIG environment 122 variable. This is also useful if you have several different identities or 123 cloud storage environments: By setting up the credentials and any additional 124 configuration in separate files for each, you can switch environments by 125 changing environment variables. 126 127 You can also set up a path of configuration files, by setting the BOTO_PATH 128 environment variable to contain a ":" delimited path. For example setting 129 the BOTO_PATH environment variable to: 130 131 /etc/projects/my_group_project.boto.cfg:/home/mylogin/.boto 132 133 will cause gsutil to load each configuration file found in the path in 134 order. This is useful if you want to set up some shared configuration 135 state among many users: The shared state can go in the central shared file 136 ( /etc/projects/my_group_project.boto.cfg) and each user's individual 137 credentials can be placed in the configuration file in each of their home 138 directories. (For security reasons users should never share credentials 139 via a shared configuration file.) 140 141 142<B>CONFIGURATION FILE STRUCTURE</B> 143 The configuration file contains a number of sections: [Credentials], 144 [Boto], [GSUtil], and [OAuth2]. If you edit the file make sure to edit the 145 appropriate section (discussed below), and to be careful not to mis-edit 146 any of the setting names (like "gs_access_key_id") and not to remove the 147 section delimiters (like "[Credentials]"). 148 149 150<B>ADDITIONAL CONFIGURATION-CONTROLLABLE FEATURES</B> 151 With the exception of setting up gsutil to work through a proxy (see 152 below), most users won't need to edit values in the boto configuration file; 153 values found in there tend to be of more specialized use than command line 154 option-controllable features. 155 156 The following are the currently defined configuration settings, broken 157 down by section. Their use is documented in comments preceding each, in 158 the configuration file. If you see a setting you want to change that's not 159 listed in your current file, see the section below on Updating to the Latest 160 Configuration File. 161 162 The currently supported settings, are, by section: 163 164 [Credentials] 165 aws_access_key_id 166 aws_secret_access_key 167 gs_access_key_id 168 gs_host 169 gs_json_host 170 gs_json_port 171 gs_oauth2_refresh_token 172 gs_port 173 gs_secret_access_key 174 s3_host 175 s3_port 176 177 [Boto] 178 proxy 179 proxy_port 180 proxy_user 181 proxy_pass 182 proxy_rdns 183 http_socket_timeout 184 https_validate_certificates 185 debug 186 max_retry_delay 187 num_retries 188 189 [GSUtil] 190 check_hashes 191 content_language 192 default_api_version 193 default_project_id 194 json_api_version 195 parallel_composite_upload_component_size 196 parallel_composite_upload_threshold 197 sliced_object_download_component_size 198 sliced_object_download_max_components 199 sliced_object_download_threshold 200 parallel_process_count 201 parallel_thread_count 202 prefer_api 203 resumable_threshold 204 resumable_tracker_dir (deprecated in 4.6, use state_dir) 205 rsync_buffer_lines 206 software_update_check_period 207 state_dir 208 tab_completion_time_logs 209 tab_completion_timeout 210 use_magicfile 211 212 [OAuth2] 213 client_id 214 client_secret 215 oauth2_refresh_retries 216 provider_authorization_uri 217 provider_label 218 provider_token_uri 219 token_cache 220 221 222<B>UPDATING TO THE LATEST CONFIGURATION FILE</B> 223 We add new configuration controllable features to the boto configuration file 224 over time, but most gsutil users create a configuration file once and then 225 keep it for a long time, so new features aren't apparent when you update 226 to a newer version of gsutil. If you want to get the latest configuration 227 file (which includes all the latest settings and documentation about each) 228 you can rename your current file (e.g., to '.boto_old'), run gsutil config, 229 and then edit any configuration settings you wanted from your old file 230 into the newly created file. Note, however, that if you're using OAuth2 231 credentials and you go back through the OAuth2 configuration dialog it will 232 invalidate your previous OAuth2 credentials. 233 234 If no explicit scope option is given, -f (full control) is assumed by default. 235 236 237<B>OPTIONS</B> 238 -a Prompt for Google Cloud Storage access key and secret (the older 239 authentication method before OAuth2 was supported) instead of 240 obtaining an OAuth2 token. 241 242 -b Causes gsutil config to launch a browser to obtain OAuth2 approval 243 and the project ID instead of showing the URL for each and asking 244 the user to open the browser. This will probably not work as 245 expected if you are running gsutil from an ssh window, or using 246 gsutil on Windows. 247 248 -e Prompt for service account credentials. This option requires that 249 -a is not set. 250 251 -f Request token with full-control access (default). 252 253 -o <file> Write the configuration to <file> instead of ~/.boto. 254 Use '-' for stdout. 255 256 -r Request token restricted to read-only access. 257 258 -s <scope> Request additional OAuth2 <scope>. 259 260 -w Request token restricted to read-write access. 261""") 262 263 264try: 265 from gcs_oauth2_boto_plugin import oauth2_helper # pylint: disable=g-import-not-at-top 266except ImportError: 267 pass 268 269GOOG_CLOUD_CONSOLE_URI = 'https://cloud.google.com/console#/project' 270 271SCOPE_FULL_CONTROL = 'https://www.googleapis.com/auth/devstorage.full_control' 272SCOPE_READ_WRITE = 'https://www.googleapis.com/auth/devstorage.read_write' 273SCOPE_READ_ONLY = 'https://www.googleapis.com/auth/devstorage.read_only' 274 275CONFIG_PRELUDE_CONTENT = """ 276# This file contains credentials and other configuration information needed 277# by the boto library, used by gsutil. You can edit this file (e.g., to add 278# credentials) but be careful not to mis-edit any of the variable names (like 279# "gs_access_key_id") or remove important markers (like the "[Credentials]" and 280# "[Boto]" section delimiters). 281# 282""" 283 284# Default number of OS processes and Python threads for parallel operations. 285# On Linux systems we automatically scale the number of processes to match 286# the underlying CPU/core count. Given we'll be running multiple concurrent 287# processes on a typical multi-core Linux computer, to avoid being too 288# aggressive with resources, the default number of threads is reduced from 289# the previous value of 24 to 10. 290# On Windows and Mac systems parallel multi-processing and multi-threading 291# in Python presents various challenges so we retain compatibility with 292# the established parallel mode operation, i.e. one process and 24 threads. 293if platform.system() == 'Linux': 294 DEFAULT_PARALLEL_PROCESS_COUNT = multiprocessing.cpu_count() 295 DEFAULT_PARALLEL_THREAD_COUNT = 10 296else: 297 DEFAULT_PARALLEL_PROCESS_COUNT = 1 298 DEFAULT_PARALLEL_THREAD_COUNT = 24 299 300# TODO: Once compiled crcmod is being distributed by major Linux distributions 301# revert DEFAULT_PARALLEL_COMPOSITE_UPLOAD_THRESHOLD value to '150M'. 302DEFAULT_PARALLEL_COMPOSITE_UPLOAD_THRESHOLD = '0' 303DEFAULT_PARALLEL_COMPOSITE_UPLOAD_COMPONENT_SIZE = '50M' 304DEFAULT_SLICED_OBJECT_DOWNLOAD_THRESHOLD = '150M' 305DEFAULT_SLICED_OBJECT_DOWNLOAD_COMPONENT_SIZE = '200M' 306DEFAULT_SLICED_OBJECT_DOWNLOAD_MAX_COMPONENTS = 4 307 308CONFIG_BOTO_SECTION_CONTENT = """ 309[Boto] 310 311# http_socket_timeout specifies the timeout (in seconds) used to tell httplib 312# how long to wait for socket timeouts. The default is 70 seconds. Note that 313# this timeout only applies to httplib, not to httplib2 (which is used for 314# OAuth2 refresh/access token exchanges). 315#http_socket_timeout = 70 316 317# The following two options control the use of a secure transport for requests 318# to S3 and Google Cloud Storage. It is highly recommended to set both options 319# to True in production environments, especially when using OAuth2 bearer token 320# authentication with Google Cloud Storage. 321 322# Set 'https_validate_certificates' to False to disable server certificate 323# checking. The default for this option in the boto library is currently 324# 'False' (to avoid breaking apps that depend on invalid certificates); it is 325# therefore strongly recommended to always set this option explicitly to True 326# in configuration files, to protect against "man-in-the-middle" attacks. 327https_validate_certificates = True 328 329# 'debug' controls the level of debug messages printed: 0 for none, 1 330# for basic boto debug, 2 for all boto debug plus HTTP requests/responses. 331# Note: 'gsutil -d' sets debug to 2 for that one command run. 332#debug = <0, 1, or 2> 333 334# 'num_retries' controls the number of retry attempts made when errors occur 335# during data transfers. The default is 6. 336# Note 1: You can cause gsutil to retry failures effectively infinitely by 337# setting this value to a large number (like 10000). Doing that could be useful 338# in cases where your network connection occasionally fails and is down for an 339# extended period of time, because when it comes back up gsutil will continue 340# retrying. However, in general we recommend not setting the value above 10, 341# because otherwise gsutil could appear to "hang" due to excessive retries 342# (since unless you run gsutil -D you won't see any logged evidence that gsutil 343# is retrying). 344# Note 2: Don't set this value to 0, as it will cause boto to fail when reusing 345# HTTP connections. 346#num_retries = <integer value> 347 348# 'max_retry_delay' controls the max delay (in seconds) between retries. The 349# default value is 60, so the backoff sequence will be 1 seconds, 2 seconds, 4, 350# 8, 16, 32, and then 60 for all subsequent retries for a given HTTP request. 351# Note: At present this value only impacts the XML API and the JSON API uses a 352# fixed value of 60. 353#max_retry_delay = <integer value> 354""" 355 356CONFIG_INPUTLESS_GSUTIL_SECTION_CONTENT = """ 357[GSUtil] 358 359# 'resumable_threshold' specifies the smallest file size [bytes] for which 360# resumable Google Cloud Storage uploads are attempted. The default is 8388608 361# (8 MiB). 362#resumable_threshold = %(resumable_threshold)d 363 364# 'rsync_buffer_lines' specifies the number of lines of bucket or directory 365# listings saved in each temp file during sorting. (The complete set is 366# split across temp files and separately sorted/merged, to avoid needing to 367# fit everything in memory at once.) If you are trying to synchronize very 368# large directories/buckets (e.g., containing millions or more objects), 369# having too small a value here can cause gsutil to run out of open file 370# handles. If that happens, you can try to increase the number of open file 371# handles your system allows (e.g., see 'man ulimit' on Linux; see also 372# http://docs.python.org/2/library/resource.html). If you can't do that (or 373# if you're already at the upper limit), increasing rsync_buffer_lines will 374# cause gsutil to use fewer file handles, but at the cost of more memory. With 375# rsync_buffer_lines set to 32000 and assuming a typical URL is 100 bytes 376# long, gsutil will require approximately 10 MiB of memory while building 377# the synchronization state, and will require approximately 60 open file 378# descriptors to build the synchronization state over all 1M source and 1M 379# destination URLs. Memory and file descriptors are only consumed while 380# building the state; once the state is built, it resides in two temp files that 381# are read and processed incrementally during the actual copy/delete 382# operations. 383#rsync_buffer_lines = 32000 384 385# 'state_dir' specifies the base location where files that 386# need a static location are stored, such as pointers to credentials, 387# resumable transfer tracker files, and the last software update check. 388# By default these files are stored in ~/.gsutil 389#state_dir = <file_path> 390# gsutil periodically checks whether a new version of the gsutil software is 391# available. 'software_update_check_period' specifies the number of days 392# between such checks. The default is 30. Setting the value to 0 disables 393# periodic software update checks. 394#software_update_check_period = 30 395 396# 'tab_completion_timeout' controls the timeout (in seconds) for tab 397# completions that involve remote requests (such as bucket or object names). 398# If tab completion does not succeed within this timeout, no tab completion 399# suggestions will be returned. 400# A value of 0 will disable completions that involve remote requests. 401#tab_completion_timeout = 5 402 403# 'parallel_process_count' and 'parallel_thread_count' specify the number 404# of OS processes and Python threads, respectively, to use when executing 405# operations in parallel. The default settings should work well as configured, 406# however, to enhance performance for transfers involving large numbers of 407# files, you may experiment with hand tuning these values to optimize 408# performance for your particular system configuration. 409# MacOS and Windows users should see 410# https://github.com/GoogleCloudPlatform/gsutil/issues/77 before attempting 411# to experiment with these values. 412#parallel_process_count = %(parallel_process_count)d 413#parallel_thread_count = %(parallel_thread_count)d 414 415# 'parallel_composite_upload_threshold' specifies the maximum size of a file to 416# upload in a single stream. Files larger than this threshold will be 417# partitioned into component parts and uploaded in parallel and then composed 418# into a single object. 419# The number of components will be the smaller of 420# ceil(file_size / parallel_composite_upload_component_size) and 421# MAX_COMPONENT_COUNT. The current value of MAX_COMPONENT_COUNT is 422# %(max_component_count)d. 423# If 'parallel_composite_upload_threshold' is set to 0, then automatic parallel 424# uploads will never occur. 425# Setting an extremely low threshold is unadvisable. The vast majority of 426# environments will see degraded performance for thresholds below 80M, and it 427# is almost never advantageous to have a threshold below 20M. 428# 'parallel_composite_upload_component_size' specifies the ideal size of a 429# component in bytes, which will act as an upper bound to the size of the 430# components if ceil(file_size / parallel_composite_upload_component_size) is 431# less than MAX_COMPONENT_COUNT. 432# Values can be provided either in bytes or as human-readable values 433# (e.g., "150M" to represent 150 mebibytes) 434# 435# Note: At present parallel composite uploads are disabled by default, because 436# using composite objects requires a compiled crcmod (see "gsutil help crcmod"), 437# and for operating systems that don't already have this package installed this 438# makes gsutil harder to use. Google is actively working with a number of the 439# Linux distributions to get crcmod included with the stock distribution. Once 440# that is done we will re-enable parallel composite uploads by default in 441# gsutil. 442# 443# Note: Parallel composite uploads should not be used with NEARLINE storage 444# class buckets, as doing this would incur an early deletion charge for each 445# component object. 446#parallel_composite_upload_threshold = %(parallel_composite_upload_threshold)s 447#parallel_composite_upload_component_size = %(parallel_composite_upload_component_size)s 448 449# 'sliced_object_download_threshold' and 450# 'sliced_object_download_component_size' have analogous functionality to 451# their respective parallel_composite_upload config values. 452# 'sliced_object_download_max_components' specifies the maximum number of 453# slices to be used when performing a sliced object download. It is not 454# restricted by MAX_COMPONENT_COUNT. 455#sliced_object_download_threshold = %(sliced_object_download_threshold)s 456#sliced_object_download_component_size = %(sliced_object_download_component_size)s 457#sliced_object_download_max_components = %(sliced_object_download_max_components)s 458 459# 'use_magicfile' specifies if the 'file --mime-type <filename>' command should 460# be used to guess content types instead of the default filename extension-based 461# mechanism. Available on UNIX and MacOS (and possibly on Windows, if you're 462# running Cygwin or some other package that provides implementations of 463# UNIX-like commands). When available and enabled use_magicfile should be more 464# robust because it analyzes file contents in addition to extensions. 465#use_magicfile = False 466 467# 'content_language' specifies the ISO 639-1 language code of the content, to be 468# passed in the Content-Language header. By default no Content-Language is sent. 469# See the ISO 639-1 column of 470# http://www.loc.gov/standards/iso639-2/php/code_list.php for a list of 471# language codes. 472content_language = en 473 474# 'check_hashes' specifies how strictly to require integrity checking for 475# downloaded data. Legal values are: 476# '%(hash_fast_else_fail)s' - (default) Only integrity check if the digest 477# will run efficiently (using compiled code), else fail the download. 478# '%(hash_fast_else_skip)s' - Only integrity check if the server supplies a 479# hash and the local digest computation will run quickly, else skip the 480# check. 481# '%(hash_always)s' - Always check download integrity regardless of possible 482# performance costs. 483# '%(hash_never)s' - Don't perform download integrity checks. This setting is 484# not recommended except for special cases such as measuring download 485# performance excluding time for integrity checking. 486# This option exists to assist users who wish to download a GCS composite object 487# and are unable to install crcmod with the C-extension. CRC32c is the only 488# available integrity check for composite objects, and without the C-extension, 489# download performance can be significantly degraded by the digest computation. 490# This option is ignored for daisy-chain copies, which don't compute hashes but 491# instead (inexpensively) compare the cloud source and destination hashes. 492#check_hashes = if_fast_else_fail 493 494# The ability to specify an alternative JSON API version is primarily for cloud 495# storage service developers. 496#json_api_version = v1 497 498# Specifies the API to use when interacting with cloud storage providers. If 499# the gsutil command supports this API for the provider, it will be used 500# instead of the default. 501# Commands typically default to XML for S3 and JSON for GCS. 502#prefer_api = json 503#prefer_api = xml 504 505""" % {'hash_fast_else_fail': CHECK_HASH_IF_FAST_ELSE_FAIL, 506 'hash_fast_else_skip': CHECK_HASH_IF_FAST_ELSE_SKIP, 507 'hash_always': CHECK_HASH_ALWAYS, 508 'hash_never': CHECK_HASH_NEVER, 509 'resumable_threshold': EIGHT_MIB, 510 'parallel_process_count': DEFAULT_PARALLEL_PROCESS_COUNT, 511 'parallel_thread_count': DEFAULT_PARALLEL_THREAD_COUNT, 512 'parallel_composite_upload_threshold': ( 513 DEFAULT_PARALLEL_COMPOSITE_UPLOAD_THRESHOLD), 514 'parallel_composite_upload_component_size': ( 515 DEFAULT_PARALLEL_COMPOSITE_UPLOAD_COMPONENT_SIZE), 516 'sliced_object_download_threshold': ( 517 DEFAULT_PARALLEL_COMPOSITE_UPLOAD_THRESHOLD), 518 'sliced_object_download_component_size': ( 519 DEFAULT_PARALLEL_COMPOSITE_UPLOAD_COMPONENT_SIZE), 520 'sliced_object_download_max_components': ( 521 DEFAULT_SLICED_OBJECT_DOWNLOAD_MAX_COMPONENTS), 522 'max_component_count': MAX_COMPONENT_COUNT} 523 524CONFIG_OAUTH2_CONFIG_CONTENT = """ 525[OAuth2] 526# This section specifies options used with OAuth2 authentication. 527 528# 'token_cache' specifies how the OAuth2 client should cache access tokens. 529# Valid values are: 530# 'in_memory': an in-memory cache is used. This is only useful if the boto 531# client instance (and with it the OAuth2 plugin instance) persists 532# across multiple requests. 533# 'file_system' : access tokens will be cached in the file system, in files 534# whose names include a key derived from the refresh token the access token 535# based on. 536# The default is 'file_system'. 537#token_cache = file_system 538#token_cache = in_memory 539 540# 'token_cache_path_pattern' specifies a path pattern for token cache files. 541# This option is only relevant if token_cache = file_system. 542# The value of this option should be a path, with place-holders '%(key)s' (which 543# will be replaced with a key derived from the refresh token the cached access 544# token was based on), and (optionally), %(uid)s (which will be replaced with 545# the UID of the current user, if available via os.getuid()). 546# Note that the config parser itself interpolates '%' placeholders, and hence 547# the above placeholders need to be escaped as '%%(key)s'. 548# The default value of this option is 549# token_cache_path_pattern = <tmpdir>/oauth2client-tokencache.%%(uid)s.%%(key)s 550# where <tmpdir> is the system-dependent default temp directory. 551 552# The following options specify the OAuth2 client identity and secret that is 553# used when requesting and using OAuth2 tokens. If not specified, a default 554# OAuth2 client for the gsutil tool is used; for uses of the boto library (with 555# OAuth2 authentication plugin) in other client software, it is recommended to 556# use a tool/client-specific OAuth2 client. For more information on OAuth2, see 557# http://code.google.com/apis/accounts/docs/OAuth2.html 558#client_id = <OAuth2 client id> 559#client_secret = <OAuth2 client secret> 560 561# The following options specify the label and endpoint URIs for the OAUth2 562# authorization provider being used. Primarily useful for tool developers. 563#provider_label = Google 564#provider_authorization_uri = https://accounts.google.com/o/oauth2/auth 565#provider_token_uri = https://accounts.google.com/o/oauth2/token 566 567# 'oauth2_refresh_retries' controls the number of retry attempts made when 568# rate limiting errors occur for OAuth2 requests to retrieve an access token. 569# The default value is 6. 570#oauth2_refresh_retries = <integer value> 571""" 572 573 574class ConfigCommand(Command): 575 """Implementation of gsutil config command.""" 576 577 # Command specification. See base class for documentation. 578 command_spec = Command.CreateCommandSpec( 579 'config', 580 command_name_aliases=['cfg', 'conf', 'configure'], 581 usage_synopsis=_SYNOPSIS, 582 min_args=0, 583 max_args=0, 584 supported_sub_args='habefwrs:o:', 585 file_url_ok=False, 586 provider_url_ok=False, 587 urls_start_arg=0, 588 ) 589 # Help specification. See help_provider.py for documentation. 590 help_spec = Command.HelpSpec( 591 help_name='config', 592 help_name_aliases=['cfg', 'conf', 'configure', 'aws', 's3'], 593 help_type='command_help', 594 help_one_line_summary=( 595 'Obtain credentials and create configuration file'), 596 help_text=_DETAILED_HELP_TEXT, 597 subcommand_help_text={}, 598 ) 599 600 def _OpenConfigFile(self, file_path): 601 """Creates and opens a configuration file for writing. 602 603 The file is created with mode 0600, and attempts to open existing files will 604 fail (the latter is important to prevent symlink attacks). 605 606 It is the caller's responsibility to close the file. 607 608 Args: 609 file_path: Path of the file to be created. 610 611 Returns: 612 A writable file object for the opened file. 613 614 Raises: 615 CommandException: if an error occurred when opening the file (including 616 when the file already exists). 617 """ 618 flags = os.O_RDWR | os.O_CREAT | os.O_EXCL 619 # Accommodate Windows; copied from python2.6/tempfile.py. 620 if hasattr(os, 'O_NOINHERIT'): 621 flags |= os.O_NOINHERIT 622 try: 623 fd = os.open(file_path, flags, 0600) 624 except (OSError, IOError), e: 625 raise CommandException('Failed to open %s for writing: %s' % 626 (file_path, e)) 627 return os.fdopen(fd, 'w') 628 629 def _CheckPrivateKeyFilePermissions(self, file_path): 630 """Checks that the file has reasonable permissions for a private key. 631 632 In particular, check that the filename provided by the user is not 633 world- or group-readable. If either of these are true, we issue a warning 634 and offer to fix the permissions. 635 636 Args: 637 file_path: The name of the private key file. 638 """ 639 if IS_WINDOWS: 640 # For Windows, this check doesn't work (it actually just checks whether 641 # the file is read-only). Since Windows files have a complicated ACL 642 # system, this check doesn't make much sense on Windows anyway, so we 643 # just don't do it. 644 return 645 646 st = os.stat(file_path) 647 if bool((stat.S_IRGRP | stat.S_IROTH) & st.st_mode): 648 self.logger.warn( 649 '\nYour private key file is readable by people other than yourself.\n' 650 'This is a security risk, since anyone with this information can use ' 651 'your service account.\n') 652 fix_it = raw_input('Would you like gsutil to change the file ' 653 'permissions for you? (y/N) ') 654 if fix_it in ('y', 'Y'): 655 try: 656 os.chmod(file_path, 0400) 657 self.logger.info( 658 '\nThe permissions on your file have been successfully ' 659 'modified.' 660 '\nThe only access allowed is readability by the user ' 661 '(permissions 0400 in chmod).') 662 except Exception, _: # pylint: disable=broad-except 663 self.logger.warn( 664 '\nWe were unable to modify the permissions on your file.\n' 665 'If you would like to fix this yourself, consider running:\n' 666 '"sudo chmod 400 </path/to/key>" for improved security.') 667 else: 668 self.logger.info( 669 '\nYou have chosen to allow this file to be readable by others.\n' 670 'If you would like to fix this yourself, consider running:\n' 671 '"sudo chmod 400 </path/to/key>" for improved security.') 672 673 def _PromptForProxyConfigVarAndMaybeSaveToBotoConfig(self, varname, prompt, 674 convert_to_bool=False): 675 """Prompts for one proxy config line, saves to boto.config if not empty. 676 677 Args: 678 varname: The config variable name. 679 prompt: The prompt to output to the user. 680 convert_to_bool: Whether to convert "y/n" to True/False. 681 """ 682 value = raw_input(prompt) 683 if value: 684 if convert_to_bool: 685 if value == 'y' or value == 'Y': 686 value = 'True' 687 else: 688 value = 'False' 689 boto.config.set('Boto', varname, value) 690 691 def _PromptForProxyConfig(self): 692 """Prompts for proxy config data, loads non-empty values into boto.config. 693 """ 694 self._PromptForProxyConfigVarAndMaybeSaveToBotoConfig( 695 'proxy', 'What is your proxy host? ') 696 self._PromptForProxyConfigVarAndMaybeSaveToBotoConfig( 697 'proxy_port', 'What is your proxy port? ') 698 self._PromptForProxyConfigVarAndMaybeSaveToBotoConfig( 699 'proxy_user', 'What is your proxy user (leave blank if not used)? ') 700 self._PromptForProxyConfigVarAndMaybeSaveToBotoConfig( 701 'proxy_pass', 'What is your proxy pass (leave blank if not used)? ') 702 self._PromptForProxyConfigVarAndMaybeSaveToBotoConfig( 703 'proxy_rdns', 704 'Should DNS lookups be resolved by your proxy? (Y if your site ' 705 'disallows client DNS lookups)? ', 706 convert_to_bool=True) 707 708 def _WriteConfigLineMaybeCommented(self, config_file, name, value, desc): 709 """Writes proxy name/value pair or comment line to config file. 710 711 Writes proxy name/value pair if value is not None. Otherwise writes 712 comment line. 713 714 Args: 715 config_file: File object to which the resulting config file will be 716 written. 717 name: The config variable name. 718 value: The value, or None. 719 desc: Human readable description (for comment). 720 """ 721 if not value: 722 name = '#%s' % name 723 value = '<%s>' % desc 724 config_file.write('%s = %s\n' % (name, value)) 725 726 def _WriteProxyConfigFileSection(self, config_file): 727 """Writes proxy section of configuration file. 728 729 Args: 730 config_file: File object to which the resulting config file will be 731 written. 732 """ 733 config = boto.config 734 config_file.write( 735 '# To use a proxy, edit and uncomment the proxy and proxy_port lines.\n' 736 '# If you need a user/password with this proxy, edit and uncomment\n' 737 '# those lines as well. If your organization also disallows DNS\n' 738 '# lookups by client machines set proxy_rdns = True\n' 739 '# If proxy_host and proxy_port are not specified in this file and\n' 740 '# one of the OS environment variables http_proxy, https_proxy, or\n' 741 '# HTTPS_PROXY is defined, gsutil will use the proxy server specified\n' 742 '# in these environment variables, in order of precedence according\n' 743 '# to how they are listed above.\n') 744 self._WriteConfigLineMaybeCommented( 745 config_file, 'proxy', config.get_value('Boto', 'proxy', None), 746 'proxy host') 747 self._WriteConfigLineMaybeCommented( 748 config_file, 'proxy_port', config.get_value('Boto', 'proxy_port', None), 749 'proxy port') 750 self._WriteConfigLineMaybeCommented( 751 config_file, 'proxy_user', config.get_value('Boto', 'proxy_user', None), 752 'proxy user') 753 self._WriteConfigLineMaybeCommented( 754 config_file, 'proxy_pass', config.get_value('Boto', 'proxy_pass', None), 755 'proxy password') 756 self._WriteConfigLineMaybeCommented( 757 config_file, 'proxy_rdns', 758 config.get_value('Boto', 'proxy_rdns', False), 759 'let proxy server perform DNS lookups') 760 761 # pylint: disable=dangerous-default-value,too-many-statements 762 def _WriteBotoConfigFile(self, config_file, launch_browser=True, 763 oauth2_scopes=[SCOPE_FULL_CONTROL], 764 cred_type=CredTypes.OAUTH2_USER_ACCOUNT): 765 """Creates a boto config file interactively. 766 767 Needed credentials are obtained interactively, either by asking the user for 768 access key and secret, or by walking the user through the OAuth2 approval 769 flow. 770 771 Args: 772 config_file: File object to which the resulting config file will be 773 written. 774 launch_browser: In the OAuth2 approval flow, attempt to open a browser 775 window and navigate to the approval URL. 776 oauth2_scopes: A list of OAuth2 scopes to request authorization for, when 777 using OAuth2. 778 cred_type: There are three options: 779 - for HMAC, ask the user for access key and secret 780 - for OAUTH2_USER_ACCOUNT, walk the user through OAuth2 approval flow 781 and produce a config with an oauth2_refresh_token credential. 782 - for OAUTH2_SERVICE_ACCOUNT, prompt the user for OAuth2 for service 783 account email address and private key file (and if the file is a .p12 784 file, the password for that file). 785 """ 786 # Collect credentials 787 provider_map = {'aws': 'aws', 'google': 'gs'} 788 uri_map = {'aws': 's3', 'google': 'gs'} 789 key_ids = {} 790 sec_keys = {} 791 service_account_key_is_json = False 792 if cred_type == CredTypes.OAUTH2_SERVICE_ACCOUNT: 793 gs_service_key_file = raw_input('What is the full path to your private ' 794 'key file? ') 795 # JSON files have the email address built-in and don't require a password. 796 try: 797 with open(gs_service_key_file, 'rb') as key_file_fp: 798 json.loads(key_file_fp.read()) 799 service_account_key_is_json = True 800 except ValueError: 801 if not HAS_CRYPTO: 802 raise CommandException( 803 'Service account authentication via a .p12 file requires ' 804 'either\nPyOpenSSL or PyCrypto 2.6 or later. Please install ' 805 'either of these\nto proceed, use a JSON-format key file, or ' 806 'configure a different type of credentials.') 807 808 if not service_account_key_is_json: 809 gs_service_client_id = raw_input('What is your service account email ' 810 'address? ') 811 gs_service_key_file_password = raw_input( 812 '\n'.join(textwrap.wrap( 813 'What is the password for your service key file [if you ' 814 'haven\'t set one explicitly, leave this line blank]?')) + ' ') 815 self._CheckPrivateKeyFilePermissions(gs_service_key_file) 816 elif cred_type == CredTypes.OAUTH2_USER_ACCOUNT: 817 oauth2_client = oauth2_helper.OAuth2ClientFromBotoConfig(boto.config, 818 cred_type) 819 try: 820 oauth2_refresh_token = oauth2_helper.OAuth2ApprovalFlow( 821 oauth2_client, oauth2_scopes, launch_browser) 822 except (ResponseNotReady, ServerNotFoundError, socket.error): 823 # TODO: Determine condition to check for in the ResponseNotReady 824 # exception so we only run proxy config flow if failure was caused by 825 # request being blocked because it wasn't sent through proxy. (This 826 # error could also happen if gsutil or the oauth2 client had a bug that 827 # attempted to incorrectly reuse an HTTP connection, for example.) 828 sys.stdout.write('\n'.join(textwrap.wrap( 829 "Unable to connect to accounts.google.com during OAuth2 flow. This " 830 "can happen if your site uses a proxy. If you are using gsutil " 831 "through a proxy, please enter the proxy's information; otherwise " 832 "leave the following fields blank.")) + '\n') 833 self._PromptForProxyConfig() 834 oauth2_client = oauth2_helper.OAuth2ClientFromBotoConfig(boto.config, 835 cred_type) 836 oauth2_refresh_token = oauth2_helper.OAuth2ApprovalFlow( 837 oauth2_client, oauth2_scopes, launch_browser) 838 elif cred_type == CredTypes.HMAC: 839 got_creds = False 840 for provider in provider_map: 841 if provider == 'google': 842 key_ids[provider] = raw_input('What is your %s access key ID? ' % 843 provider) 844 sec_keys[provider] = raw_input('What is your %s secret access key? ' % 845 provider) 846 got_creds = True 847 if not key_ids[provider] or not sec_keys[provider]: 848 raise CommandException( 849 'Incomplete credentials provided. Please try again.') 850 if not got_creds: 851 raise CommandException('No credentials provided. Please try again.') 852 853 # Write the config file prelude. 854 config_file.write(CONFIG_PRELUDE_CONTENT.lstrip()) 855 config_file.write( 856 '# This file was created by gsutil version %s at %s.\n' 857 % (gslib.VERSION, 858 datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))) 859 config_file.write( 860 '#\n# You can create additional configuration files by ' 861 'running\n# gsutil config [options] [-o <config-file>]\n\n\n') 862 863 # Write the config file Credentials section. 864 config_file.write('[Credentials]\n\n') 865 if cred_type == CredTypes.OAUTH2_SERVICE_ACCOUNT: 866 config_file.write('# Google OAuth2 service account credentials ' 867 '(for "gs://" URIs):\n') 868 config_file.write('gs_service_key_file = %s\n' % gs_service_key_file) 869 if not service_account_key_is_json: 870 config_file.write('gs_service_client_id = %s\n' 871 % gs_service_client_id) 872 873 if not gs_service_key_file_password: 874 config_file.write( 875 '# If you would like to set your password, you can do so using\n' 876 '# the following commands (replaced with your information):\n' 877 '# "openssl pkcs12 -in cert1.p12 -out temp_cert.pem"\n' 878 '# "openssl pkcs12 -export -in temp_cert.pem -out cert2.p12"\n' 879 '# "rm -f temp_cert.pem"\n' 880 '# Your initial password is "notasecret" - for more information,' 881 '\n# please see http://www.openssl.org/docs/apps/pkcs12.html.\n') 882 config_file.write('#gs_service_key_file_password =\n\n') 883 else: 884 config_file.write('gs_service_key_file_password = %s\n\n' 885 % gs_service_key_file_password) 886 elif cred_type == CredTypes.OAUTH2_USER_ACCOUNT: 887 config_file.write( 888 '# Google OAuth2 credentials (for "gs://" URIs):\n' 889 '# The following OAuth2 account is authorized for scope(s):\n') 890 for scope in oauth2_scopes: 891 config_file.write('# %s\n' % scope) 892 config_file.write( 893 'gs_oauth2_refresh_token = %s\n\n' % oauth2_refresh_token) 894 else: 895 config_file.write( 896 '# To add Google OAuth2 credentials ("gs://" URIs), ' 897 'edit and uncomment the\n# following line:\n' 898 '#gs_oauth2_refresh_token = <your OAuth2 refresh token>\n\n') 899 900 for provider in provider_map: 901 key_prefix = provider_map[provider] 902 uri_scheme = uri_map[provider] 903 if provider in key_ids and provider in sec_keys: 904 config_file.write('# %s credentials ("%s://" URIs):\n' % 905 (provider, uri_scheme)) 906 config_file.write('%s_access_key_id = %s\n' % 907 (key_prefix, key_ids[provider])) 908 config_file.write('%s_secret_access_key = %s\n' % 909 (key_prefix, sec_keys[provider])) 910 else: 911 config_file.write( 912 '# To add %s credentials ("%s://" URIs), edit and ' 913 'uncomment the\n# following two lines:\n' 914 '#%s_access_key_id = <your %s access key ID>\n' 915 '#%s_secret_access_key = <your %s secret access key>\n' % 916 (provider, uri_scheme, key_prefix, provider, key_prefix, 917 provider)) 918 host_key = Provider.HostKeyMap[provider] 919 config_file.write( 920 '# The ability to specify an alternate storage host and port\n' 921 '# is primarily for cloud storage service developers.\n' 922 '# Setting a non-default gs_host only works if prefer_api=xml.\n' 923 '#%s_host = <alternate storage host address>\n' 924 '#%s_port = <alternate storage host port>\n' 925 % (host_key, host_key)) 926 if host_key == 'gs': 927 config_file.write( 928 '#%s_json_host = <alternate JSON API storage host address>\n' 929 '#%s_json_port = <alternate JSON API storage host port>\n\n' 930 % (host_key, host_key)) 931 config_file.write('\n') 932 933 # Write the config file Boto section. 934 config_file.write('%s\n' % CONFIG_BOTO_SECTION_CONTENT) 935 self._WriteProxyConfigFileSection(config_file) 936 937 # Write the config file GSUtil section that doesn't depend on user input. 938 config_file.write(CONFIG_INPUTLESS_GSUTIL_SECTION_CONTENT) 939 940 # Write the default API version. 941 config_file.write(""" 942# 'default_api_version' specifies the default Google Cloud Storage XML API 943# version to use. If not set below gsutil defaults to API version 1. 944""") 945 api_version = 2 946 if cred_type == CredTypes.HMAC: api_version = 1 947 948 config_file.write('default_api_version = %d\n' % api_version) 949 950 # Write the config file GSUtil section that includes the default 951 # project ID input from the user. 952 if launch_browser: 953 sys.stdout.write( 954 'Attempting to launch a browser to open the Google Cloud Console at ' 955 'URL: %s\n\n' 956 '[Note: due to a Python bug, you may see a spurious error message ' 957 '"object is not\ncallable [...] in [...] Popen.__del__" which can ' 958 'be ignored.]\n\n' % GOOG_CLOUD_CONSOLE_URI) 959 sys.stdout.write( 960 'In your browser you should see the Cloud Console. Find the project ' 961 'you will\nuse, and then copy the Project ID string from the second ' 962 'column. Older projects do\nnot have Project ID strings. For such ' 963 'projects, click the project and then copy the\nProject Number ' 964 'listed under that project.\n\n') 965 if not webbrowser.open(GOOG_CLOUD_CONSOLE_URI, new=1, autoraise=True): 966 sys.stdout.write( 967 'Launching browser appears to have failed; please navigate a ' 968 'browser to the following URL:\n%s\n' % GOOG_CLOUD_CONSOLE_URI) 969 # Short delay; webbrowser.open on linux insists on printing out a message 970 # which we don't want to run into the prompt for the auth code. 971 time.sleep(2) 972 else: 973 sys.stdout.write( 974 '\nPlease navigate your browser to %s,\nthen find the project you ' 975 'will use, and copy the Project ID string from the\nsecond column. ' 976 'Older projects do not have Project ID strings. For such projects,\n' 977 'click the project and then copy the Project Number listed under ' 978 'that project.\n\n' % GOOG_CLOUD_CONSOLE_URI) 979 default_project_id = raw_input('What is your project-id? ').strip() 980 project_id_section_prelude = """ 981# 'default_project_id' specifies the default Google Cloud Storage project ID to 982# use with the 'mb' and 'ls' commands. This default can be overridden by 983# specifying the -p option to the 'mb' and 'ls' commands. 984""" 985 if not default_project_id: 986 raise CommandException( 987 'No default project ID entered. The default project ID is needed by ' 988 'the\nls and mb commands; please try again.') 989 config_file.write('%sdefault_project_id = %s\n\n\n' % 990 (project_id_section_prelude, default_project_id)) 991 992 # Write the config file OAuth2 section. 993 config_file.write(CONFIG_OAUTH2_CONFIG_CONTENT) 994 995 def RunCommand(self): 996 """Command entry point for the config command.""" 997 scopes = [] 998 cred_type = CredTypes.OAUTH2_USER_ACCOUNT 999 launch_browser = False 1000 output_file_name = None 1001 has_a = False 1002 has_e = False 1003 for opt, opt_arg in self.sub_opts: 1004 if opt == '-a': 1005 cred_type = CredTypes.HMAC 1006 has_a = True 1007 elif opt == '-b': 1008 launch_browser = True 1009 elif opt == '-e': 1010 cred_type = CredTypes.OAUTH2_SERVICE_ACCOUNT 1011 has_e = True 1012 elif opt == '-f': 1013 scopes.append(SCOPE_FULL_CONTROL) 1014 elif opt == '-o': 1015 output_file_name = opt_arg 1016 elif opt == '-r': 1017 scopes.append(SCOPE_READ_ONLY) 1018 elif opt == '-s': 1019 scopes.append(opt_arg) 1020 elif opt == '-w': 1021 scopes.append(SCOPE_READ_WRITE) 1022 else: 1023 self.RaiseInvalidArgumentException() 1024 1025 if has_e and has_a: 1026 raise CommandException('Both -a and -e cannot be specified. Please see ' 1027 '"gsutil help config" for more information.') 1028 1029 if not scopes: 1030 scopes.append(SCOPE_FULL_CONTROL) 1031 1032 default_config_path_bak = None 1033 if not output_file_name: 1034 # Check to see if a default config file name is requested via 1035 # environment variable. If so, use it, otherwise use the hard-coded 1036 # default file. Then use the default config file name, if it doesn't 1037 # exist or can be moved out of the way without clobbering an existing 1038 # backup file. 1039 boto_config_from_env = os.environ.get('BOTO_CONFIG', None) 1040 if boto_config_from_env: 1041 default_config_path = boto_config_from_env 1042 else: 1043 default_config_path = os.path.expanduser(os.path.join('~', '.boto')) 1044 if not os.path.exists(default_config_path): 1045 output_file_name = default_config_path 1046 else: 1047 default_config_path_bak = default_config_path + '.bak' 1048 if os.path.exists(default_config_path_bak): 1049 raise CommandException( 1050 'Cannot back up existing config ' 1051 'file "%s": backup file exists ("%s").' 1052 % (default_config_path, default_config_path_bak)) 1053 else: 1054 try: 1055 sys.stderr.write( 1056 'Backing up existing config file "%s" to "%s"...\n' 1057 % (default_config_path, default_config_path_bak)) 1058 os.rename(default_config_path, default_config_path_bak) 1059 except Exception, e: 1060 raise CommandException( 1061 'Failed to back up existing config ' 1062 'file ("%s" -> "%s"): %s.' 1063 % (default_config_path, default_config_path_bak, e)) 1064 output_file_name = default_config_path 1065 1066 if output_file_name == '-': 1067 output_file = sys.stdout 1068 else: 1069 output_file = self._OpenConfigFile(output_file_name) 1070 sys.stderr.write('\n'.join(textwrap.wrap( 1071 'This command will create a boto config file at %s containing your ' 1072 'credentials, based on your responses to the following questions.' 1073 % output_file_name)) + '\n') 1074 1075 # Catch ^C so we can restore the backup. 1076 RegisterSignalHandler(signal.SIGINT, _CleanupHandler) 1077 try: 1078 self._WriteBotoConfigFile(output_file, launch_browser=launch_browser, 1079 oauth2_scopes=scopes, cred_type=cred_type) 1080 except Exception as e: 1081 user_aborted = isinstance(e, AbortException) 1082 if user_aborted: 1083 sys.stderr.write('\nCaught ^C; cleaning up\n') 1084 # If an error occurred during config file creation, remove the invalid 1085 # config file and restore the backup file. 1086 if output_file_name != '-': 1087 output_file.close() 1088 os.unlink(output_file_name) 1089 try: 1090 if default_config_path_bak: 1091 sys.stderr.write('Restoring previous backed up file (%s)\n' % 1092 default_config_path_bak) 1093 os.rename(default_config_path_bak, output_file_name) 1094 except Exception as e: 1095 # Raise the original exception so that we can see what actually went 1096 # wrong, rather than just finding out that we died before assigning 1097 # a value to default_config_path_bak. 1098 raise e 1099 raise 1100 1101 if output_file_name != '-': 1102 output_file.close() 1103 if not boto.config.has_option('Boto', 'proxy'): 1104 sys.stderr.write('\n' + '\n'.join(textwrap.wrap( 1105 'Boto config file "%s" created.\nIf you need to use a proxy to ' 1106 'access the Internet please see the instructions in that file.' 1107 % output_file_name)) + '\n') 1108 1109 return 0 1110 1111 1112def _CleanupHandler(unused_signalnum, unused_handler): 1113 raise AbortException('User interrupted config command') 1114