Restrict/audit admin access to a repo

I assume when you mention security team, that security bug reports and fixes should only be visible to this team first, which includes incident response handling, CVE assignment, etc. The team’s responsibility can also include a security release with picking all security patches into a newly tagged release. The delivery team at GitLab documents the process in general/security/mirrors.md · master · GitLab.org / release / GitLab Release Docs · GitLab which could be helpful as an example.

suggests that is not possible (and in my reading Admin mode only requires to elevate privileges, but the malicious case still persists)

Limiting the admin role to the security team, and 1-2 trusted peers, together with temporary admin mode mentioned in In Git Lab, how to limit admin user to access all project? - #3 by wangpeng20150512) could be an option. The restricted group does not have any other owner, maintainer, developer, reporter roles assigned.

A second alternative would be to at least audit when some admin accesses code in the specified repository, so that security can act on the audit.
But I don’t see that as an auditable event

I can see that repository_download_operation and repository_git_operation are documented as audit event types, linked from the audit events available types.

For a quick test, I tried downloading the source code from everyonecancontribute / web / everyonecancontribute.dev · GitLab and inspected Secure > Audit events in the side menu (this group has an Ultimate license, part of the Open Source program).

The Audit Events API endpoint allows for programmatic checks. I have written a short Python script based on the blog post examples in

which retrieves all audit events and dumps them in a readable JSON format. Feel free to reuse, the source code is MIT licensed in get_audit_events.py · main · Developer Evangelism and Technical Marketing at GitLab / use-cases / GitLab API / GitLab API with Python · GitLab

#!/usr/bin/env python

# Description: Fetch audit events (all, group, project)
# Requirements: python-gitlab Python libraries. GitLab API read access, and maintainer access to all configured groups/projects.
# Author: Michael Friedrich <mfriedrich@gitlab.com>
# License: MIT, (c) 2023-present GitLab B.V.

import gitlab
import os
import sys
import json

GITLAB_SERVER = os.environ.get('GL_SERVER', 'https://gitlab.com')
GITLAB_TOKEN = os.environ.get('GL_TOKEN') # token requires maintainer permissions
PROJECT_ID = os.environ.get('GL_PROJECT_ID') #optional
GROUP_ID = os.environ.get('GL_GROUP_ID') #optional

#################
# Main

if __name__ == "__main__":
    if not GITLAB_TOKEN:
        print("🤔 Please set the GL_TOKEN env variable.")
        sys.exit(1)

    gl = gitlab.Gitlab(GITLAB_SERVER, private_token=GITLAB_TOKEN)

    print("# Starting...", end="", flush=True)

    # Collect all projects, or prefer projects from a group id, or a project id
    projects = []

    # Direct project ID
    if PROJECT_ID:
        projects.append(gl.projects.get(PROJECT_ID))

    # Groups and projects inside
    elif GROUP_ID:
        group = gl.groups.get(GROUP_ID)

        for project in group.projects.list(include_subgroups=True, all=True):
            print(".", end="", flush=True) # Get some feedback that it is still looping
            # https://python-gitlab.readthedocs.io/en/stable/gl_objects/groups.html#examples
            manageable_project = gl.projects.get(project.id , lazy=True)
            projects.append(manageable_project)

    # All projects on the instance (may take a while to process)
    else:
        projects = gl.projects.list(get_all=True)

    print(f"\n# Found {len(projects)} projects. Analysing...", end="", flush=True) # new line

    events = {}

    # Loop over projects, fetch audit events 
    for project in projects:
            # https://python-gitlab.readthedocs.io/en/stable/gl_objects/ci_lint.html
            project_obj = gl.projects.get(project.id)
            project_name = project_obj.name
            project_web_url =project_obj.web_url

            for event in project_obj.audit_events.list():
                event_obj = project_obj.audit_events.get(event.id)
                event_entity_type = event_obj.entity_type
                event_entity_id = event_obj.entity_id
                event_details = event_obj.details
                event_created_at = event_obj.created_at

                print(event_entity_id, event_entity_type, event_created_at, end="\n\n", flush=True)
                print(json.dumps(event_details, indent=4), end="", flush=True)

    print("\nDone")

Execute the script:

export GL_TOKEN=xxx
export GL_PROJECT_ID=xxx

python3 get_audit_events.py 

Example in VS Code terminal:

The script also works with GL_GROUP_ID or instance wide (not sure if that is a good idea, untested).

Hope this gets you started. If you need more technical support in your environment, suggest opening a support ticket and link this forum topic in the message.

The third alternative would be to go to the actual logs, to parse access to that repo, but it doesn’t seem right.

If you want to go that route, suggest collecting and ingesting the logs before sending them to Elasticsearch or a different log aggregation system. The production log is available as structured JSON and can be consumed by the Elastic Agent, Splunk, etc. more easily than plaintext logs.

Am I missing some obvious answer?

If the projects should be really closed down with restricted access, a secondary GitLab instance can be an option. This requires more maintenance and setup though.