def ValidateUserName(user_name): """Validates the initial user account name. Args: user_name: The user account name to be validated. Raises: CommandError: The user name is invalid. """ # Validates according to # http://technet.microsoft.com/en-us/library/cc770642.aspx # We also ban the builtin administrator account name. if not user_name: raise gcutil_errors.CommandError('The user name is missing.') if user_name.lower() == _BUILTIN_ADMINISTRATOR: raise gcutil_errors.CommandError( 'Using "%s" as initial user name is not allowed. ' 'Please choose a different user name by setting metadata entry %s.' % (_BUILTIN_ADMINISTRATOR, metadata.INITIAL_WINDOWS_USER_METADATA_NAME)) if len(user_name) > _MAX_USER_NAME_LENGTH: raise gcutil_errors.CommandError( 'User name length must not exceed %d characters: %s' % (_MAX_USER_NAME_LENGTH, user_name)) if any(c in _INVALID_USER_NAME_CHARS for c in user_name): raise gcutil_errors.CommandError( 'User name %s contains invalid characters.' % user_name) if all(c in ' .' for c in user_name): raise gcutil_errors.CommandError( 'User name cannot consist solely of periods or spaces.')
def ValidateStrongPasswordRequirement(password, user_account_name): """Validates that a password meets strong password requirement. The strong password must be at least 8 chars long and meet the Windows password complexity requirement documented at http://technet.microsoft.com/en-us/library/cc786468(v=ws.10).aspx Args: password: Password to be validated. user_account_name: The user account name. Raises: CommandError: The password does not meet the strong password requirement. """ if not password or len(password) < MIN_PASSWORD_LENGTH: raise gcutil_errors.CommandError( 'Windows password must be at least %d characters long.' % MIN_PASSWORD_LENGTH) categories = 0 uppercase = False lowercase = False digit = False nonalphanum = False alpha = False for x in password: if x.isupper(): uppercase = True elif x.islower(): lowercase = True elif x.isdigit(): digit = True elif x in NON_ALPHA_NUM_CHARS: nonalphanum = True elif x.isalpha(): alpha = True categories = uppercase + lowercase + digit + nonalphanum + alpha if categories >= MIN_CHAR_CATEGORIES: break if categories < MIN_CHAR_CATEGORIES: raise gcutil_errors.CommandError( 'Windows password must contain at least 3 types of characters. See ' 'http://technet.microsoft.com/en-us/library/cc786468(v=ws.10).aspx') # Currently, we do not set the user display name. So we only need to # check and make sure that the password does not contain the user account # name. if (len(user_account_name) >= 3 and user_account_name.lower() in password.lower()): raise gcutil_errors.CommandError( 'Windows password cannot contain the user account name: %s.' % user_account_name)
def Handle(self, target_pool_name): """Set the backup pool and failover ratio for the target pool. Args: target_pool_name: The name of the target pool to update. Returns: The result of inserting the forwarding rule. """ self._AutoDetectRegion() target_pool_context = self._context_parser.ParseContextOrPrompt( 'targetPools', target_pool_name) if (self._flags['failover_ratio'].present != self._flags['backup_pool'].present): raise gcutil_errors.CommandError( '--failover_ratio and --backup_pool ' 'must be either both set or both not ' 'set.') kwargs = self._PrepareRequestArgs(target_pool_context) request_body = {} if self._flags.backup_pool: kwargs['failoverRatio'] = self._flags.failover_ratio backup_pool = self._context_parser.NormalizeOrPrompt( 'targetPools', self._flags.backup_pool) request_body['target'] = backup_pool set_backup_request = self.api.target_pools.setBackup(body=request_body, **kwargs) return set_backup_request.execute()
def Handle(self, target_pool_name): """Remove the health check from the target pool. Args: target_pool_name: The name of the target_pool to update. Returns: The result of removing the health check from the target_pool. """ self._AutoDetectRegion() target_pool_context = self._context_parser.ParseContextOrPrompt( 'targetPools', target_pool_name) if self._flags.health_check is None: raise gcutil_errors.CommandError( 'Please specify a --health_check.') health_check = self._context_parser.NormalizeOrPrompt( 'httpHealthChecks', self._flags.health_check) target_pool_resource = { 'healthChecks': [{ 'healthCheck': health_check }] } kwargs = self._PrepareRequestArgs(target_pool_context) target_pool_request = self.api.target_pools.removeHealthCheck( body=target_pool_resource, **kwargs) return target_pool_request.execute()
def GetPublicKey(): """Returns the standard Compute key for the current user. If the key doesn't exist, it will be created and will interactively prompt the user. Returns: A dictionary of an user/key pair for the user's ssh key. Raises: gcutil_errors.CommandError: Requires --permit_root_ssh flag to log in as root. """ if FLAGS.ssh_user == 'root' and not FLAGS.permit_root_ssh: raise gcutil_errors.CommandError( 'Logging into instances as root is not recommended. If you actually ' 'wish to log in as root, you must provide the --permit_root_ssh ' 'flag.') SshKeys.EnsureSshKeyCreated() return { 'user': FLAGS.ssh_user, 'key': SshKeys.GetKeyFromFile(FLAGS.public_key_file) }
def GeneratePassword(user_account_name): """Generates a random password for Windows user account. Args: user_account_name: The user account name that we generate password for. Returns: The generated password. """ r = random.SystemRandom() for _ in xrange(_MAX_GENERATION_ATTEMPT): # First pick 12 chars randomly from letters and digits. char_list = list(r.choice(_CANDIDATES_ALPHA_NUM) for _ in xrange(12)) # Split the list into 3 groups of 4 chars each. # Add a random non-alpha-num char between the groups. char_list.insert(4, r.choice(_CANDIDATES_NON_ALPHA_NUM)) char_list.insert(9, r.choice(_CANDIDATES_NON_ALPHA_NUM)) password = ''.join(char_list) try: ValidateStrongPasswordRequirement(password, user_account_name) return password except gcutil_errors.CommandError: pass raise gcutil_errors.CommandError( 'Failed to generate password after %d attempts.' % _MAX_GENERATION_ATTEMPT)
def GenerateLocalUserNameBasedOnProject(project_id_or_number, api): """Generates the Windows user account name based on the project ID. Args: project_id_or_number: The string that is project ID or project number. api: The Google Compute Engine API client. Returns: Generated user account name. """ project_id = utils.GetProjectId(project_id_or_number, api) # We first remove the domain part in the project ID, and then take up to # the first 20 chars in the remaining project ID as the user name. # (Windows local user name should not be more than 20 char long.) user_name = project_id.split(':')[-1][:_MAX_USER_NAME_LENGTH] # Char set for project id is more restrictive than Windows user name, but we # still do the check just in case. try: ValidateUserName(user_name) except gcutil_errors.CommandError as e: raise gcutil_errors.CommandError( 'The user name %s generated from project id %s is invalid. ' 'This is unexpected. Please double check the project id. ' 'Error: %s' % (user_name, project_id, e.message)) return user_name
def Handle(self, firewall_name): """Add the specified firewall. Args: firewall_name: The name of the firewall to add. Returns: The result of inserting the firewall. Raises: gcutil_errors.CommandError: If the passed flag values cannot be interpreted. """ if not self._flags.allowed: raise gcutil_errors.CommandError( 'You must specify at least one rule through --allowed.') firewall_context = self._context_parser.ParseContextOrPrompt( 'firewalls', firewall_name) firewall_resource = { 'kind': self._GetResourceApiKind('firewall'), 'name': firewall_context['firewall'], 'description': self._flags.description, } if self._flags.network is not None: firewall_resource[ 'network'] = self._context_parser.NormalizeOrPrompt( 'networks', self._flags.network) if (not self._flags.allowed_ip_sources and not self._flags.allowed_tag_sources): self._flags.allowed_ip_sources.append('0.0.0.0/0') try: firewall_rules = FirewallRules(self._flags.allowed, self._flags.allowed_ip_sources) firewall_rules.SetTags(self._flags.allowed_tag_sources, self._flags.target_tags) firewall_rules.AddToFirewall(firewall_resource) firewall_request = self.api.firewalls.insert( project=firewall_context['project'], body=firewall_resource) return firewall_request.execute() except ValueError, e: raise gcutil_errors.CommandError(e)
def _VerifyAndGetTargetFromFlags( self, target_pool_flag, target_instance_flag): """Gets forwarding rule target from flag values. Args: target_pool_flag: the value of --target_pool flag target_instance_flag: the value of --target_instance flag Returns: (target_pool, target_instance) pair, with one and only one field set. """ if (target_pool_flag is None) == (target_instance_flag is None): raise gcutil_errors.CommandError('Please specify exactly one of ' '--target_pool or --target_instance.') elif target_pool_flag: return target_pool_flag, None else: if self.api.version < version.get('v1'): raise gcutil_errors.CommandError( 'Version does not support target instance.') return None, target_instance_flag
def Handle(self, target_pool_name): """Remove the instance from the target pool. Args: target_pool_name: The name of the target_pool to update. Returns: The result of removing an instance from the target_pool. """ self._AutoDetectRegion() self._AutoDetectZoneForInstances() target_pool_context = self._context_parser.ParseContextOrPrompt( 'targetPools', target_pool_name) if not self._flags.instances and not self._flags.instance: raise gcutil_errors.CommandError('Please specify --instances.') if self._flags.instance: gcutil_logging.LOGGER.warn( '--instance flag is deprecated; use --instances to remove one or more' ' instances') instances = [self._flags.instance] else: instances = self._flags.instances requests = [] kwargs = self._PrepareRequestArgs(target_pool_context) instance_urls = [] for zone_instance in instances: try: path = self._context_parser.NormalizeOrPrompt( 'instances', zone_instance) except ValueError: zone, instance = self.ParseZoneInstancePair(zone_instance) path = self.NormalizePerZoneResourceName( self._project, zone, 'instances', instance) instance_urls.append(path) remove_instance_request_resource = { 'instances': [{ 'instance': instance_url } for instance_url in instance_urls] } requests.append( self.api.target_pools.removeInstance( body=remove_instance_request_resource, **kwargs)) # This may be multiple API calls. return self.ExecuteRequests(requests)
def HandleCommand(self): """Set the metadata common to all instances in the specified project. Args: None. Returns: The result of setting the project wide metadata. Raises: gcutil_errors.CommandError: If the update would cause some metadata to be deleted. """ new_metadata = self._metadata_flags_processor.GatherMetadata() project = self._flags.project project_context = self._context_parser.ParseContextOrPrompt('projects', project) if not self._flags.force: get_request = self.api.projects.get(project=project_context['project']) project_resource = get_request.execute() project_metadata = project_resource.get('commonInstanceMetadata', []) if 'kind' in project_metadata: project_metadata = project_metadata.get('items', []) existing_keys = set([entry['key'] for entry in project_metadata]) new_keys = set([entry['key'] for entry in new_metadata]) dropped_keys = existing_keys - new_keys if dropped_keys: raise gcutil_errors.CommandError( 'Discarding update that would wipe out the following metadata: %s.' '\n\nRe-run with the -f flag to force the update.' % ', '.join(list(dropped_keys))) metadata_resource = {'kind': self._GetResourceApiKind('metadata'), 'items': new_metadata} if self._flags.fingerprint: metadata_resource['fingerprint'] = self._flags.fingerprint project_request = self.api.projects.setCommonInstanceMetadata( project=project_context['project'], body=metadata_resource) return project_request.execute()
def Handle(self, snapshot_name): """Add the specified snapshot. Args: snapshot_name: The name of the snapshot to add Returns: The result of inserting the snapshot. """ self._AutoDetectZone() snapshot_context = self._context_parser.ParseContextOrPrompt( 'snapshots', snapshot_name) if not self._flags.source_disk: disk = self._presenter.PromptForDisk(self.api.disks) if not disk: raise gcutil_errors.CommandError( 'You cannot create a snapshot if you have no disks.') self._flags.source_disk = disk['selfLink'] disk_context = self._context_parser.ParseContextOrPrompt( 'disks', self._flags.source_disk) snapshot_resource = { 'kind': self._GetResourceApiKind('snapshot'), 'name': snapshot_context['snapshot'], 'description': self._flags.description } kwargs = { 'project': snapshot_context['project'], 'zone': disk_context['zone'], 'disk': disk_context['disk'], 'body': snapshot_resource } snapshot_request = self.api.disks.createSnapshot(**kwargs) result = snapshot_request.execute() result = self._WaitForCompletionIfNeeded(result, snapshot_context, 'disks') return result
def RunWithFlagsAndPositionalArgs(self, flag_values, unused_pos_arg_values): """Run the command, returning the result. Args: flag_values: The parsed FlagValues instance. unused_pos_arg_values: The positional args. Raises: gcutil_errors.CommandError: If valid credentials cannot be retrieved. Returns: 0 if the command completes successfully, otherwise 1. Raises: CommandError: if valid credentials are not located. """ cred = auth_helper.GetCredentialFromStore( scopes.DEFAULT_AUTH_SCOPES, ask_user=not flag_values.just_check_auth, force_reauth=flag_values.force_reauth) if not cred: raise gcutil_errors.CommandError( 'Could not get valid credentials for API.') if flag_values.confirm_email: http = self._AuthenticateWrapper(utils.GetHttp()) resp, content = http.request( 'https://www.googleapis.com/userinfo/v2/me') if resp.status != 200: LOGGER.info('Could not get user info for token. <%d %s>', resp.status, resp.reason) userinfo = json.loads(content) if 'email' in userinfo and userinfo['email']: LOGGER.info('Authorization succeeded for user %s', userinfo['email']) else: LOGGER.info('Could not get email for token.') else: LOGGER.info('Authentication succeeded.') return (None, [])
def Handle(self, target_pool_name): """Add the specified target pool. Args: target_pool_name: The name of the target pool to add. Returns: The result of inserting the target pool. """ self._AutoDetectZoneForInstances() target_pool_context = self._context_parser.ParseContextOrPrompt( 'targetPools', target_pool_name) # Under no circumstance should we prompt for region again if we already # have. If it was otherwise specified in a fully qualified path, # assume that that's what the user meant for the backupPool. if not self._flags.region: self._flags.region = target_pool_context['region'] target_pool_resource = { 'kind': self._GetResourceApiKind('targetPool'), 'name': target_pool_context['targetPool'], 'description': self._flags.description, } target_pool_resource['sessionAffinity'] = self._flags.session_affinity if (self._flags['failover_ratio'].present != self._flags['backup_pool'].present): raise gcutil_errors.CommandError( '--failover_ratio and --backup_pool ' 'must be either both set or both not ' 'set.') if self._flags.failover_ratio: target_pool_resource['failoverRatio'] = self._flags.failover_ratio if self._flags.backup_pool: backup_pool = self._context_parser.NormalizeOrPrompt( 'targetPools', self._flags.backup_pool) target_pool_resource['backupPool'] = backup_pool health_checks = [] for health_check in self._flags.health_checks: health_checks.append( self._context_parser.NormalizeOrPrompt('httpHealthChecks', health_check)) target_pool_resource['healthChecks'] = health_checks instances = [] for zone_instance in self._flags.instances: try: path = self._context_parser.NormalizeOrPrompt( 'instances', zone_instance) except ValueError: zone, instance = self.ParseZoneInstancePair(zone_instance) path = self.NormalizePerZoneResourceName( self._project, zone, 'instances', instance) instances.append(path) target_pool_resource['instances'] = instances kwargs = {'region': target_pool_context['region']} target_pool_request = (self.api.target_pools.insert( project=target_pool_context['project'], body=target_pool_resource, **kwargs)) return target_pool_request.execute()
def CreateComputeApi( http, api_version, download=False, server=None): """Builds the Google Compute Engine API to use. Args: http: a httplib2.Http like object for communication. api_version: the version of the API to create. download: bool. Download the discovery document from the discovery service. server: URL of the API service host. Returns: The ComputeApi object to use. Raises: gcutil_errors.CommandError: If loading the discovery doc fails. """ # Use default server if none provided. default_server = 'https://www.googleapis.com/' server = server or default_server # Load the discovery document. discovery_document = None # Try to download the discovery document if download or server != default_server: url = '{server}/discovery/v1/apis/compute/{version}/rest'.format( server=server.rstrip('/'), version=api_version) response, content = http.request(url) if response.status == 200: discovery_document = content else: raise gcutil_errors.CommandError( 'Could not load discovery document from %s:\n%s' % ( url, content)) else: # Try to load locally discovery_path = os.path.join( os.path.dirname(__file__), 'compute', '%s.json' % api_version) try: with open(discovery_path) as discovery_file: discovery_document = discovery_file.read() except IOError as err: raise gcutil_errors.CommandError( 'Could not load discovery document from %s:\n%s' % ( discovery_path, err)) try: discovery_document = json.loads(discovery_document) except ValueError: raise errors.InvalidJsonError() api = discovery.build_from_document( discovery_document, http=http, model=model.JsonModel()) api = WrapApiIfNeeded(api) return ComputeApi( api, version.get(discovery_document.get('version')), GetSetOfApiMethods(discovery_document))