diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..a76497d --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,34 @@ +# This workflow will upload a Python Package using Twine when a release is created + +name: pypi-publish + +# Controls when the workflow will run +on: + workflow_dispatch: {} + release: + types: [ published ] + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build twine + - name: Create packages + run: python -m build + - name: Run twine check + run: twine check dist/* + - name: Upload to pypi + env: + TWINE_USERNAME: ${{ secrets.PYPI_USER }} + TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} + run: twine upload dist/*.whl diff --git a/.gitignore b/.gitignore index 6f33716..e463960 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ venv __pycache__/ _build/ fileio +dist/ +NetSec.egg-info/ diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..b286ed9 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,5 @@ +global-exclude .env +global-exclude *.yaml +global-exclude *.json +global-exclude .DS_Store +recursive-include netsec/modules * \ No newline at end of file diff --git a/README.md b/README.md index 81dc153..f43b014 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,29 @@ -# NetScan -Network Scanner to analyze devices connecting to the router and alert accordingly. +# NetSec (Network Security) +NetSec is a tool to analyze devices connecting to the router and alert accordingly when a new device is connected. -This app can display intruders' IP addresses, MAC addresses, and lets the user Ping the device, and even Block the device. - -Block IP Address feature helps the user to remove the specified connections and block the specific IP address. +This app can display and store intruders' IP address, MAC address, and Block the device. > Blocking device feature is currently available only for `Netgear` router users. +```python +from netsec import network_monitor, SupportedModules + +if __name__ == '__main__': + # SupportedModules.att # for AT&T users + # SupportedModules.netgear # for any network using Netgear router + network_monitor(module=SupportedModules.att, init=True) # Create snapshot + network_monitor(module=SupportedModules.att, init=False) # Scan for threats and alert +``` + +> Notifications will not repeat within an hour. + ## Coding Standards Docstring format: [`Google`](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
Styling conventions: [`PEP 8`](https://www.python.org/dev/peps/pep-0008/)
Clean code with pre-commit hooks: [`flake8`](https://flake8.pycqa.org/en/latest/) and [`isort`](https://pycqa.github.io/isort/) -## [Release Notes](https://github.com/thevickypedia/netscan/blob/master/release_notes.rst) +## [Release Notes](https://github.com/thevickypedia/NetSec/blob/master/release_notes.rst) **Requirement** ```shell python -m pip install changelog-generator @@ -40,4 +50,4 @@ pre-commit run --all-files ## Runbook [![made-with-sphinx-doc](https://img.shields.io/badge/Code%20Docs-Sphinx-1f425f.svg)](https://www.sphinx-doc.org/en/master/man/sphinx-autogen.html) -[https://thevickypedia.github.io/netscan/](https://thevickypedia.github.io/netscan/) +[https://thevickypedia.github.io/NetSec/](https://thevickypedia.github.io/NetSec/) diff --git a/docs/README.html b/docs/README.html index 621cc50..68d9958 100644 --- a/docs/README.html +++ b/docs/README.html @@ -6,7 +6,7 @@ - NetScan — NetScan documentation + NetSec (Network Security) — NetSec documentation @@ -18,7 +18,7 @@ - + @@ -42,15 +42,18 @@

Navigation

-
-

NetScan

-

Network Scanner to analyze devices connecting to the router and alert accordingly.

-

This app can display intruders’ IP addresses, MAC addresses, and lets the user Ping the device, and even Block the device.

-

Block IP Address feature helps the user to remove the specified connections and block the specific IP address.

+
+

NetSec (Network Security)

+

NetSec is a tool to analyze devices connecting to the router and alert accordingly when a new device is connected.

+

This app can display and store intruders’ IP address, MAC address, and Block the device.

Blocking device feature is currently available only for Netgear router users.

-

+
from netsec import network_monitor, SupportedModules
+
+if __name__ == '__main__':
+    network_monitor(module=SupportedModules.att, init=True)  # Create snapshot
+    network_monitor(module=SupportedModules.att, init=False)  # Run the scan
 
@@ -61,7 +64,7 @@

Coding Standardsisort

-

Release Notes

+

Release Notes

Requirement

python -m pip install changelog-generator
 
@@ -86,7 +89,7 @@

Linting

Runbook

made-with-sphinx-doc

-

https://thevickypedia.github.io/netscan/

+

https://thevickypedia.github.io/NetSec/

@@ -100,7 +103,7 @@

RunbookTable of Contents

@@ -58,22 +58,15 @@

Index

A

@@ -81,13 +74,13 @@

A

B

@@ -95,31 +88,27 @@

B

C

+ -

D

@@ -127,7 +116,7 @@

D

F

@@ -135,17 +124,17 @@

F

G

@@ -153,7 +142,7 @@

G

L

@@ -165,69 +154,74 @@

M

module + + + +

N

+ + - + -
  • - modules.netgear + netsec.modules.models
  • - modules.settings + netsec.modules.netgear
  • -
+
  • + netsec.modules.settings -

    N

    - - -
    +
  • network_monitor() (in module netsec.analyzer)
  • -
  • notify() (in module modules.helper) +
  • notify() (in module netsec.modules.helper)
  • @@ -235,7 +229,7 @@

    N

    P

    @@ -243,16 +237,16 @@

    P

    R

    @@ -261,11 +255,11 @@

    R

    S

    @@ -301,7 +295,7 @@

    Navigation

  • modules |
  • - +
    diff --git a/docs/index.html b/docs/index.html index b43bb34..04f0d10 100644 --- a/docs/index.html +++ b/docs/index.html @@ -6,7 +6,7 @@ - Welcome to NetScan’s documentation! — NetScan documentation + Welcome to NetSec’s documentation! — NetSec documentation @@ -18,7 +18,7 @@ - + @@ -42,12 +42,12 @@

    Navigation

    -
    -

    Welcome to NetScan’s documentation!

    +
    +

    Welcome to NetSec’s documentation!

    Read Me:

      -
    • NetScan
        +
      • NetSec (Network Security)
        • Coding Standards
        • Release Notes
        • Linting
        • @@ -57,11 +57,11 @@

          Welcome to NetScan’s documentation! -

          NetScan

          +
          +

          NetSec

          -
          -analyzer.network_monitor(module: SupportedModules, init: bool = True) NoReturn
          +
          +netsec.analyzer.network_monitor(module: SupportedModules, init: bool = True) NoReturn

          Monitor devices connected to the network.

          Parameters:
          @@ -74,11 +74,11 @@

          Welcome to NetScan’s documentation! -

          At&t

          +
          +

          At&t

          -
          -class modules.att.Device(dictionary: dict)
          +
          +class netsec.modules.att.Device(dictionary: dict)

          Convert dictionary into a device object.

          >>> Device
           
          @@ -86,20 +86,20 @@

          Welcome to NetScan’s documentation! -
          -modules.att.create_snapshot() NoReturn
          +
          +netsec.modules.att.create_snapshot() NoReturn

          Creates a snapshot.json which is used to determine the known and unknown devices.

          -
          -modules.att.format_key(key: str) str
          +
          +netsec.modules.att.format_key(key: str) str

          Format the key to match the Device object.

          -
          -modules.att.generate_dataframe() DataFrame
          +
          +netsec.modules.att.generate_dataframe() DataFrame

          Generate a dataframe using the devices information from router web page.

          Returns:
          @@ -112,8 +112,8 @@

          Welcome to NetScan’s documentation! -
          -modules.att.get_attached_devices() Generator[Device]
          +
          +netsec.modules.att.get_attached_devices() Generator[Device]

          Get all devices connected to the router.

          Yields:
          @@ -123,30 +123,30 @@

          Welcome to NetScan’s documentation! -
          -modules.att.get_ipaddress() str
          +
          +netsec.modules.att.get_ipaddress() str

          Get network id from the current IP address.

          -
          -modules.att.run() NoReturn
          +
          +netsec.modules.att.run() NoReturn

          Trigger to initiate a Network Scan and block the devices that are not present in snapshot.json file.

          -
          -

          Netgear

          +
          +

          Netgear

          -
          -class modules.netgear.LocalIPScan
          +
          +class netsec.modules.netgear.LocalIPScan

          Connector to scan devices in the same IP range using Netgear API.

          >>> LocalIPScan
           
          -
          -allow(device: Union[str, Device]) Optional[Device]
          +
          +allow(device: Union[str, Device]) Optional[Device]

          Allows internet access to a device.

          Parameters:
          @@ -156,14 +156,14 @@

          Welcome to NetScan’s documentation!

          Returns the device object received from get_device_by_name() method.

          Return type:
          -

          Device

          +

          Device

          -
          -always_allow(device: Device) NoReturn
          +
          +always_allow(device: Device) NoReturn

          Allows internet access to a device.

          Saves the device name to snapshot.json to not block in the future. Removes the device name from blocked.json if an entry is present.

          @@ -175,8 +175,8 @@

          Welcome to NetScan’s documentation! -
          -block(device: Union[str, Device]) Optional[Device]
          +
          +block(device: Union[str, Device]) Optional[Device]

          Blocks internet access to a device.

          Parameters:
          @@ -186,138 +186,114 @@

          Welcome to NetScan’s documentation!

          Returns the device object received from get_device_by_name() method.

          Return type:
          -

          Device

          +

          Device

          -
          -create_snapshot() NoReturn
          +
          +create_snapshot() NoReturn

          Creates a snapshot.json which is used to determine the known and unknown devices.

          -
          -run(block: bool = False) NoReturn
          +
          +run(block: bool = False) NoReturn

          Trigger to initiate a Network Scan and block the devices that are not present in snapshot.json file.

          -
          -

          Helper

          +
          +

          Helper

          -
          -modules.helper.custom_time(*args: Formatter) struct_time
          -

          Creates custom timezone for logging which gets used only when invoked by Docker.

          -

          This is used only when triggered within a docker container as it uses UTC timezone.

          -
          -
          Parameters:
          -

          *args – Takes Formatter object and current epoch time as arguments passed by formatTime from logging.

          -
          -
          Returns:
          -

          A struct_time object which is a tuple of: -current year, month, day, hour, minute, second, weekday, year day and dst (Daylight Saving Time)

          -
          -
          Return type:
          -

          struct_time

          -
          -
          -
          - -
          -
          -modules.helper.notify(msg: str) NoReturn
          +
          +netsec.modules.helper.notify(msg_dict: List[Dict[str, str]]) NoReturn

          Send an email notification when there is a threat.

          Parameters:
          -

          msg – Message that has to be sent.

          +

          msg_dict – Dict message to be sent as template.

          -
          -

          Models

          +
          +

          Models

          -
          -class modules.models.DeviceStatus(value)
          +
          +class netsec.modules.models.DeviceStatus(value)

          Device status strings for allow or block.

          -
          -allow: str = 'Allow'
          +
          +allow: str = 'Allow'
          -
          -block: str = 'Block'
          +
          +block: str = 'Block'
          -
          -class modules.models.SupportedModules(value)
          +
          +class netsec.modules.models.SupportedModules(value)

          Supported modules are At&t and Netgear.

          -
          -att: str = 'At&t'
          +
          +att: str = 'At&t'
          -
          -netgear: str = 'Netgear'
          +
          +netgear: str = 'Netgear'
          -
          -

          Settings

          +
          +

          Settings

          -
          -class modules.settings.Config
          +
          +class netsec.modules.settings.Config

          Wrapper for all the environment variables.

          -
          -blocked = 'fileio/blocked.yaml'
          -
          - -
          -
          -docker = None
          +
          +blocked: PathLike = 'fileio/blocked.yaml'
          -
          -gmail_pass = None
          +
          +gmail_pass: AnyStr = None
          -
          -gmail_user = None
          +
          +gmail_user: AnyStr = None
          -
          -phone = None
          +
          +phone: AnyStr = None
          -
          -recipient = None
          +
          +recipient: AnyStr = None
          -
          -router_pass = None
          +
          +router_pass: AnyStr = None
          -
          -snapshot = 'fileio/snapshot.json'
          +
          +snapshot: PathLike = 'fileio/snapshot.json'
          @@ -342,13 +318,13 @@

          Indices and tablesTable of Contents

          @@ -356,7 +332,7 @@

          Table of Contents

          Next topic

          NetScan

          + title="next chapter">NetSec (Network Security)

    @@ -44,52 +44,48 @@

    Navigation

    Python Module Index

    - a | - m + n
    - - - - - - + + + +
     
    - a
    - analyzer -
     
    - m
    + n
    - modules + netsec +
        + netsec.analyzer
        - modules.att + netsec.modules.att
        - modules.helper + netsec.modules.helper
        - modules.models + netsec.modules.models
        - modules.netgear + netsec.modules.netgear
        - modules.settings + netsec.modules.settings
    @@ -123,7 +119,7 @@

    Navigation

  • modules |
  • - +
    diff --git a/docs/search.html b/docs/search.html index 733416b..b4ecf7f 100644 --- a/docs/search.html +++ b/docs/search.html @@ -5,7 +5,7 @@ - Search — NetScan documentation + Search — NetSec documentation @@ -33,7 +33,7 @@

    Navigation

  • modules |
  • - +
    @@ -93,7 +93,7 @@

    Navigation

  • modules |
  • - + diff --git a/docs/searchindex.js b/docs/searchindex.js index 5bdcde1..fe4b9d5 100644 --- a/docs/searchindex.js +++ b/docs/searchindex.js @@ -1 +1 @@ -Search.setIndex({"docnames": ["README", "index"], "filenames": ["README.md", "index.rst"], "titles": ["NetScan", "Welcome to NetScan\u2019s documentation!"], "terms": {"network": [0, 1], "scanner": 0, "analyz": [0, 1], "devic": [0, 1], "connect": [0, 1], "router": [0, 1], "alert": 0, "accordingli": 0, "thi": [0, 1], "app": 0, "can": 0, "displai": 0, "intrud": 0, "ip": [0, 1], "address": [0, 1], "mac": 0, "let": 0, "user": 0, "ping": 0, "even": 0, "block": [0, 1], "featur": 0, "help": 0, "remov": [0, 1], "specifi": 0, "specif": 0, "i": [0, 1], "current": [0, 1], "avail": 0, "onli": [0, 1], "netgear": 0, "docstr": 0, "format": [0, 1], "googl": 0, "style": 0, "convent": 0, "pep": 0, "8": 0, "clean": 0, "pre": 0, "commit": 0, "hook": 0, "flake8": 0, "isort": 0, "requir": 0, "python": 0, "m": 0, "pip": 0, "instal": 0, "changelog": 0, "gener": [0, 1], "usag": 0, "revers": 0, "f": 0, "release_not": 0, "rst": 0, "t": 0, "precommit": 0, "ensur": 0, "doc": 0, "creation": 0, "ar": [0, 1], "run": [0, 1], "everi": 0, "sphinx": 0, "5": 0, "1": 0, "recommonmark": 0, "all": [0, 1], "file": [0, 1], "http": 0, "thevickypedia": 0, "github": 0, "io": 0, "code": 1, "standard": 1, "releas": 1, "note": 1, "lint": 1, "runbook": 1, "network_monitor": 1, "modul": 1, "supportedmodul": 1, "init": 1, "bool": 1, "true": 1, "noreturn": 1, "monitor": 1, "paramet": 1, "scan": 1, "support": 1, "ani": 1, "take": 1, "boolean": 1, "valu": 1, "creat": 1, "snapshot": 1, "actual": 1, "class": 1, "att": 1, "dictionari": 1, "dict": 1, "convert": 1, "object": 1, "create_snapshot": 1, "json": 1, "which": 1, "us": 1, "determin": 1, "known": 1, "unknown": 1, "format_kei": 1, "kei": 1, "str": 1, "match": 1, "generate_datafram": 1, "datafram": 1, "inform": 1, "from": 1, "web": 1, "page": 1, "return": 1, "list": 1, "data": 1, "frame": 1, "type": 1, "get_attached_devic": 1, "get": 1, "yield": 1, "each": 1, "get_ipaddress": 1, "id": 1, "trigger": 1, "initi": 1, "present": 1, "localipscan": 1, "connector": 1, "same": 1, "rang": 1, "api": 1, "allow": 1, "union": 1, "option": 1, "internet": 1, "access": 1, "name": 1, "an": 1, "argument": 1, "receiv": 1, "get_device_by_nam": 1, "method": 1, "always_allow": 1, "save": 1, "futur": 1, "entri": 1, "fals": 1, "custom_tim": 1, "arg": 1, "formatt": 1, "struct_tim": 1, "custom": 1, "timezon": 1, "log": 1, "when": 1, "invok": 1, "docker": 1, "within": 1, "contain": 1, "utc": 1, "epoch": 1, "time": 1, "pass": 1, "formattim": 1, "A": 1, "tupl": 1, "year": 1, "month": 1, "dai": 1, "hour": 1, "minut": 1, "second": 1, "weekdai": 1, "dst": 1, "daylight": 1, "notifi": 1, "msg": 1, "send": 1, "email": 1, "notif": 1, "threat": 1, "messag": 1, "ha": 1, "sent": 1, "devicestatu": 1, "statu": 1, "string": 1, "config": 1, "wrapper": 1, "environ": 1, "variabl": 1, "fileio": 1, "yaml": 1, "none": 1, "gmail_pass": 1, "gmail_us": 1, "phone": 1, "recipi": 1, "router_pass": 1, "index": 1, "search": 1}, "objects": {"": [[1, 0, 0, "-", "analyzer"]], "analyzer": [[1, 1, 1, "", "network_monitor"]], "modules": [[1, 0, 0, "-", "att"], [1, 0, 0, "-", "helper"], [1, 0, 0, "-", "models"], [1, 0, 0, "-", "netgear"], [1, 0, 0, "-", "settings"]], "modules.att": [[1, 2, 1, "", "Device"], [1, 1, 1, "", "create_snapshot"], [1, 1, 1, "", "format_key"], [1, 1, 1, "", "generate_dataframe"], [1, 1, 1, "", "get_attached_devices"], [1, 1, 1, "", "get_ipaddress"], [1, 1, 1, "", "run"]], "modules.helper": [[1, 1, 1, "", "custom_time"], [1, 1, 1, "", "notify"]], "modules.models": [[1, 2, 1, "", "DeviceStatus"], [1, 2, 1, "", "SupportedModules"]], "modules.models.DeviceStatus": [[1, 3, 1, "", "allow"], [1, 3, 1, "", "block"]], "modules.models.SupportedModules": [[1, 3, 1, "", "att"], [1, 3, 1, "", "netgear"]], "modules.netgear": [[1, 2, 1, "", "LocalIPScan"]], "modules.netgear.LocalIPScan": [[1, 4, 1, "", "allow"], [1, 4, 1, "", "always_allow"], [1, 4, 1, "", "block"], [1, 4, 1, "", "create_snapshot"], [1, 4, 1, "", "run"]], "modules.settings": [[1, 2, 1, "", "Config"]], "modules.settings.Config": [[1, 3, 1, "", "blocked"], [1, 3, 1, "", "docker"], [1, 3, 1, "", "gmail_pass"], [1, 3, 1, "", "gmail_user"], [1, 3, 1, "", "phone"], [1, 3, 1, "", "recipient"], [1, 3, 1, "", "router_pass"], [1, 3, 1, "", "snapshot"]]}, "objtypes": {"0": "py:module", "1": "py:function", "2": "py:class", "3": "py:attribute", "4": "py:method"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "function", "Python function"], "2": ["py", "class", "Python class"], "3": ["py", "attribute", "Python attribute"], "4": ["py", "method", "Python method"]}, "titleterms": {"netscan": [0, 1], "code": 0, "standard": 0, "releas": 0, "note": 0, "lint": 0, "runbook": 0, "welcom": 1, "": 1, "document": 1, "read": 1, "me": 1, "At": 1, "t": 1, "netgear": 1, "helper": 1, "model": 1, "set": 1, "indic": 1, "tabl": 1}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 6, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 56}}) \ No newline at end of file +Search.setIndex({"docnames": ["README", "index"], "filenames": ["README.md", "index.rst"], "titles": ["NetSec (Network Security)", "Welcome to NetSec\u2019s documentation!"], "terms": {"i": [0, 1], "tool": 0, "analyz": [0, 1], "devic": [0, 1], "connect": [0, 1], "router": [0, 1], "alert": 0, "accordingli": 0, "when": [0, 1], "new": 0, "thi": 0, "app": 0, "can": 0, "displai": 0, "store": 0, "intrud": 0, "ip": [0, 1], "address": [0, 1], "mac": 0, "block": [0, 1], "featur": 0, "current": [0, 1], "avail": 0, "onli": 0, "netgear": 0, "user": 0, "from": [0, 1], "import": 0, "network_monitor": [0, 1], "supportedmodul": [0, 1], "__name__": 0, "__main__": 0, "modul": [0, 1], "att": [0, 1], "init": [0, 1], "true": [0, 1], "creat": [0, 1], "snapshot": [0, 1], "fals": [0, 1], "run": [0, 1], "scan": [0, 1], "docstr": 0, "format": [0, 1], "googl": 0, "style": 0, "convent": 0, "pep": 0, "8": 0, "clean": 0, "pre": 0, "commit": 0, "hook": 0, "flake8": 0, "isort": 0, "requir": 0, "python": 0, "m": 0, "pip": 0, "instal": 0, "changelog": 0, "gener": [0, 1], "usag": 0, "revers": 0, "f": 0, "release_not": 0, "rst": 0, "t": 0, "precommit": 0, "ensur": 0, "doc": 0, "creation": 0, "ar": [0, 1], "everi": 0, "sphinx": 0, "5": 0, "1": 0, "recommonmark": 0, "all": [0, 1], "file": [0, 1], "http": 0, "thevickypedia": 0, "github": 0, "io": 0, "network": 1, "secur": 1, "code": 1, "standard": 1, "releas": 1, "note": 1, "lint": 1, "runbook": 1, "bool": 1, "noreturn": 1, "monitor": 1, "paramet": 1, "support": 1, "ani": 1, "take": 1, "boolean": 1, "valu": 1, "actual": 1, "class": 1, "dictionari": 1, "dict": 1, "convert": 1, "object": 1, "create_snapshot": 1, "json": 1, "which": 1, "us": 1, "determin": 1, "known": 1, "unknown": 1, "format_kei": 1, "kei": 1, "str": 1, "match": 1, "generate_datafram": 1, "datafram": 1, "inform": 1, "web": 1, "page": 1, "return": 1, "list": 1, "data": 1, "frame": 1, "type": 1, "get_attached_devic": 1, "get": 1, "yield": 1, "each": 1, "get_ipaddress": 1, "id": 1, "trigger": 1, "initi": 1, "present": 1, "localipscan": 1, "connector": 1, "same": 1, "rang": 1, "api": 1, "allow": 1, "union": 1, "option": 1, "internet": 1, "access": 1, "name": 1, "an": 1, "argument": 1, "receiv": 1, "get_device_by_nam": 1, "method": 1, "always_allow": 1, "save": 1, "futur": 1, "remov": 1, "entri": 1, "notifi": 1, "msg_dict": 1, "send": 1, "email": 1, "notif": 1, "threat": 1, "messag": 1, "sent": 1, "templat": 1, "devicestatu": 1, "statu": 1, "string": 1, "config": 1, "wrapper": 1, "environ": 1, "variabl": 1, "pathlik": 1, "fileio": 1, "yaml": 1, "gmail_pass": 1, "anystr": 1, "none": 1, "gmail_us": 1, "phone": 1, "recipi": 1, "router_pass": 1, "index": 1, "search": 1}, "objects": {"netsec": [[1, 0, 0, "-", "analyzer"]], "netsec.analyzer": [[1, 1, 1, "", "network_monitor"]], "netsec.modules": [[1, 0, 0, "-", "att"], [1, 0, 0, "-", "helper"], [1, 0, 0, "-", "models"], [1, 0, 0, "-", "netgear"], [1, 0, 0, "-", "settings"]], "netsec.modules.att": [[1, 2, 1, "", "Device"], [1, 1, 1, "", "create_snapshot"], [1, 1, 1, "", "format_key"], [1, 1, 1, "", "generate_dataframe"], [1, 1, 1, "", "get_attached_devices"], [1, 1, 1, "", "get_ipaddress"], [1, 1, 1, "", "run"]], "netsec.modules.helper": [[1, 1, 1, "", "notify"]], "netsec.modules.models": [[1, 2, 1, "", "DeviceStatus"], [1, 2, 1, "", "SupportedModules"]], "netsec.modules.models.DeviceStatus": [[1, 3, 1, "", "allow"], [1, 3, 1, "", "block"]], "netsec.modules.models.SupportedModules": [[1, 3, 1, "", "att"], [1, 3, 1, "", "netgear"]], "netsec.modules.netgear": [[1, 2, 1, "", "LocalIPScan"]], "netsec.modules.netgear.LocalIPScan": [[1, 4, 1, "", "allow"], [1, 4, 1, "", "always_allow"], [1, 4, 1, "", "block"], [1, 4, 1, "", "create_snapshot"], [1, 4, 1, "", "run"]], "netsec.modules.settings": [[1, 2, 1, "", "Config"]], "netsec.modules.settings.Config": [[1, 3, 1, "", "blocked"], [1, 3, 1, "", "gmail_pass"], [1, 3, 1, "", "gmail_user"], [1, 3, 1, "", "phone"], [1, 3, 1, "", "recipient"], [1, 3, 1, "", "router_pass"], [1, 3, 1, "", "snapshot"]]}, "objtypes": {"0": "py:module", "1": "py:function", "2": "py:class", "3": "py:attribute", "4": "py:method"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "function", "Python function"], "2": ["py", "class", "Python class"], "3": ["py", "attribute", "Python attribute"], "4": ["py", "method", "Python method"]}, "titleterms": {"netsec": [0, 1], "network": 0, "secur": 0, "code": 0, "standard": 0, "releas": 0, "note": 0, "lint": 0, "runbook": 0, "welcom": 1, "": 1, "document": 1, "read": 1, "me": 1, "At": 1, "t": 1, "netgear": 1, "helper": 1, "model": 1, "set": 1, "indic": 1, "tabl": 1}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 6, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 56}}) \ No newline at end of file diff --git a/docs_gen/conf.py b/docs_gen/conf.py index ac22948..8a854a6 100644 --- a/docs_gen/conf.py +++ b/docs_gen/conf.py @@ -18,7 +18,7 @@ # -- Project information ----------------------------------------------------- -project = 'NetScan' +project = 'NetSec' copyright = '2021, Vignesh Sivanandha Rao' author = 'Vignesh Sivanandha Rao' diff --git a/docs_gen/index.rst b/docs_gen/index.rst index d788cb7..fbe96c7 100644 --- a/docs_gen/index.rst +++ b/docs_gen/index.rst @@ -1,10 +1,10 @@ -.. NetScan documentation master file, created by +.. NetSec documentation master file, created by sphinx-quickstart on Thu Aug 26 16:15:06 2021. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to NetScan's documentation! -=================================== +Welcome to NetSec's documentation! +================================== .. toctree:: :maxdepth: 2 @@ -12,45 +12,45 @@ Welcome to NetScan's documentation! README -NetScan -======= +NetSec +====== -.. automodule:: analyzer +.. automodule:: netsec.analyzer :members: :undoc-members: At&t ==== -.. automodule:: modules.att +.. automodule:: netsec.modules.att :members: :undoc-members: Netgear ======= -.. automodule:: modules.netgear +.. automodule:: netsec.modules.netgear :members: :undoc-members: Helper ====== -.. automodule:: modules.helper +.. automodule:: netsec.modules.helper :members: :undoc-members: Models ====== -.. automodule:: modules.models +.. automodule:: netsec.modules.models :members: :undoc-members: Settings ======== -.. automodule:: modules.settings +.. automodule:: netsec.modules.settings :members: :undoc-members: diff --git a/modules/helper.py b/modules/helper.py deleted file mode 100644 index 52331ee..0000000 --- a/modules/helper.py +++ /dev/null @@ -1,49 +0,0 @@ -import logging -import time -from datetime import datetime, timezone -from typing import NoReturn - -import gmailconnector - -from modules.settings import LOGGER, config - - -def custom_time(*args: logging.Formatter or time.time) -> time.struct_time: - """Creates custom timezone for ``logging`` which gets used only when invoked by ``Docker``. - - This is used only when triggered within a ``docker container`` as it uses UTC timezone. - - Args: - *args: Takes ``Formatter`` object and current epoch time as arguments passed by ``formatTime`` from ``logging``. - - Returns: - struct_time: - A struct_time object which is a tuple of: - **current year, month, day, hour, minute, second, weekday, year day and dst** *(Daylight Saving Time)* - """ - LOGGER.debug(args) - local_timezone = datetime.now(tz=timezone.utc).astimezone().tzinfo - return datetime.now().astimezone(tz=local_timezone).timetuple() - - -if config.docker: - logging.Formatter.converter = custom_time - - -def notify(msg: str) -> NoReturn: - """Send an email notification when there is a threat. - - Args: - msg: Message that has to be sent. - """ - if config.gmail_user and config.gmail_pass and config.recipient: - emailer = gmailconnector.SendEmail(gmail_user=config.gmail_user, - gmail_pass=config.gmail_pass) - response = emailer.send_email(recipient=config.recipient, - subject=f"Netscan Alert - {datetime.now().strftime('%C')}", body=msg) - if response.ok: - LOGGER.info("Firewall alert has been sent to '%s'" % config.phone) - else: - LOGGER.error("Failed to send a notification.\n%s" % response.body) - else: - LOGGER.info("Env variables not found to trigger notification.") diff --git a/modules/settings.py b/modules/settings.py deleted file mode 100644 index 24e1990..0000000 --- a/modules/settings.py +++ /dev/null @@ -1,34 +0,0 @@ -import logging -import os - -import dotenv - -dotenv.load_dotenv(dotenv_path=".env") - -if not os.path.isdir('fileio'): - os.makedirs('fileio') - -LOGGER = logging.getLogger(__name__) -handler = logging.StreamHandler() -handler.setFormatter(fmt=logging.Formatter( - fmt="%(asctime)s - [%(levelname)s] - %(name)s - %(funcName)s - Line: %(lineno)d - %(message)s", - datefmt='%b-%d-%Y %H:%M:%S' -)) -LOGGER.setLevel(level=logging.DEBUG) -LOGGER.addHandler(hdlr=handler) - - -class Config: - """Wrapper for all the environment variables.""" - - router_pass = os.environ.get('ROUTER_PASS') or os.environ.get('router_pass') - gmail_user = os.environ.get('GMAIL_USER') or os.environ.get('gmail_user') - gmail_pass = os.environ.get('GMAIL_PASS') or os.environ.get('gmail_pass') - recipient = os.environ.get('RECIPIENT') or os.environ.get('recipient') - docker = os.environ.get('DOCKER') or os.environ.get('docker') - phone = os.environ.get('PHONE') or os.environ.get('phone') - snapshot = os.path.join('fileio', 'snapshot.json') - blocked = os.path.join('fileio', 'blocked.yaml') - - -config = Config() diff --git a/netsec/__init__.py b/netsec/__init__.py new file mode 100644 index 0000000..72697aa --- /dev/null +++ b/netsec/__init__.py @@ -0,0 +1,7 @@ +"""Place holder for package.""" + +from netsec.analyzer import network_monitor # noqa: F401 +from netsec.modules.models import SupportedModules # noqa: F401 +from netsec.modules.settings import config # noqa: F401 + +version = "0.1.7" diff --git a/analyzer.py b/netsec/analyzer.py similarity index 64% rename from analyzer.py rename to netsec/analyzer.py index da3d3dd..4cee253 100644 --- a/analyzer.py +++ b/netsec/analyzer.py @@ -1,20 +1,26 @@ from typing import NoReturn -from modules import att, models, netgear +from gmailconnector.validator import address as email_address +from netsec.modules import att, models, netgear, settings -def network_monitor(module: models.SupportedModules, init: bool = True) -> NoReturn: + +def network_monitor(module: models.SupportedModules, init: bool = True, block: bool = False) -> NoReturn: """Monitor devices connected to the network. Args: module: Module to scan. Currently, supports any network on a Netgear router or At&t networks. init: Takes a boolean value to create a snapshot file or actually monitor the network. + block: Takes a boolean value whether to block the intrusive device. """ + if settings.config.recipient: + email_address.logger = settings.LOGGER + settings.config.recipient = email_address.ValidateAddress(address=settings.config.recipient) # noqa if module == models.SupportedModules.netgear: if init: netgear.LocalIPScan().create_snapshot() else: - netgear.LocalIPScan().run() + netgear.LocalIPScan().run(block=block) elif module == models.SupportedModules.att: if init: att.create_snapshot() @@ -25,7 +31,3 @@ def network_monitor(module: models.SupportedModules, init: bool = True) -> NoRet "\n\nnetwork argument should either be '%s' or '%s'" % (models.SupportedModules.att, models.SupportedModules.netgear) ) - - -if __name__ == '__main__': - network_monitor(module=models.SupportedModules.att, init=False) diff --git a/modules/att.py b/netsec/modules/att.py similarity index 86% rename from modules/att.py rename to netsec/modules/att.py index a0056fb..363cd06 100644 --- a/modules/att.py +++ b/netsec/modules/att.py @@ -8,8 +8,8 @@ import requests from pandas import DataFrame -from modules.helper import notify -from modules.settings import LOGGER, config +from netsec.modules.helper import notify +from netsec.modules.settings import LOGGER, config SOURCE = "http://{NETWORK_ID}.254/cgi-bin/devices.ha" @@ -73,7 +73,10 @@ def generate_dataframe() -> DataFrame: else: if response.ok: html_source = response.text - html_tables = pandas.read_html(html_source) + try: + html_tables = pandas.read_html(html_source) + except ImportError: + raise ValueError("No tables found") return html_tables[0] else: LOGGER.error("[%s] - %s" % (response.status_code, response.text)) @@ -122,23 +125,22 @@ def create_snapshot() -> NoReturn: def run() -> NoReturn: """Trigger to initiate a Network Scan and block the devices that are not present in ``snapshot.json`` file.""" if not os.path.isfile(config.snapshot): - LOGGER.error("'%s' not found. Please run `create_snapshot()` and review it." % config.snapshot) + LOGGER.error("'%s' not found. Please pass `init=True` to generate snapshot and review it." % config.snapshot) raise FileNotFoundError( "'%s' is required" % config.snapshot ) with open(config.snapshot) as file: device_list = json.load(file) stored_ips = list(device_list.keys()) - threat = '' + threats = [] for device in get_attached_devices(): if device.ipv4_address and device.ipv4_address not in stored_ips: - # SOURCE = "http://{NETWORK_ID}.254/cgi-bin/devices.ha" + # REMOTE = "http://{NETWORK_ID}.254/cgi-bin/remoteaccess.ha" LOGGER.warning('{name} [{ip}: {mac}] is connected to your network.'.format(name=device.name, mac=device.mac_address, ip=device.ipv4_address)) - threat += '\nName: {name}\nIP: {ip}\nMAC: {mac}'.format(name=device.name, mac=device.mac_address, - ip=device.ipv4_address) - if threat: - notify(msg=threat) + threats.append(dict(Name=device.name, MAC=device.mac_address.upper(), IP=device.ipv4_address)) + if threats: + notify(msg_dict=threats) else: - LOGGER.info('NetScan has completed. No threats found on your network.') + LOGGER.info('NetSec has completed. No threats found on your network.') diff --git a/netsec/modules/email_template.html b/netsec/modules/email_template.html new file mode 100644 index 0000000..6a778f2 --- /dev/null +++ b/netsec/modules/email_template.html @@ -0,0 +1,24 @@ +

    NetSec Intrusion Alert

    + + + + + {% for alert in alerts %} + + + + + {% for key, value in alert.items() %} + + + + + {% endfor %} +
    + {% endfor %} + +
    CategoryValue
    {{ key }} {{ value }}
    +
    +
    Source code: https://github.com/thevickypedia/NetSec +
    Reach out: https://vigneshrao.com/contact +
    diff --git a/netsec/modules/helper.py b/netsec/modules/helper.py new file mode 100644 index 0000000..e513c49 --- /dev/null +++ b/netsec/modules/helper.py @@ -0,0 +1,57 @@ +import time +import os +from datetime import datetime +from typing import Dict, List, NoReturn + +import gmailconnector +import jinja2 +from gmailconnector.responder import Response + +from netsec.modules.settings import LOGGER, config + + +def _log_response(response: Response) -> NoReturn: + """Log response from gmailconnector.""" + if response.ok: + LOGGER.info(response.body) + return True + LOGGER.error("Failed to send a notification.\n%s" % response.body) + + +def notify(msg_dict: List[Dict[str, str]]) -> NoReturn: + """Send an email notification when there is a threat. + + Args: + msg_dict: Dict message to be sent as template. + """ + if not config.gmail_user and not config.gmail_pass and not (config.recipient or config.phone): + LOGGER.info("Env variables not found to trigger notifications.") + return + if os.path.isfile(config.notification): + with open(config.notification) as file: + updated = file.read() + if updated and time.time() - float(updated) < 3_600: + LOGGER.info("Last notification was sent within an hour.") + return + sub = f"NetSec Alert - {datetime.now().strftime('%c')}" + if config.recipient: + with open(os.path.join(os.path.dirname(__file__), 'email_template.html')) as file: + template = jinja2.Template(file.read()) + rendered = template.render(alerts=msg_dict) + emailer = gmailconnector.SendEmail(gmail_user=config.gmail_user, gmail_pass=config.gmail_pass) + response = emailer.send_email(recipient=config.recipient.email, sender="NetSec", + subject=sub, html_body=rendered) + if _log_response(response=response): + with open(config.notification, 'w') as file: + file.write(time.time().__str__()) + if config.phone: + msg = "" + for part in msg_dict: + for key, value in part.items(): + msg += "%s: %s\n" % (key, value) + msg += "\n" + messenger = gmailconnector.SendSMS(gmail_user=config.gmail_user, gmail_pass=config.gmail_pass) + response = messenger.send_sms(message=msg, subject=sub) + if _log_response(response=response): + with open(config.notification, 'w') as file: + file.write(time.time().__str__()) diff --git a/modules/models.py b/netsec/modules/models.py similarity index 100% rename from modules/models.py rename to netsec/modules/models.py diff --git a/modules/netgear.py b/netsec/modules/netgear.py similarity index 93% rename from modules/netgear.py rename to netsec/modules/netgear.py index 7cabbd2..3183b0d 100644 --- a/modules/netgear.py +++ b/netsec/modules/netgear.py @@ -7,9 +7,9 @@ import yaml from pynetgear import Device, Netgear -from modules.helper import notify -from modules.models import DeviceStatus -from modules.settings import LOGGER, config +from netsec.modules.helper import notify +from netsec.modules.models import DeviceStatus +from netsec.modules.settings import LOGGER, config class LocalIPScan: @@ -179,14 +179,15 @@ def always_allow(self, device: Device or str) -> NoReturn: def run(self, block: bool = False) -> NoReturn: """Trigger to initiate a Network Scan and block the devices that are not present in ``snapshot.json`` file.""" if not os.path.isfile(config.snapshot): - LOGGER.error("'%s' not found. Please generate one and review it." % config.snapshot) + LOGGER.error("'%s' not found. Please pass `init=True` to generate " + "snapshot and review it." % config.snapshot) raise FileNotFoundError( '%s is required' % config.snapshot ) with open(config.snapshot) as file: device_list = json.load(file) stored_ips = list(device_list.keys()) - threat = '' + threat = [] blocked = list(self._get_blocked()) for device in self._get_devices(): if device.ip and device.ip not in stored_ips: @@ -200,12 +201,11 @@ def run(self, block: bool = False) -> NoReturn: self._dump_blocked(device=device) else: LOGGER.info("'%s' is a part of deny list." % device.name) - threat += '\nName: {name}\nIP: {ip}\nMAC: {mac}'.format(name=device.name, ip=device.ip, - mac=device.mac) + threat.append(dict(Name=device.name, IP=device.ip, MAC=device.mac)) else: LOGGER.info("'%s' does not have internet access." % device.name) if threat: - notify(msg=threat) + notify(msg_dict=threat) else: - LOGGER.info('NetScan has completed. No threats found on your network.') + LOGGER.info('NetSec has completed. No threats found on your network.') diff --git a/netsec/modules/settings.py b/netsec/modules/settings.py new file mode 100644 index 0000000..464180d --- /dev/null +++ b/netsec/modules/settings.py @@ -0,0 +1,35 @@ +import logging +import os +from typing import AnyStr + +import dotenv + +dotenv.load_dotenv(dotenv_path=".env") + +if not os.path.isdir('fileio'): + os.makedirs('fileio') + +LOGGER = logging.getLogger(__name__) +handler = logging.StreamHandler() +handler.setFormatter(fmt=logging.Formatter( + fmt="%(asctime)s - [%(levelname)s] - %(name)s - %(funcName)s - Line: %(lineno)d - %(message)s", + datefmt='%b-%d-%Y %H:%M:%S' +)) +LOGGER.setLevel(level=logging.DEBUG) +LOGGER.addHandler(hdlr=handler) + + +class Config: + """Wrapper for all the environment variables.""" + + router_pass: AnyStr = os.environ.get('ROUTER_PASS') or os.environ.get('router_pass') + gmail_user: AnyStr = os.environ.get('GMAIL_USER') or os.environ.get('gmail_user') + gmail_pass: AnyStr = os.environ.get('GMAIL_PASS') or os.environ.get('gmail_pass') + recipient: AnyStr = os.environ.get('RECIPIENT') or os.environ.get('recipient') + phone: AnyStr = os.environ.get('PHONE') or os.environ.get('phone') + snapshot: os.PathLike = os.path.join('fileio', 'snapshot.json') + blocked: os.PathLike = os.path.join('fileio', 'blocked.yaml') + notification: os.PathLike = os.path.join('fileio', 'last_notify') + + +config = Config() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c5655ad --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,50 @@ +[project] +name = "NetSec" +dynamic = ["version"] +description = "Python module to analyze devices connected to the router and alert accordingly." +readme = "README.md" +authors = [{ name = "Vignesh Sivanandha Rao", email = "svignesh1793@gmail.com" }] +license = { file = "LICENSE" } +classifiers = [ + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Development Status :: 5 - Production/Stable", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX :: Linux", + "Topic :: System :: Networking :: Firewalls", + "Topic :: System :: Networking :: Monitoring :: Hardware Watchdog" +] +keywords = ["NetSec", "network-security", "lan", "wlan"] +requires-python = ">=3" +dependencies = [ + "pynetgear==0.10.9", + "python-dotenv", + "pytz", + "PyYAML", + "requests", + "pandas", + "lxml", + "gmail-connector", + "Jinja2" +] + +[tool.setuptools] +packages = ["netsec"] + +[tool.setuptools.dynamic] +version = {attr = "netsec.version"} + +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" + +[project.optional-dependencies] +dev = ["pytest", "pre-commit"] + +[project.urls] +Homepage = "https://github.com/thevickypedia/NetSec" +Docs = "https://thevickypedia.github.io/NetSec/" +Source = "https://github.com/thevickypedia/NetSec" +"Bug Tracker" = "https://github.com/thevickypedia/NetSec/issues" +"Release Notes" = "https://github.com/thevickypedia/NetSec/blob/main/release_notes.rst" diff --git a/release_notes.rst b/release_notes.rst index 93845e7..32bce80 100644 --- a/release_notes.rst +++ b/release_notes.rst @@ -1,6 +1,20 @@ Release Notes ============= +0.1.7 (02/25/2023) +------------------ +- Make `NetSec` pip installable +- Do not repeat notifications in under an hour +- Onboard github actions to deploy to pypi +- Update .gitignore, README.md and release_notes.rst + +0.1.6 (02/24/2023) +------------------ +- Support `AT&T` networks +- Restructure code to improve speed and usability +- Update README.md, docs and requirements.txt +- Remove Dockerfile + 0.1.5 (04/16/2022) ------------------ - Update docstrings diff --git a/requirements.txt b/requirements.txt index 4fc25dc..a0f0e4e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,4 @@ -pynetgear==0.10.9 -python-dotenv -pytz -PyYAML -requests -pandas -lxml -gmail-connector \ No newline at end of file +-e . +sphinx==5.1.1 +pre-commit +recommonmark \ No newline at end of file