Create relational databases, in neo4j, of an OpenShift cluster.
Project description
OpenShift Enumeration
OpenShiftGrapher is used to enumerate OpenShift clusters.
OpenShiftGrapher
What it is
The script is mean to create relational databases, in neo4j, of an OpenShift cluster.
It extracts objects and relationships for information like projects, service accounts, SecurityContextConstraints and others.
The neo4j query system can then be used to spot inconsistency in the database that could lead to vulnerabilities.
Installation
Option 1 — Install from PyPI
Using pip:
pip install OpenShiftGrapher
Using Astral's uv:
uv venv .venv
source .venv/bin/activate
uv pip install OpenShiftGrapher
Option 2 — Install directly from GitHub
To install from the GitHub repository:
uv venv .venv
source .venv/bin/activate
uv pip install git+https://github.com/AmadeusITGroup/OpenShiftGrapher.git@main
Requirement
The script needs to communicate with the neo4j database, and the OpenShift cluster in python.
To install the neo4j database we recommend to install neo4j desktop, which contain the database and bloom for visualisation:
Usage
Then script can be launched with the following command:
OpenShiftGrapher -h
usage: OpenShiftGrapher [-h] [-r] -a APIURL -t TOKEN [-c COLLECTOR [COLLECTOR ...]] [-u USERNEO4J] [-p PASSWORDNEO4J] [-x PROXYURL] [-d DATABASENAME]
Exemple:
OpenShiftGrapher -a "https://api.cluster.net:6443" -t "eyJhbGciOi..."
OpenShiftGrapher -a "https://api.cluster.net:6443" -t $(cat token.txt) -c all -d customDB -u neo4j -p rootroot -r
OpenShiftGrapher -a "https://api.cluster.net:6443" -t $(cat token.txt) -c SecurityContextConstraints role route
options:
-h, --help show this help message and exit
-r, --resetDB reset the neo4j db.
-a APIURL, --apiUrl APIURL
api url.
-t TOKEN, --token TOKEN
service account token.
-c COLLECTOR [COLLECTOR ...], --collector COLLECTOR [COLLECTOR ...]
list of collectors. Possible values: all, project, SecurityContextConstraints, sa, role, clusterrole, rolebinding, clusterrolebinding, route, pod
-u USERNEO4J, --userNeo4j USERNEO4J
neo4j database user.
-p PASSWORDNEO4J, --passwordNeo4j PASSWORDNEO4J
neo4j database password.
-x PROXYURL, --proxyUrl PROXYURL
proxy url.
-d DATABASENAME, --databaseName DATABASENAME
Database Name.
OpenShiftGrapher -a "https://api.cluster.net:6443" -t $(cat quota.token) -c all
Exemples of Queries
MATCH (n:AbsentServiceAccount {name:"servicenow-sa"}) RETURN n LIMIT 25
MATCH p=(n1:Project) WHERE NOT (n1.name =~ ('openshift.*') OR n1.name =~ ('test')) RETURN p LIMIT 25
MATCH p=(n:AbsentServiceAccount {name:"servicenow-sa"})-[]->()-[r:`HAS CLUSTERROLE`]->() RETURN p LIMIT 25
MATCH p=(n1:AbsentProject)-[r1:`CONTAIN SA`]->(n2:AbsentServiceAccount)-[]->()-[r2:`HAS CLUSTERROLE`]->() RETURN p LIMIT 25
MATCH p=(n1:AbsentProject)-[r1:`CONTAIN SA`]->(n2:AbsentServiceAccount)-[]->()-[]->()-[r2:`get`]->(n4:Resource) WHERE (n4.name =~ ('secrets')) RETURN p LIMIT 25
MATCH p=(n1:Project)-[r1:`CONTAIN SA`]->(n2:ServiceAccount)-[]->()-[]->()-[r2:`get`]->(n4:Resource) WHERE (n4.name =~ ('secrets')) RETURN p LIMIT 25
MATCH p=(n1:AbsentProject)-[r1:`CONTAIN SA`]->(n2:AbsentServiceAccount)-[]->()-[r2:`CAN USE SecurityContextConstraints`]->() RETURN p LIMIT 25
MATCH p=(n2:AbsentServiceAccount)-[]->()-[r2:`CAN USE SecurityContextConstraints`]->() RETURN p LIMIT 25
MATCH p=()-[r2:`HAS CLUSTERROLE`]->()-[r1:`create`]->() RETURN p LIMIT 25
MATCH p=(n1:Role)-[r1:`create`]->() RETURN p LIMIT 25
MATCH p=(n2:ServiceAccount)-[]->()-[]->(n1:Role)-[]->() RETURN p LIMIT 100
MATCH p=(n2:AbsentServiceAccount)-[]->()-[]->(n1:Role)-[r1:`create`]->() RETURN p LIMIT 100
MATCH p=(n1)-[r2:`CAN USE SecurityContextConstraints`]->(n2) WHERE NOT (n2.name =~ ('acs-splunk')) RETURN p LIMIT 25
MATCH p=(n1)-[r2:`CAN USE SecurityContextConstraints`]->(n2) WHERE NOT (n2.name =~ ('acs-splunk.*')) RETURN p LIMIT 25
MATCH p=(n4:Resource) WHERE (n4.name =~ ('secrets')) RETURN p LIMIT 25
MATCH p=(n1:Project)-[]->(n2:ServiceAccount)-[]->()-[]->(n3:Role)-[r1:`*`]->(n4:Resource) WHERE NOT (n1.name =~ ('openshift.*') OR n1.name =~ ('test')) RETURN p LIMIT 25
MATCH p=(n4:Resource) WHERE (n4.name =~ ('.*bypass.*')) RETURN p LIMIT 25
MATCH p=(n1:Project)-[]->(n2:ServiceAccount)-[]->()-[]->(n3:Role)-[]->(n4:Resource) WHERE NOT (n1.name =~ ('openshift.*')) RETURN p LIMIT 1000
MATCH p=(n1:Project)-[]->(n2:ServiceAccount)-[]->()-[]->(n3:Role)-[]->(n4:Resource) RETURN p LIMIT 1000
MATCH p=(n1:Project)-[r1:`CONTAIN SA`]->(n2:ServiceAccount)-[]->()-[]->()-[r2:`get`]->(n4:Resource) WHERE (n4.name =~ ('secrets')) AND NOT (n1.name =~ ('openshift.*')) RETURN p LIMIT 200
MATCH p=(n1:Project)-[r1:`CONTAIN SA`]->(n2:ServiceAccount)-[]->()-[]->()-[r2:`create`]->(n4:Resource) WHERE (n4.name =~ ('namespaces')) AND NOT (n1.name =~ ('openshift.*')) RETURN p LIMIT 200
SA not in openshift* project that can use SecurityContextConstraints
MATCH p=(n1:Project)-[]->(n2:ServiceAccount)-[]->()-[]->()-[r1:`CAN USE SecurityContextConstraints`]->() WHERE NOT (n1.name =~ ('openshift.*')) RETURN p LIMIT 100
SA not in openshift* project that has cluster role that can read secrets
MATCH p=(n1:Project)-[]->(n2:ServiceAccount)-[]->()-[r1:`HAS CLUSTERROLE`]->()-[]->(n4:Resource) RETURN p LIMIT 25
MATCH p=(n1:Project)-[]->(n2:ServiceAccount)-[]->()-[r2:`HAS CLUSTERROLE`]->()-[r3:`get`]->(n4:Resource) WHERE (n4.name =~ ('secrets')) AND NOT (n1.name =~ ('openshift.*')) RETURN p LIMIT 200
Potential vulnerability
It happens that cluster is deployed with preconfigured template automatically setting Roles, RoleBindings and even SecurityContextConstraints to service account that is not yet created. This can lead to privilege escalation in the case where you can create them. In this case, you would be able to get the token of the SA newly created and the role or SecurityContextConstraints associated. Same case happens when the missing SA is part of a missing project, in this case if you can create the project and then the SA you get the Roles and SecurityContextConstraints associated.
Absent SA that can use SecurityContextConstraints
SecurityContextConstraints can be given by role binding or directly:
MATCH p=(n1:AbsentProject)-[]->(n2:AbsentServiceAccount)-[]->()-[]->()-[r1:`CAN USE SecurityContextConstraints`]->() RETURN p LIMIT 100
MATCH p=(n1:AbsentProject)-[]->(n2:AbsentServiceAccount)-[r1:`CAN USE SecurityContextConstraints`]->() RETURN p LIMIT 100
Absent SA that has cluster role
MATCH p=(n1:AbsentProject)-[]->(n2:AbsentServiceAccount)-[]->()-[r1:`HAS CLUSTERROLE`]->() RETURN p LIMIT 100
MATCH p=(n1:AbsentProject)-[]->(n2:AbsentServiceAccount)-[]->()-[r2:`HAS CLUSTERROLE`]->()-[r3:`get`]->(n4:Resource) WHERE (n4.name =~ ('secrets')) AND NOT (n1.name =~ ('openshift.*')) RETURN p LIMIT 200
Absent SA that has role binding in another namespace than their own
MATCH p=(n1:AbsentProject)-[]->(n2:AbsentServiceAccount)-[r1:`HAS ROLEBINDING`]->(n3:RoleBinding)-[]->() WHERE NOT (n1.name =~ n3.namespace) RETURN p LIMIT 100
Absent SA that can bypass kyverno
MATCH p=(n1)-[]->(n2)-[r1:`CAN BYPASS KYVERNO`]->() RETURN p LIMIT 100
MATCH p=(n1:AbsentProject)-[]->(n2:AbsentServiceAccount)-[r1:`CAN BYPASS KYVERNO`]->() RETURN p LIMIT 100
Gatekeeper whitelist
MATCH p=(n1:GatekeeperWhitelist) RETURN p LIMIT 100
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file openshiftgrapher-0.6.tar.gz.
File metadata
- Download URL: openshiftgrapher-0.6.tar.gz
- Upload date:
- Size: 26.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7f7f4fdef01fad2981ff1cb2b50dc35481e5e20f7ed2cde577d1d7e7d395b920
|
|
| MD5 |
bbe8a5cea25a01dec89dde2cec516557
|
|
| BLAKE2b-256 |
9e870bd8dd13c3b5feab335bbac3baf5dc7ab9df7eb26affe40ef068e6ecdb67
|
File details
Details for the file openshiftgrapher-0.6-py3-none-any.whl.
File metadata
- Download URL: openshiftgrapher-0.6-py3-none-any.whl
- Upload date:
- Size: 23.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3b9895d42f00c2ef94d34ce6c0072ac04f4897666f93bb42506f33b15c2a5e82
|
|
| MD5 |
58340a053c1aba8a13f4a3ee51a9161a
|
|
| BLAKE2b-256 |
eb42687b7556d8fd14b4c72a954464dc103314bb3a052c2b57ff315cb270d63e
|