{"id":19825811,"url":"https://github.com/maxdcb/openshiftgrapher","last_synced_at":"2025-05-01T14:30:41.355Z","repository":{"id":229863753,"uuid":"773251534","full_name":"maxDcb/OpenShiftGrapher","owner":"maxDcb","description":"OpenShift Pentesting Tool for enumerating and graphing clusters in Neo4j","archived":true,"fork":false,"pushed_at":"2024-11-12T13:59:38.000Z","size":173,"stargazers_count":6,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-06T10:41:04.632Z","etag":null,"topics":["cybersecurity","openshift","pentest"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/maxDcb.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-17T06:30:26.000Z","updated_at":"2024-11-18T08:02:17.000Z","dependencies_parsed_at":"2024-03-26T17:31:42.233Z","dependency_job_id":"d7d32e95-8831-43bd-9f6e-2938107226ee","html_url":"https://github.com/maxDcb/OpenShiftGrapher","commit_stats":null,"previous_names":["maxdcb/openshiftgrapher"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxDcb%2FOpenShiftGrapher","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxDcb%2FOpenShiftGrapher/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxDcb%2FOpenShiftGrapher/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxDcb%2FOpenShiftGrapher/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maxDcb","download_url":"https://codeload.github.com/maxDcb/OpenShiftGrapher/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251889941,"owners_count":21660416,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cybersecurity","openshift","pentest"],"created_at":"2024-11-12T11:08:47.227Z","updated_at":"2025-05-01T14:30:41.348Z","avatar_url":"https://github.com/maxDcb.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# OpenShiftGrapher New Home\n\nThis repo is not active anymore. Here the is the new home of the actively maintained project [OpenShiftGrapher](https://github.com/AmadeusITGroup/OpenShiftGrapher).\n\n# OpenShift Enumeration\n\nThis repository contains 2 scripts, EnumAbsentObject.py is used to detect absent service accounts that could represent a vulnerability and OpenShiftGrapher.py that is used to enumerate more largely the cluster.  \n\n## OpenShiftGrapher\n\n### What it is\n\nThe script is mean to create relational databases, in neo4j, of an OpenShift cluster.  \nIt extracts objects as and relationships for common information like projects, service accounts, scc and others.  \nThe query system can then be used to spot inconsistency in the database, that could lead to vulnerabilities.\n\n![alt text](https://github.com/maxDcb/OpenShiftGrapher/blob/master/media/general.png?raw=true)\n\n### Installation\n\nThe script needs to communicate with the neo4j database, and the OpenShift cluster in python:  \n\n```\npip install py2neo  \npip install openshift  \n```\n\nTo install the neo4j database we recommend to install neo4j desktop, which contain the database and bloom for visualisation:  \n\nhttps://neo4j.com/download/  \n\n### Setup\n\nThen script can be launched with the following command:  \n\n```bash\npython3 OpenShiftGrapher.py -h\nusage: OpenShiftGrapher.py [-h] [-r] -a APIURL -t TOKEN [-c COLLECTOR [COLLECTOR ...]] [-u USERNEO4J] [-p PASSWORDNEO4J]\n\nExemple:\n    python3 GenClusterGraph.py -a \"https://api.cluster.net:6443\" -t \"eyJhbGciOi...\"\n    python3 GenClusterGraph.py -a \"https://api.cluster.net:6443\" -t $(cat token.txt)\n    python3 GenClusterGraph.py -a \"https://api.cluster.net:6443\" -t $(cat token.txt) -c scc role route\n\noptions:\n  -h, --help            show this help message and exit\n  -r, --resetDB         reset the neo4j db.\n  -a APIURL, --apiUrl APIURL\n                        api url.\n  -t TOKEN, --token TOKEN\n                        service account token.\n  -c COLLECTOR [COLLECTOR ...], --collector COLLECTOR [COLLECTOR ...]\n                        list of collectors. Possible values: all, project, scc, sa, role, clusterrole, route, pod \n  -u USERNEO4J, --userNeo4j USERNEO4J\n                        neo4j database user.\n  -p PASSWORDNEO4J, --passwordNeo4j PASSWORDNEO4J\n                        neo4j database password.\n```\n\n```bash\npython3 OpenShiftGrapher.py -a \"https://api.cluster.net:6443\" -t $(cat quota.token) -c all\n```\n\n### Exemples of Queries\n\nThose queries are not up to date with the last version of the tool\n\n```\nMATCH (n:AbsentServiceAccount {name:\"servicenow-sa\"}) RETURN n LIMIT 25  \n\nMATCH p=(n1:Project) WHERE NOT (n1.name =~ ('openshift.*') OR n1.name =~ ('test'))  RETURN p LIMIT 25  \n\nMATCH p=(n:AbsentServiceAccount {name:\"servicenow-sa\"})-[]-\u003e()-[r:`HAS CLUSTERROLE`]-\u003e() RETURN p LIMIT 25  \n\nMATCH p=(n1:AbsentProject)-[r1:`CONTAIN SA`]-\u003e(n2:AbsentServiceAccount)-[]-\u003e()-[r2:`HAS CLUSTERROLE`]-\u003e() RETURN p LIMIT 25  \n\nMATCH p=(n1:AbsentProject)-[r1:`CONTAIN SA`]-\u003e(n2:AbsentServiceAccount)-[]-\u003e()-[]-\u003e()-[r2:`get`]-\u003e(n4:Resource) WHERE (n4.name =~ ('secrets')) RETURN p LIMIT 25  \n\nMATCH p=(n1:Project)-[r1:`CONTAIN SA`]-\u003e(n2:ServiceAccount)-[]-\u003e()-[]-\u003e()-[r2:`get`]-\u003e(n4:Resource) WHERE (n4.name =~ ('secrets')) RETURN p LIMIT 25  \n\nMATCH p=(n1:AbsentProject)-[r1:`CONTAIN SA`]-\u003e(n2:AbsentServiceAccount)-[]-\u003e()-[r2:`CAN USE SCC`]-\u003e() RETURN p LIMIT 25  \n\nMATCH p=(n2:AbsentServiceAccount)-[]-\u003e()-[r2:`CAN USE SCC`]-\u003e() RETURN p LIMIT 25  \n\nMATCH p=()-[r2:`HAS CLUSTERROLE`]-\u003e()-[r1:`create`]-\u003e() RETURN p LIMIT 25  \n\nMATCH p=(n1:Role)-[r1:`create`]-\u003e() RETURN p LIMIT 25  \n\nMATCH p=(n2:ServiceAccount)-[]-\u003e()-[]-\u003e(n1:Role)-[]-\u003e() RETURN p LIMIT 100  \n\nMATCH p=(n2:AbsentServiceAccount)-[]-\u003e()-[]-\u003e(n1:Role)-[r1:`create`]-\u003e() RETURN p LIMIT 100  \n\nMATCH p=(n1)-[r2:`CAN USE SCC`]-\u003e(n2) WHERE NOT (n2.name =~ ('acs-splunk'))  RETURN p LIMIT 25  \n\nMATCH p=(n1)-[r2:`CAN USE SCC`]-\u003e(n2) WHERE NOT (n2.name =~ ('acs-splunk.*'))  RETURN p LIMIT 25  \n\nMATCH p=(n4:Resource) WHERE (n4.name =~ ('secrets')) RETURN p LIMIT 25  \n\nMATCH p=(n1:Project)-[]-\u003e(n2:ServiceAccount)-[]-\u003e()-[]-\u003e(n3:Role)-[r1:`*`]-\u003e(n4:Resource) WHERE NOT (n1.name =~ ('openshift.*') OR n1.name =~ ('test'))  RETURN p LIMIT 25  \n\nMATCH p=(n4:Resource) WHERE (n4.name =~ ('.*bypass.*')) RETURN p LIMIT 25  \n\nMATCH p=(n1:Project)-[]-\u003e(n2:ServiceAccount)-[]-\u003e()-[]-\u003e(n3:Role)-[]-\u003e(n4:Resource) WHERE NOT (n1.name =~ ('openshift.*'))  RETURN p LIMIT 1000\n\nMATCH p=(n1:Project)-[]-\u003e(n2:ServiceAccount)-[]-\u003e()-[]-\u003e(n3:Role)-[]-\u003e(n4:Resource) RETURN p LIMIT 1000\n\nMATCH p=(n1:Project)-[r1:`CONTAIN SA`]-\u003e(n2:ServiceAccount)-[]-\u003e()-[]-\u003e()-[r2:`get`]-\u003e(n4:Resource) WHERE (n4.name =~ ('secrets')) AND NOT (n1.name =~ ('openshift.*')) RETURN p LIMIT 200  \n\nMATCH p=(n1:Project)-[r1:`CONTAIN SA`]-\u003e(n2:ServiceAccount)-[]-\u003e()-[]-\u003e()-[r2:`create`]-\u003e(n4:Resource) WHERE (n4.name =~ ('namespaces')) AND NOT (n1.name =~ ('openshift.*')) RETURN p LIMIT 200  \n\n```\n\n### SA not in openshift* project that can use SCC\n\n```\nMATCH p=(n1:Project)-[]-\u003e(n2:ServiceAccount)-[]-\u003e()-[]-\u003e()-[r1:`CAN USE SCC`]-\u003e() WHERE NOT (n1.name =~ ('openshift.*'))  RETURN p LIMIT 100\n```\n\n### SA not in openshift* project that has cluster role that can read secrets\n\n```\nMATCH p=(n1:Project)-[]-\u003e(n2:ServiceAccount)-[]-\u003e()-[r1:`HAS CLUSTERROLE`]-\u003e()-[]-\u003e(n4:Resource) RETURN p LIMIT 25  \nMATCH p=(n1:Project)-[]-\u003e(n2:ServiceAccount)-[]-\u003e()-[r2:`HAS CLUSTERROLE`]-\u003e()-[r3:`get`]-\u003e(n4:Resource) WHERE (n4.name =~ ('secrets')) AND NOT (n1.name =~ ('openshift.*')) RETURN p LIMIT 200  \n```\n\n## Potential vulnerability\n\nIt happens that cluster is deployed with preconfigured template automatically setting Roles, RoleBindings and even SCC 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 SCC 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 SCC associated.\n\n### Absent SA that can use SCC\n\n```\nMATCH p=(n1:AbsentProject)-[]-\u003e(n2:AbsentServiceAccount)-[]-\u003e()-[]-\u003e()-[r1:`CAN USE SCC`]-\u003e() WHERE NOT (n1.name =~ ('openshift.*'))  RETURN p LIMIT 100 \n```\n\n### Absent SA that has cluster role\n\n```\nMATCH p=(n1:AbsentProject)-[]-\u003e(n2:AbsentServiceAccount)-[]-\u003e()-[r1:`HAS CLUSTERROLE`]-\u003e() RETURN p LIMIT 25  \n```\n\n## EnumAbsentObject\n\nFor EnumAbsentObject.py their is no need to install the neo4j database and it can be used with the following dependency:  \n```\npip install openshift  \n```\n\n```bash\npython3 EnumAbsentObject.py -h\nusage: EnumAbsentObject.py [-h] -a APIURL -t TOKEN\n\nExemple:\n    python3 AbsentEnum.py -a \"https://api.cluster.net:6443\" -t \"eyJhbGciOi...\"\n    python3 AbsentEnum.py -a \"https://api.cluster.net:6443\" -t $(cat token.txt)\n\noptions:\n  -h, --help            show this help message and exit\n  -a APIURL, --apiUrl APIURL\n                        api url.\n  -t TOKEN, --token TOKEN\n                        service account token.\n```\n\n```bash\npython3 EnumAbsentObject.py -a \"https://api.cluster.net:6443\" -t $(cat quota.token)\n```\n\nOutput are the following:  \n\n```\n[o] serviceAccount related to ClusterRole: uniping-operator, don't exist: uniping:uniping-operator     \t\t\t-\u003e the uniping-operator SA is missing \n[+] serviceAccount and project related to ClusterRole: uniping-operator, don't exist: uniping:uniping-operator \t-\u003e the uniping-operator SA and the uniping project are missing \n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxdcb%2Fopenshiftgrapher","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaxdcb%2Fopenshiftgrapher","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxdcb%2Fopenshiftgrapher/lists"}