Skip to main content

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.

alt text

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:

https://neo4j.com/download/

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

openshiftgrapher-0.6.tar.gz (26.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

openshiftgrapher-0.6-py3-none-any.whl (23.2 kB view details)

Uploaded Python 3

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

Hashes for openshiftgrapher-0.6.tar.gz
Algorithm Hash digest
SHA256 7f7f4fdef01fad2981ff1cb2b50dc35481e5e20f7ed2cde577d1d7e7d395b920
MD5 bbe8a5cea25a01dec89dde2cec516557
BLAKE2b-256 9e870bd8dd13c3b5feab335bbac3baf5dc7ab9df7eb26affe40ef068e6ecdb67

See more details on using hashes here.

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

Hashes for openshiftgrapher-0.6-py3-none-any.whl
Algorithm Hash digest
SHA256 3b9895d42f00c2ef94d34ce6c0072ac04f4897666f93bb42506f33b15c2a5e82
MD5 58340a053c1aba8a13f4a3ee51a9161a
BLAKE2b-256 eb42687b7556d8fd14b4c72a954464dc103314bb3a052c2b57ff315cb270d63e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page