diff options
author | Christian Cleberg <hello@cleberg.net> | 2025-04-07 22:52:59 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-04-07 22:52:59 -0500 |
commit | 8b78620c2c39c996007212616a586a8644ae7e33 (patch) | |
tree | 309e2dd196f9ce99893bf5f945dfcd8a8c0c3615 /applications | |
parent | bee22b97b652cd04fa470d7f31c3b917e44f3ab9 (diff) | |
download | audit-tools-8b78620c2c39c996007212616a586a8644ae7e33.tar.gz audit-tools-8b78620c2c39c996007212616a586a8644ae7e33.tar.bz2 audit-tools-8b78620c2c39c996007212616a586a8644ae7e33.zip |
Gitlab enhancements (#2)
* add various in-progress scripts for gitlab
* Commit from GitHub Actions (Ruff)
* add gitlab results for free-tier tools
* Commit from GitHub Actions (Ruff)
* add gitlab results for ultimate-tier tools
* Commit from GitHub Actions (Ruff)
---------
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Diffstat (limited to 'applications')
-rw-r--r-- | applications/gitlab/README.org | 103 | ||||
-rw-r--r-- | applications/gitlab/approvals.py | 38 | ||||
-rw-r--r-- | applications/gitlab/branch_protections.py | 25 | ||||
-rw-r--r-- | applications/gitlab/gitlab_admins.py | 25 | ||||
-rw-r--r-- | applications/gitlab/passwords.py | 29 | ||||
-rw-r--r-- | applications/gitlab/provisioning.py | 32 | ||||
-rw-r--r-- | applications/gitlab/users.py | 53 |
7 files changed, 278 insertions, 27 deletions
diff --git a/applications/gitlab/README.org b/applications/gitlab/README.org index 81d610d..37ce59b 100644 --- a/applications/gitlab/README.org +++ b/applications/gitlab/README.org @@ -1,11 +1,110 @@ #+title: GitLab Scripts -* =gitlab_admins.py= +* =approvals.py= + +\*This script requires an active Premium or Ultimate subscription.*\ + +#+begin_src sh +python ./approvals.py +#+end_src + +#+begin_src text +Rule: All Members + Approvals Required: 1 + Rule type: any_approver +Rule: Default + Approvals Required: 1 + Rule type: regular + Protected Branch: master + Eligible Approver: Christian Cleberg +#+end_src + +* =branch_protections.py= + +#+begin_src sh +python ./branch_protections.py +#+end_src + +#+begin_src json +[ + { + "id": 148448212, + "name": "main", + "push_access_levels": [ + { + "id": 185900194, + "access_level": 40, + "access_level_description": "Maintainers", + "deploy_key_id": null, + "user_id": null, + "group_id": null + } + ], + "merge_access_levels": [ + { + "id": 156461000, + "access_level": 40, + "access_level_description": "Maintainers", + "user_id": null, + "group_id": null + } + ], + "allow_force_push": false, + "unprotect_access_levels": [], + "code_owner_approval_required": false, + "inherited": false + } +] +#+end_src + +* =passwords.py= + +\*This script does not apply to GitLab.com. This is for self-hosted instances only.*\ + +#+begin_src sh +python ./passwords.py +#+end_src + +#+begin_src text +# TODO: Need access to a self-hosted version of GitLab to test this out. +#+end_src + +* =provisioning.py= + +\*This script requires an active Premium or Ultimate subscription.*\ #+begin_src sh -python ./gitlab_admins.py +python ./provisioning.py #+end_src #+begin_src text +Group: 105300140 + 2025-04-08T03:33:17.055Z : Action: member_created, Member: 128029250, Author: 24608590 +#+end_src + +* =users.py= + +#+begin_src sh +python ./users.py +#+end_src + +#+begin_src text +Access Level Roles: + 0 : No access + 5 : Minimal access + 10 : Guest + 15 : Planner + 20 : Reporter + 30 : Developer + 40 : Maintainer + 50 : Owner + 60 : Admin + + +Group 97083755 Members: +Username: ccleberg, Access Level: 50 + +Project 68701468 Members: Username: ccleberg, Access Level: 50 +Username: project_68701468_bot_2c7ee010a479c0e48cdb4c7c5cfae886, Access Level: 40 #+end_src diff --git a/applications/gitlab/approvals.py b/applications/gitlab/approvals.py new file mode 100644 index 0000000..b6ea3ef --- /dev/null +++ b/applications/gitlab/approvals.py @@ -0,0 +1,38 @@ +"""
+Extract merge request approval rules and their statuses in GitLab.
+"""
+
+import requests
+
+BASE_URL = "https://gitlab.com/api/v4"
+PRIVATE_TOKEN = "your_access_token"
+PROJECT_ID = "your_project_id"
+TIMEOUT = 30
+
+URL = f"{BASE_URL}/projects/{PROJECT_ID}/approval_rules"
+HEADERS = {"PRIVATE-TOKEN": PRIVATE_TOKEN}
+
+if __name__ == "__main__":
+ # Get approval rules
+ response = requests.get(URL, headers=HEADERS, timeout=TIMEOUT)
+ if response.status_code == 200:
+ approval_rules = response.json()
+ for rule in approval_rules:
+ name = rule["name"]
+ approvals_required = rule["approvals_required"]
+ rule_type = rule["rule_type"]
+ protected_branches = rule["protected_branches"]
+ eligible_approvers = rule["eligible_approvers"]
+ print(f"Rule: {name}")
+ print(f" Approvals Required: {approvals_required}")
+ print(f" Rule type: {rule_type}")
+ for branch in protected_branches:
+ branch_name = branch["name"]
+ print(f" Protected Branch: {branch_name}")
+ for approver in eligible_approvers:
+ approver_username = approver["name"]
+ print(f" Eligible Approver: {approver_username}")
+ else:
+ print(
+ f"Failed to fetch approval rules: {response.status_code}, {response.text}"
+ )
diff --git a/applications/gitlab/branch_protections.py b/applications/gitlab/branch_protections.py new file mode 100644 index 0000000..3d83165 --- /dev/null +++ b/applications/gitlab/branch_protections.py @@ -0,0 +1,25 @@ +"""
+List all branch protection rules and their configurations in GitLab.
+"""
+
+import requests
+import json
+
+BASE_URL = "https://gitlab.com/api/v4"
+PRIVATE_TOKEN = "your_access_token"
+PROJECT_ID = "your_project_id"
+TIMEOUT = 30
+
+URL = f"{BASE_URL}/projects/{PROJECT_ID}/protected_branches"
+HEADERS = {"PRIVATE-TOKEN": PRIVATE_TOKEN}
+
+if __name__ == "__main__":
+ # Get protected branches
+ response = requests.get(URL, headers=HEADERS, timeout=TIMEOUT)
+ if response.status_code == 200:
+ protected_branches = response.json()
+ print(json.dumps(protected_branches, indent=4))
+ else:
+ print(
+ f"Failed to fetch protected branches: {response.status_code}, {response.text}"
+ )
diff --git a/applications/gitlab/gitlab_admins.py b/applications/gitlab/gitlab_admins.py deleted file mode 100644 index 091032c..0000000 --- a/applications/gitlab/gitlab_admins.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -Gather all members of a GitLab group and their access levels. -""" - -import requests - -BASE_URL = "https://gitlab.com/api/v4" -PRIVATE_TOKEN = "your_access_token" -GROUP_ID = "your_group_id" -TIMEOUT = 30 - -URL = f"{BASE_URL}/groups/{GROUP_ID}/members" -HEADERS = {"PRIVATE-TOKEN": PRIVATE_TOKEN} - -if __name__ == "__main__": - # Get group members - response = requests.get(URL, headers=HEADERS, timeout=TIMEOUT) - if response.status_code == 200: - members = response.json() - for member in members: - print( - f"Username: {member['username']}, Access Level: {member['access_level']}" - ) - else: - print(f"Failed to fetch group members: {response.status_code}, {response.text}") diff --git a/applications/gitlab/passwords.py b/applications/gitlab/passwords.py new file mode 100644 index 0000000..84ab107 --- /dev/null +++ b/applications/gitlab/passwords.py @@ -0,0 +1,29 @@ +"""
+Verify if password policies are enforced in a self-hosted GitLab instance.
+
+ Ref: https://docs.gitlab.com/api/settings/
+"""
+
+import requests
+
+BASE_URL = "https://gitlab.com/api/v4"
+PRIVATE_TOKEN = "your_access_token"
+TIMEOUT = 30
+
+URL = f"{BASE_URL}/application/settings"
+HEADERS = {"PRIVATE-TOKEN": PRIVATE_TOKEN}
+
+if __name__ == "__main__":
+ # Get application settings
+ response = requests.get(URL, headers=HEADERS, timeout=TIMEOUT)
+ if response.status_code == 200:
+ settings = response.json()
+ password_length = settings.get("password_length", "Not set")
+ password_complexity = settings.get("password_complexity", "Not set")
+
+ print(f"Password Length: {password_length}")
+ print(f"Password Complexity: {password_complexity}")
+ else:
+ print(
+ f"Failed to fetch application settings: {response.status_code}, {response.text}"
+ )
diff --git a/applications/gitlab/provisioning.py b/applications/gitlab/provisioning.py new file mode 100644 index 0000000..bd0e695 --- /dev/null +++ b/applications/gitlab/provisioning.py @@ -0,0 +1,32 @@ +"""
+Track user creation and deletion events in GitLab with timestamps.
+"""
+
+import requests
+
+BASE_URL = "https://gitlab.com/api/v4"
+PRIVATE_TOKEN = "your_access_token"
+GROUP_ID = "your_group_id"
+TIMEOUT = 30
+
+URL = f"{BASE_URL}/groups/{GROUP_ID}/audit_events"
+HEADERS = {"PRIVATE-TOKEN": PRIVATE_TOKEN}
+
+if __name__ == "__main__":
+ # Get audit events
+ response = requests.get(URL, headers=HEADERS, timeout=TIMEOUT)
+ if response.status_code == 200:
+ audit_events = response.json()
+ for event in audit_events:
+ if event["entity_type"] == "User" or event["entity_type"] == "Group":
+ action = event["event_name"]
+ member_id = event["details"].get("member_id")
+ created_at = event["created_at"]
+ author = event["author_id"]
+ if action in ["member_created", "member_destroyed", "member_updated"]:
+ print(
+ f"Group: {GROUP_ID}\n",
+ f" {created_at} : Action: {action}, Member: {member_id}, Author: {author}",
+ )
+ else:
+ print(f"Failed to fetch audit events: {response.status_code}, {response.text}")
diff --git a/applications/gitlab/users.py b/applications/gitlab/users.py new file mode 100644 index 0000000..fab2646 --- /dev/null +++ b/applications/gitlab/users.py @@ -0,0 +1,53 @@ +""" +Gather all members of specified GitLab groups and projects and their access levels. + + Ref: https://docs.gitlab.com/api/members/ +""" + +import requests + +BASE_URL = "https://gitlab.com/api/v4" +PRIVATE_TOKEN = "your_access_token" +GROUP_IDS = ["group_id_1", "group_id_2"] # Add your group IDs here +PROJECT_IDS = ["project_id_1", "project_id_2"] # Add your project IDs here +TIMEOUT = 30 + +HEADERS = {"PRIVATE-TOKEN": PRIVATE_TOKEN} + + +def get_members(url, name): + response = requests.get(url, headers=HEADERS, timeout=TIMEOUT) + if response.status_code == 200: + members = response.json() + print(f"\n{name} Members:") + for member in members: + print( + f"Username: {member['username']}, Access Level: {member['access_level']}" + ) + else: + print( + f"Failed to fetch members for {name}: {response.status_code}, {response.text}" + ) + + +if __name__ == "__main__": + access_levels = """Access Level Roles: + 0 : No access + 5 : Minimal access + 10 : Guest + 15 : Planner + 20 : Reporter + 30 : Developer + 40 : Maintainer + 50 : Owner + 60 : Admin + """ + print(access_levels) + + for group_id in GROUP_IDS: + group_url = f"{BASE_URL}/groups/{group_id}/members" + get_members(group_url, f"Group {group_id}") + + for project_id in PROJECT_IDS: + project_url = f"{BASE_URL}/projects/{project_id}/members" + get_members(project_url, f"Project {project_id}") |