• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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