Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
anasty17
GitHub Repository: anasty17/mirror-leech-telegram-bot
Path: blob/master/gen_sa_accounts.py
1643 views
1
from argparse import ArgumentParser
2
from base64 import b64decode
3
from errno import EEXIST
4
from glob import glob
5
from json import loads
6
from os import mkdir, path as ospath
7
from pickle import dump, load
8
from random import choice
9
from sys import exit
10
from time import sleep
11
12
from google.auth.transport.requests import Request
13
from google_auth_oauthlib.flow import InstalledAppFlow
14
from googleapiclient.discovery import build
15
from googleapiclient.errors import HttpError
16
17
SCOPES = [
18
"https://www.googleapis.com/auth/drive",
19
"https://www.googleapis.com/auth/cloud-platform",
20
"https://www.googleapis.com/auth/iam",
21
]
22
project_create_ops = []
23
current_key_dump = []
24
sleep_time = 30
25
CHARS = "-abcdefghijklmnopqrstuvwxyz1234567890"
26
27
28
def _create_accounts(service, project, count):
29
batch = service.new_batch_http_request(callback=_def_batch_resp)
30
for _ in range(count):
31
aid = _generate_id("mfc-")
32
batch.add(
33
service.projects()
34
.serviceAccounts()
35
.create(
36
name=f"projects/{project}",
37
body={
38
"accountId": aid,
39
"serviceAccount": {"displayName": aid},
40
},
41
)
42
)
43
try:
44
batch.execute()
45
except HttpError as e:
46
print("Error creating accounts:", e)
47
48
49
def _create_remaining_accounts(iam, project):
50
print(f"Creating accounts in {project}")
51
sa_count = len(_list_sas(iam, project))
52
while sa_count != 100:
53
_create_accounts(iam, project, 100 - sa_count)
54
sa_count = len(_list_sas(iam, project))
55
56
57
def _generate_id(prefix="saf-"):
58
return prefix + "".join(choice(CHARS) for _ in range(25)) + choice(CHARS[1:])
59
60
61
def _get_projects(service):
62
try:
63
return [i["projectId"] for i in service.projects().list().execute()["projects"]]
64
except HttpError as e:
65
print("Error fetching projects:", e)
66
return []
67
68
69
def _def_batch_resp(id, resp, exception):
70
if exception is not None:
71
if str(exception).startswith("<HttpError 429"):
72
sleep(sleep_time / 100)
73
else:
74
print("Batch error:", exception)
75
76
77
def _pc_resp(id, resp, exception):
78
global project_create_ops
79
if exception is not None:
80
print("Project creation error:", exception)
81
else:
82
for i in resp.values():
83
project_create_ops.append(i)
84
85
86
def _create_projects(cloud, count):
87
global project_create_ops
88
batch = cloud.new_batch_http_request(callback=_pc_resp)
89
new_projs = []
90
for _ in range(count):
91
new_proj = _generate_id()
92
new_projs.append(new_proj)
93
batch.add(cloud.projects().create(body={"project_id": new_proj}))
94
try:
95
batch.execute()
96
except HttpError as e:
97
print("Error creating projects:", e)
98
return []
99
100
for op in project_create_ops:
101
while True:
102
try:
103
resp = cloud.operations().get(name=op).execute()
104
if resp.get("done"):
105
break
106
except HttpError as e:
107
print("Error fetching operation status:", e)
108
break
109
sleep(3)
110
return new_projs
111
112
113
def _enable_services(service, projects, ste):
114
batch = service.new_batch_http_request(callback=_def_batch_resp)
115
for project in projects:
116
for s in ste:
117
batch.add(
118
service.services().enable(name=f"projects/{project}/services/{s}")
119
)
120
try:
121
batch.execute()
122
except HttpError as e:
123
print("Error enabling services:", e)
124
125
126
def _list_sas(iam, project):
127
try:
128
resp = (
129
iam.projects()
130
.serviceAccounts()
131
.list(name=f"projects/{project}", pageSize=100)
132
.execute()
133
)
134
return resp.get("accounts", [])
135
except HttpError as e:
136
print("Error listing service accounts:", e)
137
return []
138
139
140
def _batch_keys_resp(id, resp, exception):
141
global current_key_dump
142
if exception is not None:
143
current_key_dump = None
144
sleep(sleep_time / 100)
145
elif current_key_dump is None:
146
sleep(sleep_time / 100)
147
else:
148
try:
149
key_name = resp["name"][resp["name"].rfind("/") :]
150
key_data = b64decode(resp["privateKeyData"]).decode("utf-8")
151
current_key_dump.append((key_name, key_data))
152
except Exception as e:
153
print("Error processing key response:", e)
154
155
156
def _create_sa_keys(iam, projects, path_dir):
157
global current_key_dump
158
for project in projects:
159
current_key_dump = []
160
print(f"Downloading keys from {project}")
161
while current_key_dump is None or len(current_key_dump) != 100:
162
batch = iam.new_batch_http_request(callback=_batch_keys_resp)
163
total_sas = _list_sas(iam, project)
164
for sa in total_sas:
165
batch.add(
166
iam.projects()
167
.serviceAccounts()
168
.keys()
169
.create(
170
name=f"projects/{project}/serviceAccounts/{sa['uniqueId']}",
171
body={
172
"privateKeyType": "TYPE_GOOGLE_CREDENTIALS_FILE",
173
"keyAlgorithm": "KEY_ALG_RSA_2048",
174
},
175
)
176
)
177
try:
178
batch.execute()
179
except HttpError as e:
180
print("Error creating SA keys:", e)
181
current_key_dump = None
182
183
if current_key_dump is None:
184
print(f"Redownloading keys from {project}")
185
current_key_dump = []
186
else:
187
for index, key in enumerate(current_key_dump):
188
try:
189
with open(f"{path_dir}/{index}.json", "w+") as f:
190
f.write(key[1])
191
except IOError as e:
192
print(f"Error writing key file {index}.json:", e)
193
194
195
def _delete_sas(iam, project):
196
sas = _list_sas(iam, project)
197
batch = iam.new_batch_http_request(callback=_def_batch_resp)
198
for account in sas:
199
batch.add(iam.projects().serviceAccounts().delete(name=account["name"]))
200
try:
201
batch.execute()
202
except HttpError as e:
203
print("Error deleting service accounts:", e)
204
205
206
def serviceaccountfactory(
207
credentials="credentials.json",
208
token="token_sa.pickle",
209
path=None,
210
list_projects=False,
211
list_sas=None,
212
create_projects=None,
213
max_projects=12,
214
enable_services=None,
215
services=None,
216
create_sas=None,
217
delete_sas=None,
218
download_keys=None,
219
):
220
if services is None:
221
services = ["iam", "drive"]
222
selected_projects = []
223
try:
224
proj_id = loads(open(credentials, "r").read())["installed"]["project_id"]
225
except Exception as e:
226
exit(f"Error reading credentials file: {str(e)}")
227
228
creds = None
229
if path and not path.endswith("/"):
230
path = path.rstrip("/")
231
232
if path and path != "accounts":
233
try:
234
mkdir(path)
235
except OSError as e:
236
if e.errno != EEXIST:
237
print("Error creating output directory:", e)
238
exit(1)
239
240
if ospath.exists(token):
241
try:
242
with open(token, "rb") as t:
243
creds = load(t)
244
except Exception as e:
245
print("Error loading token file:", e)
246
if not creds or not creds.valid:
247
try:
248
if creds and creds.expired and creds.refresh_token:
249
creds.refresh(Request())
250
else:
251
flow = InstalledAppFlow.from_client_secrets_file(credentials, SCOPES)
252
creds = flow.run_local_server(port=0, open_browser=False)
253
with open(token, "wb") as t:
254
dump(creds, t)
255
except Exception as e:
256
exit(f"Error obtaining credentials: {str(e)}")
257
258
try:
259
cloud = build("cloudresourcemanager", "v1", credentials=creds)
260
iam = build("iam", "v1", credentials=creds)
261
serviceusage = build("serviceusage", "v1", credentials=creds)
262
except Exception as e:
263
exit(f"Error building service clients: {str(e)}")
264
265
projs = None
266
while projs is None:
267
try:
268
projs = _get_projects(cloud)
269
except HttpError:
270
try:
271
serviceusage.services().enable(
272
name=f"projects/{proj_id}/services/cloudresourcemanager.googleapis.com"
273
).execute()
274
except HttpError as ee:
275
print("Error enabling cloudresourcemanager:", ee)
276
input("Press Enter to retry.")
277
if list_projects:
278
return _get_projects(cloud)
279
if list_sas:
280
return _list_sas(iam, list_sas)
281
if create_projects:
282
print(f"Creating projects: {create_projects}")
283
if create_projects > 0:
284
current_count = len(_get_projects(cloud))
285
if current_count + create_projects <= max_projects:
286
print("Creating %d projects" % create_projects)
287
nprjs = _create_projects(cloud, create_projects)
288
selected_projects = nprjs
289
else:
290
exit(
291
"Cannot create %d new project(s).\n"
292
"Please reduce the value or delete existing projects.\n"
293
"Max projects allowed: %d, already in use: %d"
294
% (create_projects, max_projects, current_count)
295
)
296
else:
297
print("Overwriting all service accounts in existing projects.")
298
input("Press Enter to continue...")
299
300
if enable_services:
301
target = [enable_services]
302
if enable_services == "~":
303
target = selected_projects
304
elif enable_services == "*":
305
target = _get_projects(cloud)
306
service_list = [f"{s}.googleapis.com" for s in services]
307
print("Enabling services")
308
_enable_services(serviceusage, target, service_list)
309
if create_sas:
310
target = [create_sas]
311
if create_sas == "~":
312
target = selected_projects
313
elif create_sas == "*":
314
target = _get_projects(cloud)
315
for proj in target:
316
_create_remaining_accounts(iam, proj)
317
if download_keys:
318
target = [download_keys]
319
if download_keys == "~":
320
target = selected_projects
321
elif download_keys == "*":
322
target = _get_projects(cloud)
323
_create_sa_keys(iam, target, path)
324
if delete_sas:
325
target = [delete_sas]
326
if delete_sas == "~":
327
target = selected_projects
328
elif delete_sas == "*":
329
target = _get_projects(cloud)
330
for proj in target:
331
print(f"Deleting service accounts in {proj}")
332
_delete_sas(iam, proj)
333
334
335
if __name__ == "__main__":
336
parse = ArgumentParser(description="A tool to create Google service accounts.")
337
parse.add_argument(
338
"--path",
339
"-p",
340
default="accounts",
341
help="Specify an alternate directory to output the credential files.",
342
)
343
parse.add_argument("--token", default="token_sa.pickle", help="Token file path.")
344
parse.add_argument(
345
"--credentials",
346
default="credentials.json",
347
help="Credentials file path.",
348
)
349
parse.add_argument(
350
"--list-projects",
351
default=False,
352
action="store_true",
353
help="List projects viewable by the user.",
354
)
355
parse.add_argument(
356
"--list-sas", default=False, help="List service accounts in a project."
357
)
358
parse.add_argument(
359
"--create-projects", type=int, default=None, help="Creates up to N projects."
360
)
361
parse.add_argument(
362
"--max-projects",
363
type=int,
364
default=12,
365
help="Max projects allowed. Default: 12",
366
)
367
parse.add_argument(
368
"--enable-services",
369
default=None,
370
help="Enables services on the project. Default: IAM and Drive",
371
)
372
parse.add_argument(
373
"--services",
374
nargs="+",
375
default=["iam", "drive"],
376
help="Specify a different set of services to enable.",
377
)
378
parse.add_argument(
379
"--create-sas", default=None, help="Create service accounts in a project."
380
)
381
parse.add_argument(
382
"--delete-sas", default=None, help="Delete service accounts in a project."
383
)
384
parse.add_argument(
385
"--download-keys",
386
default=None,
387
help="Download keys for service accounts in a project.",
388
)
389
parse.add_argument(
390
"--quick-setup",
391
default=None,
392
type=int,
393
help="Create projects, enable services, create SAs and download keys.",
394
)
395
parse.add_argument(
396
"--new-only",
397
default=False,
398
action="store_true",
399
help="Do not use existing projects.",
400
)
401
args = parse.parse_args()
402
403
if not ospath.exists(args.credentials):
404
options = glob("*.json")
405
print(
406
f"No credentials found at {args.credentials}. Please enable the Drive API and save the JSON as {args.credentials}."
407
)
408
if not options:
409
exit("No available credential files found.")
410
else:
411
print("Select a credentials file:")
412
for idx, opt in enumerate(options):
413
print(f" {idx + 1}) {opt}")
414
while True:
415
inp = input("> ")
416
try:
417
choice_idx = int(inp) - 1
418
if 0 <= choice_idx < len(options):
419
args.credentials = options[choice_idx]
420
break
421
except ValueError:
422
if inp in options:
423
args.credentials = inp
424
break
425
print(
426
f"Use --credentials {args.credentials} next time to use this credentials file."
427
)
428
if args.quick_setup:
429
opt = "~" if args.new_only else "*"
430
args.services = ["iam", "drive"]
431
args.create_projects = args.quick_setup
432
args.enable_services = opt
433
args.create_sas = opt
434
args.download_keys = opt
435
resp = serviceaccountfactory(
436
path=args.path,
437
token=args.token,
438
credentials=args.credentials,
439
list_projects=args.list_projects,
440
list_sas=args.list_sas,
441
create_projects=args.create_projects,
442
max_projects=args.max_projects,
443
create_sas=args.create_sas,
444
delete_sas=args.delete_sas,
445
enable_services=args.enable_services,
446
services=args.services,
447
download_keys=args.download_keys,
448
)
449
if resp is not None:
450
if args.list_projects:
451
if resp:
452
print("Projects (%d):" % len(resp))
453
for proj in resp:
454
print(f" {proj}")
455
else:
456
print("No projects found.")
457
elif args.list_sas:
458
if resp:
459
print("Service accounts in %s (%d):" % (args.list_sas, len(resp)))
460
for sa in resp:
461
print(f" {sa['email']} ({sa['uniqueId']})")
462
else:
463
print("No service accounts found.")
464
465