Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
wpostdam-anssi committed Nov 4, 2024
1 parent 49e9283 commit 7b38788
Show file tree
Hide file tree
Showing 37 changed files with 4,205 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
HOST_IP=172.18.0.20
NEO4J_PASSWORD=ChangeMe
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__pycache__/
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
![OSAKA](./logo.jpeg)

# OSAKA
Outil de Sécurité des Architectures Kubernetes Avancées
Advanced Kubernetes Architecture Security Tool

This tool allows to perform the reconstruction of complex attack paths by graph generation in a Kubernetes cluster. It uses Neo4j for the
storage of objects and relationships in database as well as neodash for visualization. The language Cypher
as MySQL allows queries to the database to retrieve graphs.
It can be used by security auditors to quickly identify attack paths or security experts to monitor theses paths.

There are four microservices within the tool :
- osaka-database
- osaka-loader
- osaka-visualizer
- osaka-editor

The tool was not designed in a secure development model, it is necessary to deploy it in an isolated and controlled environment. Also, it is recommended to install a HTTPS reverse proxy in front of the application.

## Prerequisites :
- Docker
- Docker Compose

## Installation
Configure the environment variables in the. env file at project root but also at sources/. env :
*HOST_IP* is the host system IP address
*NEO4J_PASSWORD* is the database password

### Build and run
```sh
$ cd osaka/build
$ chmod +x build.sh
$ ./build.sh
```
### To stop the services :
```sh
$ cd osaka
$ docker compose down
```
### To start the services :
```sh
$ cd osaka
$ docker compose up -d
```

## Usage
### Data collection
Run the collect.sh script in the "tools" directory with a kubectl binary and an access to kube-apiserver, then retrieve the "collect-*. zip" and upload the file to the osaka-loader service on port 8080
```sh
$ ./collect.sh
```
### Reading attack paths
The osaka-visualizer service allows to consult the dashboard in read-only mode. An access to the service can be provided to the beneficiary during an audit (::8082)

### Customizing graphs
It is necessary to configure the osaka-editor service with the value of *NEO4J_PASSWORD* located in the .env. This gives write access to the dashboard and allows the modification of cypher queries, etc... (::8081)
9 changes: 9 additions & 0 deletions build/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash

git clone https://github.com/neo4j-labs/neodash.git
sed -i 's\Warning: NeoDash is running with a plaintext password in config.json.\\g' neodash/src/dashboard/Dashboard.tsx

docker rmi pythonloader
docker rmi visualizer
docker compose --env-file ../.env -f ../compose.yml up -d
rm -rf neodash
19 changes: 19 additions & 0 deletions build/loader/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Distroless image

FROM python:3.12-slim AS build-env
RUN groupadd -g 1001 python \
&& useradd --uid 1001 --gid python --shell /bin/bash --create-home python
WORKDIR /loader
ADD . .
RUN pip install --disable-pip-version-check -r requirements.txt
RUN chown -R python:python /loader


FROM gcr.io/distroless/python3-debian12
COPY --from=build-env /loader /loader
COPY --from=build-env /usr/local/lib/ /usr/local/lib/
COPY --from=build-env /usr/local/bin /usr/local/bin
ENV PYTHONPATH=/usr/local/lib/python3.12/site-packages
USER 1001
WORKDIR /loader
ENTRYPOINT ["/usr/local/bin/python3.12","app.py"]
12 changes: 12 additions & 0 deletions build/loader/Dockerfile.bak
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Non distroless image

FROM python:3.12.6
RUN groupadd -g 1001 python \
&& useradd --uid 1001 --gid python --shell /bin/bash --create-home python

WORKDIR /loader
ADD . .
RUN chown -R python:python /loader
USER 1001
RUN pip install --no-cache-dir -r requirements.txt
CMD ["python", "app.py"]
77 changes: 77 additions & 0 deletions compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
services:
volume-perm-job:
image: alpine
user: "root"
group_add:
- 1001
volumes:
- /opt/db:/opt/db
command: chown -R 1001:1001 /opt/db
networks:
net:
database:
image: neo4j:5.24.2
ports:
- 7474:7474
- 7687:7687
- 7473:7473
volumes:
- "/opt/db:/data"
user: 1001:1001
environment:
- NEO4J_AUTH=neo4j/${NEO4J_PASSWORD}
networks:
net:
ipv4_address: 172.18.0.20
restart: always
editor:
image: neo4jlabs/neodash:2.4.8
ports:
- 8081:5005
networks:
net:
ipv4_address: 172.18.0.21
restart: always
loader:
image: pythonloader
build:
context: sources
dockerfile: ../build/loader/Dockerfile
ports:
- 8080:5000
networks:
net:
ipv4_address: 172.18.0.22
restart: always
visualizer:
image: visualizer
build:
context: build/neodash
dockerfile: Dockerfile
ports:
- 8082:5005
environment:
- standalone=true
- standaloneProtocol=neo4j
- standaloneHost=${HOST_IP}
- standalonePort=7687
- standaloneDatabase=neo4j
- standaloneUsername=neo4j
- standalonePassword=${NEO4J_PASSWORD}
- standaloneDashboardName=Dashboard
- standaloneDashboardDatabase=neo4j
networks:
net:
ipv4_address: 172.18.0.23
restart: always

volumes:
db:
driver: local

networks:
net:
driver: bridge
ipam:
config:
- subnet: 172.18.0.0/24
Binary file added logo.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions sources/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
NEO4J_URI=neo4j://172.18.0.20:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=ChangeMe
NEO4J_VERSION=4
NEO4J_DATABASE=neo4j
50 changes: 50 additions & 0 deletions sources/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import os
from main import main
from flask_assets import Environment, Bundle
from flask import Flask, flash, request, redirect, render_template
from werkzeug.utils import secure_filename
from waitress import serve


pwd = os.getcwd()
path = pwd + "/main.py"
UPLOAD_FOLDER = pwd + "/uploads"
ALLOWED_EXTENSIONS = {'zip'}

app = Flask(__name__)
app.secret_key = "super secret key"
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
env = Environment(app)
js = Bundle('js/clarity-icons.min.js', 'js/clarity-icons-api.js',
'js/clarity-icons-element.js', 'js/custom-elements.min.js')
env.register('js_all', js)
css = Bundle('css/clarity-ui.min.css', 'css/clarity-icons.min.css')
env.register('css_all', css)


def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route('/', methods=['GET', 'POST'])
def upload_file():
error = None
if request.method == 'POST':
if 'file' not in request.files:
flash('No file part')
return redirect(request.url)
file = request.files['file']
if file.filename == '':
error = "No selected file"
return render_template('index.html', error=error)
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return render_template('index.html', name=filename), main(filename)
return render_template('index.html', error=error)


if __name__ == '__main__':
#app.run(host="0.0.0.0", port=5000, debug=True)
serve(app, host="0.0.0.0", port=5000)
21 changes: 21 additions & 0 deletions sources/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from neo4j import GraphDatabase, basic_auth
from src.common import utils
from src.Loader import loader, dashboard
from src.Database import init, relationships
import settings
import os


def main(filename):
pwd = os.getcwd()
path = pwd + "/uploads/" + filename.split(".")[0]

driver = GraphDatabase.driver(settings.url, auth=basic_auth(settings.username, settings.password))
driver.verify_connectivity()

init.setup(driver)
utils.extract_zipfile(pwd, filename)
loader.data(path, driver)
relationships.Add(driver)
dashboard.push(driver)

14 changes: 14 additions & 0 deletions sources/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
blinker==1.8.2
click==8.1.7
Flask==3.0.3
Flask-Assets==2.1.0
itsdangerous==2.2.0
Jinja2==3.1.4
MarkupSafe==2.1.5
neo4j==5.24.0
python-dotenv==1.0.1
pytz==2024.2
PyYAML==6.0.2
waitress==3.0.0
webassets==2.0
Werkzeug==3.0.4
10 changes: 10 additions & 0 deletions sources/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import os
from dotenv import load_dotenv

load_dotenv()

url = os.getenv("NEO4J_URI")
username = os.getenv("NEO4J_USER")
password = os.getenv("NEO4J_PASSWORD")
neo4j_version = os.getenv("NEO4J_VERSION")
neo4j_database = os.getenv("NEO4J_DATABASE")
Empty file.
10 changes: 10 additions & 0 deletions sources/src/Database/init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
def setup(driver):
query = """
MATCH (n) DETACH DELETE n
"""
driver.execute_query(query)

query = """
CREATE (c:ClusterAdmins { Name : "CLUSTER ADMINS" })
"""
driver.execute_query(query)
Loading

0 comments on commit 7b38788

Please sign in to comment.