import errno import os import pickle import sys from argparse import ArgumentParser from base64 import b64decode from glob import glob from json import loads from random import choice from time import sleep from google.auth.transport.requests import Request from google_auth_oauthlib.flow import InstalledAppFlow from googleapiclient.discovery import build from googleapiclient.errors import HttpError SCOPES = ['https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/cloud-platform', 'https://www.googleapis.com/auth/iam'] project_create_ops = [] current_key_dump = [] sleep_time = 30 # Create count SAs in project def _create_accounts(service, project, count): batch = service.new_batch_http_request(callback=_def_batch_resp) for _ in range(count): aid = _generate_id('mfc-') batch.add(service.projects().serviceAccounts().create(name='projects/' + project, body={'accountId': aid, 'serviceAccount': { 'displayName': aid}})) batch.execute() # Create accounts needed to fill project def _create_remaining_accounts(iam, project): print('Creating accounts in %s' % project) sa_count = len(_list_sas(iam, project)) while sa_count != 100: _create_accounts(iam, project, 100 - sa_count) sa_count = len(_list_sas(iam, project)) # Generate a random id def _generate_id(prefix='saf-'): chars = '-abcdefghijklmnopqrstuvwxyz1234567890' return prefix + ''.join(choice(chars) for _ in range(25)) + choice(chars[1:]) # List projects using service def _get_projects(service): return [i['projectId'] for i in service.projects().list().execute()['projects']] # Default batch callback handler def _def_batch_resp(id, resp, exception): if exception is not None: if str(exception).startswith(' 0: current_count = len(_get_projects(cloud)) if current_count + create_projects <= max_projects: print('Creating %d projects' % (create_projects)) nprjs = _create_projects(cloud, create_projects) selected_projects = nprjs else: sys.exit('No, you cannot create %d new project (s).\n' 'Please reduce value of --quick-setup.\n' 'Remember that you can totally create %d projects (%d already).\n' 'Please do not delete existing projects unless you know what you are doing' % ( create_projects, max_projects, current_count)) else: print('Will overwrite all service accounts in existing projects.\n' 'So make sure you have some projects already.') input("Press Enter to continue...") if enable_services: ste = [enable_services] if enable_services == '~': ste = selected_projects elif enable_services == '*': ste = _get_projects(cloud) services = [i + '.googleapis.com' for i in services] print('Enabling services') _enable_services(serviceusage, ste, services) if create_sas: stc = [create_sas] if create_sas == '~': stc = selected_projects elif create_sas == '*': stc = _get_projects(cloud) for i in stc: _create_remaining_accounts(iam, i) if download_keys: try: os.mkdir(path) except OSError as e: if e.errno != errno.EEXIST: raise std = [download_keys] if download_keys == '~': std = selected_projects elif download_keys == '*': std = _get_projects(cloud) _create_sa_keys(iam, std, path) if delete_sas: std = [] std.append(delete_sas) if delete_sas == '~': std = selected_projects elif delete_sas == '*': std = _get_projects(cloud) for i in std: print('Deleting service accounts in %s' % i) _delete_sas(iam, i) if __name__ == '__main__': parse = ArgumentParser( description='A tool to create Google service accounts.') parse.add_argument('--path', '-p', default='accounts', help='Specify an alternate directory to output the credential files.') parse.add_argument('--token', default='token_sa.pickle', help='Specify the pickle token file path.') parse.add_argument('--credentials', default='credentials.json', help='Specify the credentials file path.') parse.add_argument('--list-projects', default=False, action='store_true', help='List projects viewable by the user.') parse.add_argument('--list-sas', default=False, help='List service accounts in a project.') parse.add_argument('--create-projects', type=int, default=None, help='Creates up to N projects.') parse.add_argument('--max-projects', type=int, default=12, help='Max amount of project allowed. Default: 12') parse.add_argument('--enable-services', default=None, help='Enables services on the project. Default: IAM and Drive') parse.add_argument('--services', nargs='+', default=['iam', 'drive'], help='Specify a different set of services to enable. Overrides the default.') parse.add_argument('--create-sas', default=None, help='Create service accounts in a project.') parse.add_argument('--delete-sas', default=None, help='Delete service accounts in a project.') parse.add_argument('--download-keys', default=None, help='Download keys for all the service accounts in a project.') parse.add_argument('--quick-setup', default=None, type=int, help='Create projects, enable services, create service accounts and download keys. ') parse.add_argument('--new-only', default=False, action='store_true', help='Do not use exisiting projects.') args = parse.parse_args() # If credentials file is invalid, search for one. if not os.path.exists(args.credentials): options = glob('*.json') print('No credentials found at %s. Please enable the Drive API in:\n' 'https://developers.google.com/drive/api/v3/quickstart/python\n' 'and save the json file as credentials.json' % args.credentials) if len(options) < 1: exit(-1) else: print('Select a credentials file below.') inp_options = [str(i) for i in list( range(1, len(options) + 1))] + options for i in range(len(options)): print(' %d) %s' % (i + 1, options[i])) inp = None while True: inp = input('> ') if inp in inp_options: break args.credentials = inp if inp in options else options[int(inp) - 1] print('Use --credentials %s next time to use this credentials file.' % args.credentials) if args.quick_setup: opt = '~' if args.new_only else '*' args.services = ['iam', 'drive'] args.create_projects = args.quick_setup args.enable_services = opt args.create_sas = opt args.download_keys = opt resp = serviceaccountfactory( path=args.path, token=args.token, credentials=args.credentials, list_projects=args.list_projects, list_sas=args.list_sas, create_projects=args.create_projects, max_projects=args.max_projects, create_sas=args.create_sas, delete_sas=args.delete_sas, enable_services=args.enable_services, services=args.services, download_keys=args.download_keys ) if resp is not None: if args.list_projects: if resp: print('Projects (%d):' % len(resp)) for i in resp: print(' ' + i) else: print('No projects.') elif args.list_sas: if resp: print('Service accounts in %s (%d):' % (args.list_sas, len(resp))) for i in resp: print(' %s (%s)' % (i['email'], i['uniqueId'])) else: print('No service accounts.')