audit-labs/audit-tools

A collection of scripts, queries, and other goodies you can use in an audit. audit automation compliance evidence scripts

Commit bb3b669469

bb3b6694694e06d8faab3cd579fa9d4de16d636e

parent: 6817ff6335

Unsigned

cmc <hello@cleberg.net> · 2025-04-08 03:52 UTC
committer: <noreply@github.com>

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>

Layout: unified · split

applications/gitlab/README.org +101 −2
@@ -1,11 +1,110 @@
11#+title: GitLab Scripts
22
3* =gitlab_admins.py=
3* =approvals.py=
4
5\*This script requires an active Premium or Ultimate subscription.*\
6
7#+begin_src sh
8python ./approvals.py
9#+end_src
10
11#+begin_src text
12Rule: All Members
13 Approvals Required: 1
14 Rule type: any_approver
15Rule: Default
16 Approvals Required: 1
17 Rule type: regular
18 Protected Branch: master
19 Eligible Approver: Christian Cleberg
20#+end_src
21
22* =branch_protections.py=
23
24#+begin_src sh
25python ./branch_protections.py
26#+end_src
27
28#+begin_src json
29[
30 {
31 "id": 148448212,
32 "name": "main",
33 "push_access_levels": [
34 {
35 "id": 185900194,
36 "access_level": 40,
37 "access_level_description": "Maintainers",
38 "deploy_key_id": null,
39 "user_id": null,
40 "group_id": null
41 }
42 ],
43 "merge_access_levels": [
44 {
45 "id": 156461000,
46 "access_level": 40,
47 "access_level_description": "Maintainers",
48 "user_id": null,
49 "group_id": null
50 }
51 ],
52 "allow_force_push": false,
53 "unprotect_access_levels": [],
54 "code_owner_approval_required": false,
55 "inherited": false
56 }
57]
58#+end_src
59
60* =passwords.py=
61
62\*This script does not apply to GitLab.com. This is for self-hosted instances only.*\
63
64#+begin_src sh
65python ./passwords.py
66#+end_src
67
68#+begin_src text
69# TODO: Need access to a self-hosted version of GitLab to test this out.
70#+end_src
71
72* =provisioning.py=
73
74\*This script requires an active Premium or Ultimate subscription.*\
475
576#+begin_src sh
6python ./gitlab_admins.py
77python ./provisioning.py
778#+end_src
879
980#+begin_src text
81Group: 105300140
82 2025-04-08T03:33:17.055Z : Action: member_created, Member: 128029250, Author: 24608590
83#+end_src
84
85* =users.py=
86
87#+begin_src sh
88python ./users.py
89#+end_src
90
91#+begin_src text
92Access Level Roles:
93 0 : No access
94 5 : Minimal access
95 10 : Guest
96 15 : Planner
97 20 : Reporter
98 30 : Developer
99 40 : Maintainer
100 50 : Owner
101 60 : Admin
102
103
104Group 97083755 Members:
105Username: ccleberg, Access Level: 50
106
107Project 68701468 Members:
10108Username: ccleberg, Access Level: 50
109Username: project_68701468_bot_2c7ee010a479c0e48cdb4c7c5cfae886, Access Level: 40
11110#+end_src
applications/gitlab/approvals.py added +38
@@ -0,0 +1,38 @@
1"""
2Extract merge request approval rules and their statuses in GitLab.
3"""
4
5import requests
6
7BASE_URL = "https://gitlab.com/api/v4"
8PRIVATE_TOKEN = "your_access_token"
9PROJECT_ID = "your_project_id"
10TIMEOUT = 30
11
12URL = f"{BASE_URL}/projects/{PROJECT_ID}/approval_rules"
13HEADERS = {"PRIVATE-TOKEN": PRIVATE_TOKEN}
14
15if __name__ == "__main__":
16 # Get approval rules
17 response = requests.get(URL, headers=HEADERS, timeout=TIMEOUT)
18 if response.status_code == 200:
19 approval_rules = response.json()
20 for rule in approval_rules:
21 name = rule["name"]
22 approvals_required = rule["approvals_required"]
23 rule_type = rule["rule_type"]
24 protected_branches = rule["protected_branches"]
25 eligible_approvers = rule["eligible_approvers"]
26 print(f"Rule: {name}")
27 print(f" Approvals Required: {approvals_required}")
28 print(f" Rule type: {rule_type}")
29 for branch in protected_branches:
30 branch_name = branch["name"]
31 print(f" Protected Branch: {branch_name}")
32 for approver in eligible_approvers:
33 approver_username = approver["name"]
34 print(f" Eligible Approver: {approver_username}")
35 else:
36 print(
37 f"Failed to fetch approval rules: {response.status_code}, {response.text}"
38 )
applications/gitlab/branch_protections.py added +25
@@ -0,0 +1,25 @@
1"""
2List all branch protection rules and their configurations in GitLab.
3"""
4
5import requests
6import json
7
8BASE_URL = "https://gitlab.com/api/v4"
9PRIVATE_TOKEN = "your_access_token"
10PROJECT_ID = "your_project_id"
11TIMEOUT = 30
12
13URL = f"{BASE_URL}/projects/{PROJECT_ID}/protected_branches"
14HEADERS = {"PRIVATE-TOKEN": PRIVATE_TOKEN}
15
16if __name__ == "__main__":
17 # Get protected branches
18 response = requests.get(URL, headers=HEADERS, timeout=TIMEOUT)
19 if response.status_code == 200:
20 protected_branches = response.json()
21 print(json.dumps(protected_branches, indent=4))
22 else:
23 print(
24 f"Failed to fetch protected branches: {response.status_code}, {response.text}"
25 )
applications/gitlab/gitlab_admins.py deleted −25
@@ -1,25 +0,0 @@
1"""
2Gather all members of a GitLab group and their access levels.
3"""
4
5import requests
6
7BASE_URL = "https://gitlab.com/api/v4"
8PRIVATE_TOKEN = "your_access_token"
9GROUP_ID = "your_group_id"
10TIMEOUT = 30
11
12URL = f"{BASE_URL}/groups/{GROUP_ID}/members"
13HEADERS = {"PRIVATE-TOKEN": PRIVATE_TOKEN}
14
15if __name__ == "__main__":
16 # Get group members
17 response = requests.get(URL, headers=HEADERS, timeout=TIMEOUT)
18 if response.status_code == 200:
19 members = response.json()
20 for member in members:
21 print(
22 f"Username: {member['username']}, Access Level: {member['access_level']}"
23 )
24 else:
25 print(f"Failed to fetch group members: {response.status_code}, {response.text}")
applications/gitlab/passwords.py added +29
@@ -0,0 +1,29 @@
1"""
2Verify if password policies are enforced in a self-hosted GitLab instance.
3
4 Ref: https://docs.gitlab.com/api/settings/
5"""
6
7import requests
8
9BASE_URL = "https://gitlab.com/api/v4"
10PRIVATE_TOKEN = "your_access_token"
11TIMEOUT = 30
12
13URL = f"{BASE_URL}/application/settings"
14HEADERS = {"PRIVATE-TOKEN": PRIVATE_TOKEN}
15
16if __name__ == "__main__":
17 # Get application settings
18 response = requests.get(URL, headers=HEADERS, timeout=TIMEOUT)
19 if response.status_code == 200:
20 settings = response.json()
21 password_length = settings.get("password_length", "Not set")
22 password_complexity = settings.get("password_complexity", "Not set")
23
24 print(f"Password Length: {password_length}")
25 print(f"Password Complexity: {password_complexity}")
26 else:
27 print(
28 f"Failed to fetch application settings: {response.status_code}, {response.text}"
29 )
applications/gitlab/provisioning.py added +32
@@ -0,0 +1,32 @@
1"""
2Track user creation and deletion events in GitLab with timestamps.
3"""
4
5import requests
6
7BASE_URL = "https://gitlab.com/api/v4"
8PRIVATE_TOKEN = "your_access_token"
9GROUP_ID = "your_group_id"
10TIMEOUT = 30
11
12URL = f"{BASE_URL}/groups/{GROUP_ID}/audit_events"
13HEADERS = {"PRIVATE-TOKEN": PRIVATE_TOKEN}
14
15if __name__ == "__main__":
16 # Get audit events
17 response = requests.get(URL, headers=HEADERS, timeout=TIMEOUT)
18 if response.status_code == 200:
19 audit_events = response.json()
20 for event in audit_events:
21 if event["entity_type"] == "User" or event["entity_type"] == "Group":
22 action = event["event_name"]
23 member_id = event["details"].get("member_id")
24 created_at = event["created_at"]
25 author = event["author_id"]
26 if action in ["member_created", "member_destroyed", "member_updated"]:
27 print(
28 f"Group: {GROUP_ID}\n",
29 f" {created_at} : Action: {action}, Member: {member_id}, Author: {author}",
30 )
31 else:
32 print(f"Failed to fetch audit events: {response.status_code}, {response.text}")
applications/gitlab/users.py added +53
@@ -0,0 +1,53 @@
1"""
2Gather all members of specified GitLab groups and projects and their access levels.
3
4 Ref: https://docs.gitlab.com/api/members/
5"""
6
7import requests
8
9BASE_URL = "https://gitlab.com/api/v4"
10PRIVATE_TOKEN = "your_access_token"
11GROUP_IDS = ["group_id_1", "group_id_2"] # Add your group IDs here
12PROJECT_IDS = ["project_id_1", "project_id_2"] # Add your project IDs here
13TIMEOUT = 30
14
15HEADERS = {"PRIVATE-TOKEN": PRIVATE_TOKEN}
16
17
18def get_members(url, name):
19 response = requests.get(url, headers=HEADERS, timeout=TIMEOUT)
20 if response.status_code == 200:
21 members = response.json()
22 print(f"\n{name} Members:")
23 for member in members:
24 print(
25 f"Username: {member['username']}, Access Level: {member['access_level']}"
26 )
27 else:
28 print(
29 f"Failed to fetch members for {name}: {response.status_code}, {response.text}"
30 )
31
32
33if __name__ == "__main__":
34 access_levels = """Access Level Roles:
35 0 : No access
36 5 : Minimal access
37 10 : Guest
38 15 : Planner
39 20 : Reporter
40 30 : Developer
41 40 : Maintainer
42 50 : Owner
43 60 : Admin
44 """
45 print(access_levels)
46
47 for group_id in GROUP_IDS:
48 group_url = f"{BASE_URL}/groups/{group_id}/members"
49 get_members(group_url, f"Group {group_id}")
50
51 for project_id in PROJECT_IDS:
52 project_url = f"{BASE_URL}/projects/{project_id}/members"
53 get_members(project_url, f"Project {project_id}")