audit-labs/audit-tools

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

clone: git clone https://gitbay.org/audit-labs/audit-tools.git

v1.0.0: applications/gitlab/audit.py · raw

  1"""
  2GitLab audit CLI.
  3
  4Runs all collectors against a GitLab group and writes a timestamped audit
  5package to an output directory.
  6
  7Usage:
  8    export GITLAB_TOKEN=your_token
  9    export GITLAB_GROUP=your_group_id_or_path
 10
 11    python audit.py
 12    python audit.py --group my-group
 13    python audit.py --group my-group --out ./output
 14    python audit.py --group my-group --url https://gitlab.example.com/api/v4
 15
 16Output:
 17    <out>/gitlab_audit_<group>_<date>/
 18        group_members.csv
 19        projects.csv
 20        project_members.csv
 21        branch_protections.csv
 22        pipelines.csv
 23        approval_rules.csv
 24        audit_events.csv
 25        password_policy.csv
 26        summary.txt
 27"""
 28
 29import argparse
 30import os
 31import sys
 32from datetime import date
 33
 34import config
 35from collectors import (
 36    approvals,
 37    audit_events,
 38    branch_protections,
 39    members,
 40    pipelines,
 41    projects,
 42    settings,
 43)
 44from reporters import csv_reporter
 45
 46
 47def parse_args():
 48    parser = argparse.ArgumentParser(
 49        description="Generate a GitLab audit package for a group."
 50    )
 51    parser.add_argument(
 52        "--group",
 53        help="GitLab group ID or path. Overrides GITLAB_GROUP env var.",
 54    )
 55    parser.add_argument(
 56        "--url",
 57        help="GitLab API base URL. Overrides GITLAB_URL env var. "
 58        "Default: https://gitlab.com/api/v4",
 59    )
 60    parser.add_argument(
 61        "--out",
 62        default="./output",
 63        help="Directory to write the audit package into. Default: ./output",
 64    )
 65    return parser.parse_args()
 66
 67
 68def run():
 69    args = parse_args()
 70    cfg = config.load(group_override=args.group, base_url_override=args.url)
 71    group = cfg["group"]
 72
 73    safe_group = group.replace("/", "-")
 74    output_dir = os.path.join(
 75        args.out, f"gitlab_audit_{safe_group}_{date.today().isoformat()}"
 76    )
 77
 78    print(f"GitLab Audit — {group}")
 79    print(f"Output directory: {output_dir}")
 80    print()
 81
 82    sections = []
 83
 84    def collect(label, fn, filename, *fn_args):
 85        print(f"Collecting: {label}...")
 86        try:
 87            rows = fn(*fn_args)
 88        except Exception as e:
 89            print(f"  Error: {e}", file=sys.stderr)
 90            rows = []
 91        csv_reporter.write(output_dir, filename, rows)
 92        sections.append((label, len(rows)))
 93        return rows
 94
 95    print("Enumerating projects (shared cache)...")
 96    try:
 97        project_cache = projects.fetch_projects(group, cfg)
 98    except Exception as e:
 99        print(f"  Error enumerating projects: {e}", file=sys.stderr)
100        project_cache = []
101
102    collect("Group members", members.group_members, "group_members.csv", group, cfg)
103    collect(
104        "Projects", projects.project_list, "projects.csv", group, cfg, project_cache
105    )
106    collect(
107        "Project members",
108        members.project_members,
109        "project_members.csv",
110        group,
111        cfg,
112        project_cache,
113    )
114    collect(
115        "Branch protections",
116        branch_protections.branch_protections,
117        "branch_protections.csv",
118        group,
119        cfg,
120        project_cache,
121    )
122    collect(
123        "Pipelines", pipelines.pipelines, "pipelines.csv", group, cfg, project_cache
124    )
125    collect(
126        "Approval rules",
127        approvals.approval_rules,
128        "approval_rules.csv",
129        group,
130        cfg,
131        project_cache,
132    )
133    collect("Audit events", audit_events.audit_events, "audit_events.csv", group, cfg)
134    collect(
135        "Password policy",
136        settings.password_policy,
137        "password_policy.csv",
138        group,
139        cfg,
140    )
141
142    print()
143    csv_reporter.write_summary(output_dir, group, sections)
144    print()
145    print("Done.")
146
147
148if __name__ == "__main__":
149    run()