-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
t_watts
committed
Oct 19, 2020
0 parents
commit 6c2bed9
Showing
5 changed files
with
231 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| *.json | ||
| settings.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| import subprocess | ||
| import json | ||
| from settings import ORGANIZATION_ID | ||
|
|
||
| FOLDER_LIST_CMD = 'gcloud resource-manager folders list --format json' | ||
| FOLDER_IAM_CMD = 'gcloud resource-manager folders get-iam-policy --format json' | ||
|
|
||
| def get_reverse_path(folder_id, path): | ||
| displayName = folders[folder_id]['displayName'] | ||
| parent = folders[folder_id]['parent'] | ||
|
|
||
| parts = parent.split('/') | ||
| type = parts[0] | ||
| parent_id = parts[1] | ||
| if type == 'folders': | ||
| return get_reverse_path(folder_id=parent_id, | ||
| path=(path + '/' + displayName)) | ||
| else: | ||
| return path + '/' + displayName | ||
| return parent_id | ||
|
|
||
|
|
||
| def get_proper_path(reverse_path): | ||
| parts = reverse_path.split('/') | ||
| parts.reverse() | ||
|
|
||
| path = '' | ||
| for part in parts: | ||
| if part: | ||
| path = path + '/' + part | ||
|
|
||
| return path | ||
|
|
||
|
|
||
| def get_path(folder_id): | ||
| displayName = folders[folder_id]['displayName'] | ||
| parent = folders[folder_id]['parent'] | ||
|
|
||
| parts = parent.split('/') | ||
| type = parts[0] | ||
| parent_id = parts[1] | ||
| if type != 'folders': | ||
| return '/' + displayName | ||
|
|
||
| parent_path = folders[parent_id].get('path') | ||
| if parent_path: | ||
| return parent_path + '/' + displayName | ||
|
|
||
| reverse_path = get_reverse_path(folder_id=folder_id, path='') | ||
| return get_proper_path(reverse_path=reverse_path) | ||
|
|
||
|
|
||
| def get_folders(folder_id, | ||
| type_flag='--folder'): | ||
|
|
||
| list_cmd = FOLDER_LIST_CMD + ' ' + type_flag + '=' + str(folder_id) | ||
| folder_list = json.loads(subprocess.check_output(list_cmd, | ||
| shell=True, | ||
| stderr=subprocess.STDOUT)) | ||
|
|
||
| for folder in folder_list: | ||
| name = folder['name'] | ||
| id = name.split('/')[1] | ||
| iam_cmd = FOLDER_IAM_CMD + ' ' + id | ||
| folder['perm'] = json.loads(subprocess.check_output(iam_cmd, | ||
| shell=True, | ||
| stderr=subprocess.STDOUT)) | ||
| folders[id] = folder | ||
| get_folders(folder_id=id) | ||
|
|
||
| return folders | ||
|
|
||
|
|
||
| def add_paths(): | ||
| for folder_id in folders: | ||
| folder = folders[folder_id] | ||
| # reverse_path = get_reverse_path(folder_id=folder_id, path='') | ||
| folder['path'] = get_path(folder_id=folder_id) | ||
|
|
||
|
|
||
| folders = {} | ||
| folders = get_folders(folder_id=ORGANIZATION_ID, | ||
| type_flag='--organization') | ||
|
|
||
| add_paths() | ||
|
|
||
| with open('folders.json', 'w') as outfile: | ||
| json.dump(folders, outfile, ensure_ascii=False) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import subprocess | ||
| import json | ||
| from settings import APPS_SCRIPT_FOLDER_ID, EXCLUDED_PROJECTS | ||
|
|
||
| IAM_POLICY_CMD = 'gcloud projects get-iam-policy --format json' | ||
|
|
||
| def init_projects(): | ||
| projects = {} | ||
| project_list_cmd = 'gcloud projects list --format json' | ||
|
|
||
| print('initializing projects') | ||
| proj_list = json.loads(subprocess.check_output(project_list_cmd, | ||
| shell=True, | ||
| stderr=subprocess.STDOUT)) | ||
| for proj in proj_list: | ||
| project_id = proj['projectId'] | ||
| if proj['parent']['id'] != APPS_SCRIPT_FOLDER_ID: | ||
| projects[project_id] = { | ||
| 'project': proj | ||
| } | ||
| return projects | ||
|
|
||
|
|
||
| def write_projects(projects): | ||
| with open('projects.json', 'w') as outfile: | ||
| json.dump(projects, outfile, ensure_ascii=False) | ||
|
|
||
|
|
||
| def read_projects(): | ||
| try: | ||
| with open('projects.json', 'r') as jsonfile: | ||
| return json.load(jsonfile) | ||
| except Exception: | ||
| projects = init_projects() | ||
| write_projects(projects=projects) | ||
| return projects | ||
|
|
||
|
|
||
| def get_iam_policy(project_id): | ||
| if project_id not in EXCLUDED_PROJECTS: | ||
| get_iam_policy_cmd = IAM_POLICY_CMD + ' ' + project_id | ||
| return json.loads(subprocess.check_output(get_iam_policy_cmd, | ||
| shell=True, | ||
| stderr=subprocess.STDOUT)) | ||
|
|
||
|
|
||
| projects = read_projects() | ||
| for project_id in projects: | ||
| proj = projects[project_id] | ||
| if not proj.get('iam_policy'): | ||
| print('reading iam_policy for ' + project_id) | ||
| proj['iam_policy'] = get_iam_policy(project_id=project_id) | ||
| write_projects(projects=projects) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| import json | ||
| import time | ||
| import pandas as pd | ||
|
|
||
| def get_entry(member, role, project): | ||
| parts = member.split(':') | ||
| type = parts[0] | ||
| email = parts[1] | ||
|
|
||
| entry = { | ||
| 'project': project, | ||
| 'role': role, | ||
| 'type': type, | ||
| 'email': email | ||
| } | ||
|
|
||
| if project['parent']['type'] == 'folder': | ||
| parent_id = project['parent']['id'] | ||
| folder = folders.get(parent_id) | ||
| entry['project']['parent']['folder'] = folder['displayName'] | ||
| entry['project']['parent']['path'] = folder['path'] | ||
|
|
||
| return entry | ||
|
|
||
|
|
||
| projects_by_user = {} | ||
| entries = [] | ||
| folders = {} | ||
|
|
||
| df = pd.DataFrame() | ||
|
|
||
| with open('folders.json', 'r') as jsonfile: | ||
| folders = json.load(jsonfile) | ||
|
|
||
| with open('projects.json', 'r') as jsonfile: | ||
| projects = json.load(jsonfile) | ||
|
|
||
| for project_id in projects.keys(): | ||
| proj = projects[project_id] | ||
|
|
||
| try: | ||
| for binding in proj['iam_policy']['bindings']: | ||
| for member in binding['members']: | ||
| entry = get_entry(member=member, | ||
| role=binding['role'], | ||
| project=proj['project']) | ||
|
|
||
|
|
||
| entry_df = pd.json_normalize(entry) | ||
| entries.append(entry_df) | ||
|
|
||
| #local_part = entry['email'].split('@')[0].lower() | ||
| #if not projects_by_user.get(local_part): | ||
| # projects_by_user[local_part] = [] | ||
|
|
||
| #projects_by_user[local_part].append(entry) | ||
| except: | ||
| pass | ||
|
|
||
| #print(json.dumps(projects_by_user)) | ||
| df = df.append(other=entries) | ||
|
|
||
| # get rid of the .'s in the column names created by json_normalize | ||
| df.columns = df.columns.str.replace(r".", "_") | ||
| df.columns = df.columns.str.replace("-", "_") | ||
|
|
||
| # add the date of the audit so we can create a time series | ||
| df['audit_time'] = pd.Timestamp.now().isoformat() | ||
|
|
||
| # convert all field values to string type | ||
| df = df.astype(str) | ||
|
|
||
| # workaround for pandas v1.1.1, due to the fact that astype(str) will convert a np.nan to the literal string 'nan'.... | ||
| # so we'll just flip it back to a none type.... | ||
| df = df.replace(['nan'], [None]) | ||
|
|
||
| #output to row delimited json | ||
| df.to_json(path_or_buf='owners_nldj.json',orient='records', lines=True, date_format='iso') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| # the org id for the domain | ||
| # found at Google Cloud Platform -> [Select Domain] -> IAM & Admin -> Settings | ||
| ORGANIZATION_ID = '' | ||
|
|
||
| # apps script project id. | ||
| # create a new app script project at script.google.com | ||
| APPS_SCRIPT_FOLDER_ID = '' | ||
|
|
||
| # projects to exclude in the audit | ||
| EXCLUDED_PROJECTS = [] |