From 8ff4aaebbefe6b8bcaaa0412c3428237328d10cf Mon Sep 17 00:00:00 2001 From: Evan Reichard Date: Wed, 17 Mar 2021 22:19:03 -0400 Subject: [PATCH] Database Models & Connector --- README.md | 35 ++++++++++++++++++++++- setup.py | 3 +- src/overseer/database.py | 54 +++++++++++++++++++++++++++++++++++ src/overseer/models.py | 37 ++++++++++++++++++++++++ src/overseer/scanner.py | 0 src/overseer_client/README.md | 2 ++ 6 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 src/overseer/models.py create mode 100644 src/overseer/scanner.py diff --git a/README.md b/README.md index cf88877..e2c59be 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,42 @@ # Overseer + ## Description Overseer is a port scanning web interface. All current and historical results are stored in a database. -## Building +## Developing +### Server +Ideally from a virtualenv: +``` +python setup.py develop +``` + +### Client +At this point, you have to build the client to appropriately populate the static resources for +the Flask server to serve up. + +## Building +Overseer consists of a server and client component. + +### Server: +Ideally from a virtualenv: +``` +python setup.py install +``` + +### Client: +You must run these commands from the `./src/overseer_client/` directory: +``` +yarn build +``` ## Running +Ideally from a virtual env: +``` +overseer run +``` + +# Notes +In a production environment, it would be ideal to setup something like nginx to properly forward +the `/api/*` routes to the Flask server, and all other endpoints to the static client resources. diff --git a/setup.py b/setup.py index b2f04be..1944ee8 100644 --- a/setup.py +++ b/setup.py @@ -16,6 +16,7 @@ setup( }, install_requires = [ "Flask", - "flask_socketio" + "flask_socketio", + "sqlalchemy", ], ) diff --git a/src/overseer/database.py b/src/overseer/database.py index e69de29..500d0ef 100644 --- a/src/overseer/database.py +++ b/src/overseer/database.py @@ -0,0 +1,54 @@ +import models +from datetime import datetime +from os import path +from sqlalchemy import create_engine, or_, insert, Table, Column, Integer, String, ForeignKey, DateTime +from sqlalchemy.orm import declarative_base, Session + +class DatabaseConnector: + def __init__(self, data_path): + self.engine = create_engine("sqlite+pysqlite:///%s" % path.join(data_path, "overseer.sqlite"), echo=True, future=True) + models.Base.metadata.create_all(self.engine) + + def create_scan_result(self, ip_address, scan_results, hostname=None): + session = Session(bind=self.engine) + + # Does an existing target exist? + scan_target = session.query(models.ScanTarget).filter(or_( + models.ScanTarget.ip==ip_address, + models.ScanTarget.hostname==hostname, + )).first() + + # Nope, create one + if not scan_target: + scan_target = models.ScanTarget(ip=ip_address, hostname=hostname) + session.add(scan_target) + session.commit() + + # Create scan history + scan_history = models.ScanHistory(target=scan_target.id, results=",".join(map(str, scan_results)), datetime=datetime.now()) + session.add(scan_history) + session.commit() + + session.close() + + + def create_target(self, ip_address, hostname): + stmt = insert(models.ScanTarget).values(ip=ip_address, hostname=hostname) + compiled = stmt.compile() + self.__execute_statement(stmt) + return stmt + + + def __execute_statement(self, stmt): + with self.engine.connect() as conn: + result = conn.execute(stmt) + conn.commit() + return result + + +# FOR TESTING PURPOSES +def main(): + db = DatabaseConnector("/Users/evanreichard/Development/git/overseer/src/overseer") + db.create_scan_result(1234577, [5,6,7,8], "test222.com") +if __name__ == "__main__": + main() diff --git a/src/overseer/models.py b/src/overseer/models.py new file mode 100644 index 0000000..f875702 --- /dev/null +++ b/src/overseer/models.py @@ -0,0 +1,37 @@ +from sqlalchemy import Column, Integer, String, ForeignKey, DateTime +from sqlalchemy.orm import declarative_base + +Base = declarative_base() + +class ScanTarget(Base): + __tablename__ = "scan_target" + + # Unique ID + id = Column(Integer, primary_key=True, unique=True) + + # Integer representation of an IP Address. Can use SQL queries to filter subnets. + ip = Column(Integer, index=True, unique=True) + + # Corresponding hostname. + hostname = Column(String, index=True, unique=True) + + def __repr__(self): + return f"ScanTarget(id={self.id!r}, ip={self.ip!r}, hostname={self.hostname!r})" + +class ScanHistory(Base): + __tablename__ = "scan_history" + + # Unique ID + id = Column(Integer, primary_key=True, unique=True) + + # Scan Target Reference + target = Column(Integer, ForeignKey("scan_target.id")) + + # Results + results = Column(String) + + # DateTime + datetime = Column(DateTime()) + + def __repr__(self): + return f"ScanHistory(id={self.id!r}, target={self.target!r}, results={self.results!r}, datetime={self.datetime!r})" diff --git a/src/overseer/scanner.py b/src/overseer/scanner.py new file mode 100644 index 0000000..e69de29 diff --git a/src/overseer_client/README.md b/src/overseer_client/README.md index 57577b0..2c65731 100644 --- a/src/overseer_client/README.md +++ b/src/overseer_client/README.md @@ -11,6 +11,8 @@ yarn serve ``` ### Compiles and minifies for production +This will automatically populate the overseer server with the appropriate resources in `templates` and `static` + ``` yarn build ```