From 8b78620c2c39c996007212616a586a8644ae7e33 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Mon, 7 Apr 2025 22:52:59 -0500 Subject: 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> --- applications/gitlab/README.org | 103 +++++++++++++++++++++++++++++- applications/gitlab/approvals.py | 38 +++++++++++ applications/gitlab/branch_protections.py | 25 ++++++++ applications/gitlab/gitlab_admins.py | 25 -------- applications/gitlab/passwords.py | 29 +++++++++ applications/gitlab/provisioning.py | 32 ++++++++++ applications/gitlab/users.py | 53 +++++++++++++++ 7 files changed, 278 insertions(+), 27 deletions(-) create mode 100644 applications/gitlab/approvals.py create mode 100644 applications/gitlab/branch_protections.py delete mode 100644 applications/gitlab/gitlab_admins.py create mode 100644 applications/gitlab/passwords.py create mode 100644 applications/gitlab/provisioning.py create mode 100644 applications/gitlab/users.py (limited to 'applications') 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}") -- cgit v1.2.3-70-g09d2