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 @@ |
| 1 | #+title: GitLab Scripts |
1 | #+title: GitLab Scripts |
| 2 | |
2 | |
| 3 | * =gitlab_admins.py= |
3 | * =approvals.py= |
| |
4 | |
| |
5 | \*This script requires an active Premium or Ultimate subscription.*\ |
| |
6 | |
| |
7 | #+begin_src sh |
| |
8 | python ./approvals.py |
| |
9 | #+end_src |
| |
10 | |
| |
11 | #+begin_src text |
| |
12 | Rule: All Members |
| |
13 | Approvals Required: 1 |
| |
14 | Rule type: any_approver |
| |
15 | Rule: 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 |
| |
25 | python ./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 |
| |
65 | python ./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.*\ |
| 4 | |
75 | |
| 5 | #+begin_src sh |
76 | #+begin_src sh |
| 6 | python ./gitlab_admins.py |
77 | python ./provisioning.py |
| 7 | #+end_src |
78 | #+end_src |
| 8 | |
79 | |
| 9 | #+begin_src text |
80 | #+begin_src text |
| |
81 | Group: 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 |
| |
88 | python ./users.py |
| |
89 | #+end_src |
| |
90 | |
| |
91 | #+begin_src text |
| |
92 | Access 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 | |
| |
104 | Group 97083755 Members: |
| |
105 | Username: ccleberg, Access Level: 50 |
| |
106 | |
| |
107 | Project 68701468 Members: |
| 10 | Username: ccleberg, Access Level: 50 |
108 | Username: ccleberg, Access Level: 50 |
| |
109 | Username: project_68701468_bot_2c7ee010a479c0e48cdb4c7c5cfae886, Access Level: 40 |
| 11 | #+end_src |
110 | #+end_src |
applications/gitlab/approvals.py
added
+38
| @@ -0,0 +1,38 @@ |
| |
1 | """ |
| |
2 | Extract merge request approval rules and their statuses in GitLab. |
| |
3 | """ |
| |
4 |
|
| |
5 | import requests |
| |
6 |
|
| |
7 | BASE_URL = "https://gitlab.com/api/v4" |
| |
8 | PRIVATE_TOKEN = "your_access_token" |
| |
9 | PROJECT_ID = "your_project_id" |
| |
10 | TIMEOUT = 30 |
| |
11 |
|
| |
12 | URL = f"{BASE_URL}/projects/{PROJECT_ID}/approval_rules" |
| |
13 | HEADERS = {"PRIVATE-TOKEN": PRIVATE_TOKEN} |
| |
14 |
|
| |
15 | if __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 | """ |
| |
2 | List all branch protection rules and their configurations in GitLab. |
| |
3 | """ |
| |
4 |
|
| |
5 | import requests |
| |
6 | import json |
| |
7 |
|
| |
8 | BASE_URL = "https://gitlab.com/api/v4" |
| |
9 | PRIVATE_TOKEN = "your_access_token" |
| |
10 | PROJECT_ID = "your_project_id" |
| |
11 | TIMEOUT = 30 |
| |
12 |
|
| |
13 | URL = f"{BASE_URL}/projects/{PROJECT_ID}/protected_branches" |
| |
14 | HEADERS = {"PRIVATE-TOKEN": PRIVATE_TOKEN} |
| |
15 |
|
| |
16 | if __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 | """ |
| |
| 2 | Gather all members of a GitLab group and their access levels. |
| |
| 3 | """ |
| |
| 4 | |
| |
| 5 | import requests |
| |
| 6 | |
| |
| 7 | BASE_URL = "https://gitlab.com/api/v4" |
| |
| 8 | PRIVATE_TOKEN = "your_access_token" |
| |
| 9 | GROUP_ID = "your_group_id" |
| |
| 10 | TIMEOUT = 30 |
| |
| 11 | |
| |
| 12 | URL = f"{BASE_URL}/groups/{GROUP_ID}/members" |
| |
| 13 | HEADERS = {"PRIVATE-TOKEN": PRIVATE_TOKEN} |
| |
| 14 | |
| |
| 15 | if __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 | """ |
| |
2 | Verify if password policies are enforced in a self-hosted GitLab instance. |
| |
3 | |
| |
4 | Ref: https://docs.gitlab.com/api/settings/ |
| |
5 | """ |
| |
6 |
|
| |
7 | import requests |
| |
8 |
|
| |
9 | BASE_URL = "https://gitlab.com/api/v4" |
| |
10 | PRIVATE_TOKEN = "your_access_token" |
| |
11 | TIMEOUT = 30 |
| |
12 |
|
| |
13 | URL = f"{BASE_URL}/application/settings" |
| |
14 | HEADERS = {"PRIVATE-TOKEN": PRIVATE_TOKEN} |
| |
15 |
|
| |
16 | if __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 | """ |
| |
2 | Track user creation and deletion events in GitLab with timestamps. |
| |
3 | """ |
| |
4 |
|
| |
5 | import requests |
| |
6 |
|
| |
7 | BASE_URL = "https://gitlab.com/api/v4" |
| |
8 | PRIVATE_TOKEN = "your_access_token" |
| |
9 | GROUP_ID = "your_group_id" |
| |
10 | TIMEOUT = 30 |
| |
11 |
|
| |
12 | URL = f"{BASE_URL}/groups/{GROUP_ID}/audit_events" |
| |
13 | HEADERS = {"PRIVATE-TOKEN": PRIVATE_TOKEN} |
| |
14 |
|
| |
15 | if __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 | """ |
| |
2 | Gather all members of specified GitLab groups and projects and their access levels. |
| |
3 | |
| |
4 | Ref: https://docs.gitlab.com/api/members/ |
| |
5 | """ |
| |
6 | |
| |
7 | import requests |
| |
8 | |
| |
9 | BASE_URL = "https://gitlab.com/api/v4" |
| |
10 | PRIVATE_TOKEN = "your_access_token" |
| |
11 | GROUP_IDS = ["group_id_1", "group_id_2"] # Add your group IDs here |
| |
12 | PROJECT_IDS = ["project_id_1", "project_id_2"] # Add your project IDs here |
| |
13 | TIMEOUT = 30 |
| |
14 | |
| |
15 | HEADERS = {"PRIVATE-TOKEN": PRIVATE_TOKEN} |
| |
16 | |
| |
17 | |
| |
18 | def 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 | |
| |
33 | if __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}") |