GitOops is a tool to help attackers and defenders identify lateral movement and privilege escalation paths in GitHub organizations by abusing CI/CD pipelines and GitHub access controls.
It works by mapping relationships between a GitHub organization and its CI/CD jobs and environment variables. It’ll use any Bolt-compatible graph database as backend, so you can query your attack paths with openCypher:
MATCH p=(:User{login:”alice”})-[..5]->(v:EnvironmentVariable) WHERE v.name =~ “.SECRET.*”
RETURN p
Docs
In addition to mapping relationships between your users, teams and repositories, GitOops maps relationships between those and environment variables in your CI/CD systems.
The following CI/CD systems are currently supported:
- GitHub Actions
- CircleCI
On top of this, GitOops ingests CI/CD configuration files from repositories for other popular CI/CD systems, enabling less refined queries on those.
Finally, GitOops will also map webhooks and status checks from commits to a repository’s pull requests and default branch. These allow you to find integrations that are typically configured server-side (e.g. AWS CodeBuild).
Install, Build & Run
You can build GitOops yourself or use our binaries.
You may also consider using it as a package if you want to run some custom ingestion.
Download the latest release for your OS from the releases page or:
$ export OS=linux # or macos/windows
$ curl -Lso gitoops “https://github.com/ovotech/gitoops/releases/latest/download/gitoops-$OS”
Build
$ go version
go version go1.16.6 linux/amd64
$ git clone git@github.com:ovotech/gitoops.git
$ cd gitoops
$ make
$ ./gitoops
Usage: ./gitoops [SUBCOMMAND] [OPTIONS]…
Available subcommands:
circleci
enrich
github
CLI
You will need a Bolt-compatible database. We provide a docker-compose
file for Neo4j.
IMPORTANT: This sets up an unauthenticated Neo4j instance listening on localhost. You’re recommended to set a password.
$ docker-compose -f docker-compose.yml up -d
Ingest GitHub data
GitOops uses a Personal Access Token (PAT) to ingest GitHub data. You will need the read:org
and repo
(Full control of private repositories
) scopes.
To get full coverage you should use an organization owner PAT. You can use an organization member PAT but you will get only partial coverage.
$ gitoops github \
-debug \
-organization fakenews \
-neo4j-password $NEO4J_PASSWORD \
-neo4j-uri=”neo4j://localhost:7687″ \
-token $GITHUB_TOKEN \
-ingestor default \
-ingestor secrets \
-session helloworld
Most parameters should be self-explanatory.
Please check gitoops github -h
for more information on the -ingestor
.
The session
is just a unique identifier for this run of the ingestor. You can use this to remove old nodes and relationships that are no longer relevant (by removing any nodes and relationships that don’t have the latest session identifier from your database).
If you are targeting a large GitHub organization, you may encounter rate limits. If this happens you can use the -ingestor
flags to limit the information you are ingesting at a time.
The following ingestors need to run first and in this particular order:
- Organizations
- Teams
- Users
- Repos
Order doesn’t matter for other ingestors.
If you are targetting a self-hosted GitHub Enterprise Server, you will want to set the -github-rest-url
and -github-graphql-url
parameters. These default to the GitHub cloud URLs.
Unfortunately, the documented CircleCI REST API doesn’t give everything we want. Luckily there’s a “hidden” GraphQL API we can access with a cookie. With your browser, navigate to the CircleCI web UI and fetch your ring-session
cookie. You should be able to find this in a request to the graphql-unstable
endpoint when loading some pages.
$ export CIRCLECI_COOKIE=RING_SESSION_COOKIE_VALUE
$ gitoops circleci \
-debug \
-organization fakenews \
-neo4j-password $NEO4J_PASSWORD \
-neo4j-uri=”neo4j://localhost:7687″ \
-cookie=$CIRCLECI_COOKIE \
-session helloworld
Data enrichment
We do some very crude “enriching” of data. After you’ve ingested GitHub proceed to:
$ gitoops enrich \
-debug \
-organization fakenews \
-session helloworld \
-neo4j-password $NEO4J_PASSWORD \
-neo4j-uri=”neo4j://localhost:7687″
Schema
This file is generated by ./scripts/generate_schema_doc.py
Branch Protection Rule
Key | Type |
---|---|
id | STRING |
pattern | STRING |
requiresReviews | BOOLEAN |
session | STRING |
Outbound | Inbound |
---|---|
HAS_BRANCH_PROTECTION_RULE |
Key | Type |
---|---|
allMembers | BOOLEAN |
id | STRING |
name | STRING |
session | STRING |
Outbound | Inbound |
---|---|
EXPOSES_ENVIRONMENT_VARIABLE | HAS_ACCESS_TO_CIRCLECI_CONTEXT |
Key | Type |
---|---|
id | STRING |
repository | STRING |
session | STRING |
Outbound | Inbound |
---|---|
EXPOSES_ENVIRONMENT_VARIABLE | HAS_CI |
Key | Type |
---|---|
customBranchPolicy | BOOLEAN |
id | STRING |
name | STRING |
protectedBranches | BOOLEAN |
session | STRING |
url | STRING |
Outbound | Inbound |
---|---|
EXPOSES_ENVIRONMENT_VARIABLE | HAS_ENVIRONMENT |
Key | Type |
---|---|
id | STRING |
session | STRING |
truncatedValue | STRING |
variable | STRING |
Outbound | Inbound |
---|---|
EXPOSES_ENVIRONMENT_VARIABLE |
Key | Type |
---|---|
id | STRING |
path | STRING |
session | STRING |
text | STRING |
Outbound | Inbound |
---|---|
HAS_CI_CONFIGURATION_FILE |
Key | Type |
---|---|
id | STRING |
login | STRING |
session | STRING |
url | STRING |
Outbound | Inbound |
---|---|
EXPOSES_ENVIRONMENT_VARIABLE | IS_MEMBER_OF |
OWNED_BY |
Key | Type |
---|---|
databaseId | INTEGER |
id | STRING |
isArchived | BOOLEAN |
isPrivate | BOOLEAN |
name | STRING |
session | STRING |
url | STRING |
Outbound | Inbound |
---|---|
EXPOSES_ENVIRONMENT_VARIABLE | HAS_PERMISSION_ON |
HAS_WEBHOOK | |
HAS_STATUS_CHECK | |
OWNED_BY | |
HAS_BRANCH_PROTECTION_RULE | |
HAS_CI | |
HAS_CI_CONFIGURATION_FILE | |
HAS_ENVIRONMENT |
Key | Type |
---|---|
context | STRING |
host | STRING |
id | STRING |
session | STRING |
Outbound | Inbound |
---|---|
HAS_STATUS_CHECK |
Key | Type |
---|---|
id | STRING |
name | STRING |
session | STRING |
slug | STRING |
url | STRING |
Outbound | Inbound |
---|---|
HAS_PERMISSION_ON | IS_MEMBER_OF |
HAS_ACCESS_TO_CIRCLECI_CONTEXT |
Key | Type |
---|---|
id | STRING |
login | STRING |
session | STRING |
url | STRING |
Outbound | Inbound |
---|---|
HAS_PERMISSION_ON | |
IS_MEMBER_OF | |
HAS_ACCESS_TO_CIRCLECI_CONTEXT |
Key | Type |
---|---|
events | LIST OF STRING |
host | STRING |
id | STRING |
name | STRING |
session | STRING |
target | STRING |
url | STRING |
Outbound | Inbound |
---|---|
HAS_WEBHOOK |