├── .coveragerc ├── .editorconfig ├── .flake8 ├── .gitignore ├── .gitmodules ├── .idea ├── ProbeManager.iml ├── codeStyles │ └── Project.xml ├── deployment.xml ├── inspectionProfiles │ └── Project_Default.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── .travis.yml ├── CHANGELOG.rst ├── CODE_OF_CONDUCT.md ├── CONTRIBUTORS.txt ├── LICENSE ├── README.rst ├── docs ├── Makefile ├── _static │ └── .init ├── changelog.rst ├── conf.py ├── data │ ├── Deployement_example_of_Probemanager_in_a_VPS.png │ ├── Deployement_example_of_Probemanager_in_a_network.png │ └── favicon.ico ├── index.rst ├── internal │ ├── core.rst │ ├── index.rst │ └── rules.rst ├── modules │ └── .init └── readme.rst ├── install.sh ├── merge-develop.sh ├── probemanager ├── LICENSE ├── README.rst ├── api │ ├── __init__.py │ ├── apps.py │ ├── migrations │ │ └── __init__.py │ ├── pagination.py │ ├── serializers.py │ ├── urls.py │ └── views.py ├── core │ ├── README.rst │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── data │ │ ├── admin-index.png │ │ ├── admin-jobs-full.png │ │ ├── admin-jobs.png │ │ ├── admin-server-add.png │ │ └── admin-sshkey-add.png │ ├── exceptions.py │ ├── fixtures │ │ ├── test-core-probe.json │ │ ├── test-core-probeconfiguration.json │ │ └── test-core-sshkey.json │ ├── forms.py │ ├── git.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── modelsmixins.py │ ├── notifications.py │ ├── ssh.py │ ├── static │ │ └── core │ │ │ ├── bootstrap │ │ │ ├── css │ │ │ │ ├── bootstrap-grid.min.css │ │ │ │ ├── bootstrap-reboot.min.css │ │ │ │ └── bootstrap.min.css │ │ │ └── js │ │ │ │ └── bootstrap.min.js │ │ │ ├── css │ │ │ └── style.css │ │ │ ├── images │ │ │ └── favicon.ico │ │ │ └── js │ │ │ ├── jquery-3.2.1.min.js │ │ │ ├── popper.min.js │ │ │ └── reload.js │ ├── tasks.py │ ├── templates │ │ └── core │ │ │ ├── base.html │ │ │ ├── index.html │ │ │ └── index_probe.html │ ├── templatetags │ │ ├── __init__.py │ │ ├── status.py │ │ └── version.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_api.py │ │ ├── test_models.py │ │ ├── test_ssh.py │ │ ├── test_utils.py │ │ └── test_views.py │ ├── urls.py │ ├── utils.py │ └── views.py ├── manage.py ├── probemanager │ ├── __init__.py │ ├── celery.py │ ├── fixtures │ │ ├── crontab.json │ │ └── init.json │ ├── settings │ │ ├── __init__.py │ │ ├── base.py │ │ ├── dev.py │ │ └── prod.py │ ├── urls.py │ └── wsgi.py ├── rules │ ├── README.rst │ ├── __init__.py │ ├── apps.py │ ├── fixtures │ │ ├── test-rules-rule.json │ │ ├── test-rules-ruleset.json │ │ └── test-rules-source.json │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── templates │ │ └── rules │ │ │ └── search.html │ ├── tests │ │ ├── __init__.py │ │ ├── test_models.py │ │ └── test_views.py │ ├── urls.py │ └── views.py ├── runtests.py ├── scripts │ ├── __init__.py │ ├── apache.py │ ├── db_password.py │ ├── generate_doc.py │ ├── remove_in_file.py │ ├── secrets.py │ ├── setup_smtp.py │ ├── setup_tests.py │ ├── utilities.py │ └── version.py └── templates │ └── admin │ ├── base.html │ ├── base_site.html │ └── index.html ├── requirements ├── base.txt ├── dev.txt ├── os │ ├── debian.txt │ ├── debian_prod.txt │ ├── osx.txt │ └── osx_prod.txt ├── prod.txt └── test.txt ├── secrets.tar.enc ├── start.sh ├── stop_celery.sh └── test.sh /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | plugins = 3 | django_coverage_plugin 4 | [report] 5 | omit = */tests/* 6 | venv/* 7 | /home/travis/virtualenv/* 8 | */apps.py 9 | probemanager/probemanager/celery.py 10 | probemanager/probemanager/settings/* 11 | probemanager/manage.py 12 | probemanager/runtests.py 13 | */migrations/* 14 | */__init__.py 15 | */settings.py 16 | suricata-*/* 17 | [html] 18 | title = "ProbeManager Coverage report" 19 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | [*.{py,rst,ini}] 10 | indent_size = 4 11 | indent_style = space 12 | 13 | [*.{html,css,scss,json,yml}] 14 | indent_size = 2 15 | indent_style = space 16 | 17 | [*.md] 18 | trim_trailing_whitespace = false 19 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | exclude = 4 | .git, 5 | __pycache__, 6 | docs/, 7 | venv/, 8 | htmlcov/, 9 | manage.py, 10 | suricata-*/*, 11 | */migrations/*, 12 | */tests/*, 13 | probemanager/probemanager/settings/*, 14 | max-complexity = 30 15 | #E501 line too long 16 | #E402 Not importing files at top of the file 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # Jupyter Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # SageMath parsed files 81 | *.sage.py 82 | 83 | # dotenv 84 | .env 85 | 86 | # virtualenv 87 | .venv 88 | venv/ 89 | ENV/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | ### JetBrains template 98 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 99 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 100 | 101 | # User-specific stuff: 102 | .idea/**/workspace.xml 103 | .idea/**/tasks.xml 104 | .idea/dictionaries 105 | 106 | # Sensitive or high-churn files: 107 | .idea/**/dataSources/ 108 | .idea/**/dataSources.ids 109 | .idea/**/dataSources.xml 110 | .idea/**/dataSources.local.xml 111 | .idea/**/sqlDataSources.xml 112 | .idea/**/dynamic.xml 113 | .idea/**/uiDesigner.xml 114 | .idea/dbnavigator.xml 115 | 116 | # Gradle: 117 | .idea/**/gradle.xml 118 | .idea/**/libraries 119 | 120 | # CMake 121 | cmake-build-debug/ 122 | cmake-build-release/ 123 | 124 | # Mongo Explorer plugin: 125 | .idea/**/mongoSettings.xml 126 | 127 | ## File-based project format: 128 | *.iws 129 | 130 | ## Plugin-specific files: 131 | 132 | # IntelliJ 133 | out/ 134 | 135 | # mpeltonen/sbt-idea plugin 136 | .idea_modules/ 137 | 138 | # JIRA plugin 139 | atlassian-ide-plugin.xml 140 | 141 | # Cursive Clojure plugin 142 | .idea/replstate.xml 143 | 144 | # Crashlytics plugin (for Android Studio and IntelliJ) 145 | com_crashlytics_export_strings.xml 146 | crashlytics.properties 147 | crashlytics-build.properties 148 | fabric.properties 149 | 150 | 151 | ### Specific Project 152 | probemanager.db 153 | probemanager/celery.pid 154 | probemanager/celerybeat-schedule.db 155 | geckodriver.log 156 | migrations/* 157 | probemanager/bro/migrations/0* 158 | core/migrations/0* 159 | probemanager/rules/migrations/0* 160 | probemanager/Probe_Manager.egg-info 161 | probemanager/build 162 | probemanager/static/ 163 | /probemanager/probemanager/fixtures/test-suricata-probe.json 164 | /probemanager/ssh_keys/ 165 | /probemanager/pcap_success/ 166 | /conf.ini 167 | /progress.json 168 | /probemanager/tmp/ 169 | /docs/modules/*.rst 170 | /probemanager/version.txt 171 | .coveralls.yml 172 | /core/fixtures/init-test.json 173 | /probemanager/core/version.txt 174 | /probemanager/core/migrations/0* 175 | /probemanager/core/fixtures/test-core-secrets.json 176 | /probemanager/core/fixtures/test-core-secrets.ini 177 | secrets.tar 178 | /probemanager/file_test_success/ 179 | /erl_crash.dump 180 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "suricata"] 2 | path = probemanager/suricata 3 | url = https://github.com/treussart/ProbeManager_Suricata.git 4 | branch = master 5 | [submodule "bro"] 6 | path = probemanager/bro 7 | url = https://github.com/treussart/ProbeManager_Bro.git 8 | branch = master 9 | [submodule "ossec"] 10 | path = probemanager/ossec 11 | url = https://github.com/treussart/ProbeManager_Ossec.git 12 | branch = master 13 | [submodule "checkcve"] 14 | path = probemanager/checkcve 15 | url = https://github.com/treussart/ProbeManager_CheckCVE.git 16 | branch = master 17 | -------------------------------------------------------------------------------- /.idea/ProbeManager.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 26 | 27 | 28 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /.idea/deployment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 99 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 10 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | notifications: 2 | email: 3 | recipients: 4 | - matthieu@treussart.com 5 | on_success: change 6 | on_failure: always 7 | language: python 8 | python: 9 | - '3.5' 10 | - '3.6' 11 | env: 12 | global: 13 | - MOZ_HEADLESS=1 14 | - SURICATA_VERSION="4.0.4" 15 | - BRO_VERSION="2.5.3" 16 | - OSSEC_VERSION="2.9.3" 17 | os: 18 | - linux 19 | sudo: required 20 | branches: 21 | only: 22 | - master 23 | - develop 24 | services: 25 | - postgresql 26 | - rabbitmq 27 | install: 28 | - "./install.sh prod ." 29 | script: 30 | - "./test.sh" 31 | before_install: 32 | - mkdir probemanager/ssh_keys 33 | - openssl aes-256-cbc -K $encrypted_0112b2c0954f_key -iv $encrypted_0112b2c0954f_iv 34 | -in secrets.tar.enc -out secrets.tar -d 35 | - tar xvf secrets.tar 36 | before_script: 37 | - export DISPLAY=:99.0 38 | - sh -e /etc/init.d/xvfb start 39 | - sleep 3 40 | addons: 41 | firefox: latest 42 | deploy: 43 | provider: pages 44 | local-dir: docs/_build/html 45 | skip-cleanup: true 46 | github-token: "$GITHUB_TOKEN" 47 | keep-history: false 48 | target-branch: gh-pages 49 | on: 50 | branch: master 51 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Changelog 3 | ========= 4 | 5 | All notable changes to this project will be documented in this file. 6 | 7 | [Unreleased] 8 | ------------ 9 | 10 | [1.2.0] - 2018-04-30 11 | -------------------- 12 | 13 | Added 14 | ~~~~~ 15 | 16 | - Add Classtype to Suricata. 17 | 18 | Changed 19 | ~~~~~~~ 20 | 21 | - Improved API. 22 | - Improved Documentation. 23 | 24 | Removed 25 | ~~~~~~~ 26 | 27 | - Remove Classtype from rules 28 | 29 | 30 | [1.1.0] - 2018-04-19 31 | -------------------- 32 | 33 | Added 34 | ~~~~~ 35 | 36 | - Bro IDS. 37 | - Import rules from MISP in Suricata 38 | 39 | Changed 40 | ~~~~~~~ 41 | 42 | - Refactoring sources 43 | 44 | Removed 45 | ~~~~~~~ 46 | 47 | 48 | [1.0.0] - 2018-04-06 49 | -------------------- 50 | 51 | Added 52 | ~~~~~ 53 | 54 | - Suricata IDS. 55 | - CheckCVE. 56 | 57 | Changed 58 | ~~~~~~~ 59 | 60 | - Increase Tests 61 | 62 | Removed 63 | ~~~~~~~ 64 | 65 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at matthieu@treussart.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTORS.txt: -------------------------------------------------------------------------------- 1 | Treussart Matthieu 2 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ############ 2 | ProbeManager 3 | ############ 4 | 5 | .. image:: https://www.ko-fi.com/img/donate_sm.png 6 | :alt: Donate 7 | :target: https://ko-fi.com/mtreussart 8 | 9 | |Licence| |Version| 10 | 11 | 12 | .. image:: https://api.codacy.com/project/badge/Grade/afc2ab5226584ac3b594eb09ebcc2ccc?branch=master 13 | :alt: Codacy Grade 14 | :target: https://app.codacy.com/app/treussart/ProbeManager?utm_source=github.com&utm_medium=referral&utm_content=treussart/ProbeManager&utm_campaign=badger 15 | 16 | .. image:: https://api.codacy.com/project/badge/Coverage/8c16c475964d4db58ce0c7de0d03abbf?branch=master 17 | :alt: Codacy Coverage 18 | :target: https://www.codacy.com/app/treussart/ProbeManager?utm_source=github.com&utm_medium=referral&utm_content=treussart/ProbeManager&utm_campaign=Badge_Coverage 19 | 20 | +------------------+--------------------+ 21 | | Status | Operating system | 22 | +==================+====================+ 23 | | |Build_Status| | Linux x86\_64 | 24 | +------------------+--------------------+ 25 | 26 | .. |Licence| image:: https://img.shields.io/github/license/treussart/ProbeManager.svg 27 | .. |Stars| image:: https://img.shields.io/github/stars/treussart/ProbeManager.svg 28 | .. |Forks| image:: https://img.shields.io/github/forks/treussart/ProbeManager.svg 29 | .. |Downloads| image:: https://img.shields.io/github/downloads/treussart/ProbeManager/total.svg 30 | .. |Version| image:: https://img.shields.io/github/tag/treussart/ProbeManager.svg 31 | .. |Commits| image:: https://img.shields.io/github/commits-since/treussart/ProbeManager/latest.svg 32 | .. |Build_Status| image:: https://travis-ci.org/treussart/ProbeManager.svg?branch=master 33 | :target: https://travis-ci.org/treussart/ProbeManager 34 | 35 | Presentation 36 | ============ 37 | 38 | It is common to see that many IDS (intrusion and detection system), including the software and its rules are not updated regularly. This can be explained by the fact the software and rule management is often complicated, which can be a particular problem for small and medium sized enterprises that normally lack system security expertise and full time operators to supervise their respective IDS. This finding encouraged me to develop an application (ProbeManager) that will better manage network and machine detection probes on a system. 39 | 40 | ProbeManager is an application that centralizes the management of intrusion detection systems. The purpose of ProbeManager is to simplify the deployment of detection probes and to put together all of their functionalities in one single place. ProbeManager also allows you to check the status of the probes and to be notified whenever there is a problem or dysfunction. ProbeManager is not a SIEM (security information and event management), therefore, it doesn’t display the probe outputs (alerts, logs, etc…) 41 | 42 | ProbeManager is currently compatible with NIDS Suricata and Bro, and it will soon also be compatible with OSSEC. 43 | 44 | Features 45 | -------- 46 | 47 | * Search rules in all probes. 48 | * List installed probes and their status (Running or not, uptime ...). 49 | * Install, update probe. 50 | * Start, stop, reload and restart probe. 51 | * Push, Email notifications (change of status, ...). 52 | * API Restfull. 53 | * See all asynchronous jobs. 54 | 55 | Usage 56 | ===== 57 | 58 | .. image:: https://raw.githubusercontent.com/treussart/ProbeManager/master/docs/data/Deployement_example_of_Probemanager_in_a_network.png 59 | :alt: Deployement example of Probemanager in a network 60 | 61 | .. image:: https://raw.githubusercontent.com/treussart/ProbeManager/master/docs/data/Deployement_example_of_Probemanager_in_a_VPS.png 62 | :alt: Deployement example of Probemanager in a VPS 63 | 64 | Installation 65 | ============ 66 | 67 | Operating System 68 | ---------------- 69 | 70 | +------------+------------+-----------+ 71 | | OS | prod | test | 72 | +============+============+===========+ 73 | | OSX 12+ | | X | 74 | +------------+------------+-----------+ 75 | | Debian 9 | X | | 76 | +------------+------------+-----------+ 77 | | Ubuntu 14 | X | | 78 | +------------+------------+-----------+ 79 | 80 | OSX 12+ (Only for project development), Debian stable and Ubuntu 14.04+ are Supported and tested. 81 | 82 | Requirements 83 | ------------ 84 | 85 | - Python3.5+ 86 | - Pip 87 | - Rabbitmq-server (installed with install script) 88 | - Postgresql (installed with install script) 89 | 90 | Retrieve the project 91 | -------------------- 92 | 93 | `Source code on Github `_ 94 | 95 | .. code-block:: sh 96 | 97 | git clone --recursive https://github.com/treussart/ProbeManager.git 98 | 99 | Install 100 | ------- 101 | 102 | For developer : 103 | ^^^^^^^^^^^^^^^ 104 | 105 | .. code-block:: sh 106 | 107 | ./install.sh 108 | ./start.sh 109 | 110 | For Production : 111 | ^^^^^^^^^^^^^^^^ 112 | 113 | Default destination path : /usr/local/share 114 | 115 | For same destination path : . 116 | 117 | Be sure to have the write rights in the destination path. 118 | 119 | .. code-block:: sh 120 | 121 | ./install.sh prod [destination path] 122 | 123 | With Django server (not recommended) : 124 | 125 | .. code-block:: sh 126 | 127 | [destination path]./start.sh prod 128 | 129 | With Apache (Only for Debian) : 130 | 131 | .. code-block:: sh 132 | 133 | http://localhost 134 | 135 | Launch the tests 136 | ---------------- 137 | 138 | (Only for Dev or Travis) : 139 | 140 | .. code-block:: sh 141 | 142 | ./test.sh 143 | 144 | 145 | Open the file with a web browser : 146 | 147 | :: 148 | 149 | coverage_html/index.html 150 | 151 | 152 | Add a submodule 153 | =============== 154 | 155 | .. code-block:: sh 156 | 157 | git submodule add -b master --name suricata https://github.com/treussart/ProbeManager_Suricata.git probemanager/suricata 158 | 159 | Modules must respect a few rules: 160 | 161 | * A file version.txt (generated by install script) 162 | * A file README.rst 163 | * A folder api with a variable 'urls_to_register' into urls.py (Optional) 164 | * An install script : install.sh (Optional) 165 | * A script for initializing the database : init_db.sh (Optional) 166 | 167 | 168 | Documentation 169 | ============= 170 | 171 | 172 | Respect standard : reStructuredText (RST). 173 | 174 | .. code-block:: sh 175 | 176 | venv/bin/python probemanager/manage.py runscript generate_doc --settings=probemanager.settings.dev 177 | 178 | 179 | Open the file with a web browser : 180 | 181 | :: 182 | 183 | docs/_build/html/index.html 184 | 185 | Or retrieve the full documentation `here `_ 186 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = probemanager 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/_static/.init: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treussart/ProbeManager/afef59036ca7e279a9125b226721fe3d237f04be/docs/_static/.init -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CHANGELOG.rst 2 | 3 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import re 4 | import datetime 5 | 6 | 7 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 8 | import django 9 | django.setup() 10 | from django.conf import settings 11 | 12 | 13 | def get_infos(): 14 | filename = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)) + \ 15 | '/probemanager/probemanager/__init__.py' 16 | with open(filename) as fh: 17 | metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", fh.read())) 18 | return metadata 19 | 20 | 21 | # -- General configuration ------------------------------------------------ 22 | 23 | last_reviewed = datetime.date.today().strftime('%d, %b %Y') 24 | rst_epilog = '.. |last_reviewed| replace:: %s' % last_reviewed 25 | 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | # 29 | # needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = ['sphinx.ext.autodoc', 35 | 'sphinx.ext.todo', 36 | 'sphinx.ext.viewcode', 37 | 'sphinx.ext.extlinks', 38 | 'sphinx.ext.autosummary'] 39 | 40 | # Add any paths that contain templates here, relative to this directory. 41 | templates_path = ['_templates'] 42 | 43 | # The suffix(es) of source filenames. 44 | # You can specify multiple suffix as a list of string: 45 | # 46 | # source_suffix = ['.rst', '.md'] 47 | source_suffix = '.rst' 48 | 49 | # The master toctree document. 50 | master_doc = 'index' 51 | 52 | # General information about the project. 53 | project = get_infos()['title'] 54 | copyright = get_infos()['licence'] 55 | author = get_infos()['author'] 56 | 57 | # The version info for the project you're documenting, acts as replacement for 58 | # |version| and |release|, also used in various other places throughout the 59 | # built documents. 60 | # 61 | # The short X.Y version. 62 | version = settings.VERSION 63 | 64 | # The full version, including alpha/beta/rc tags. 65 | release = version 66 | 67 | # The language for content autogenerated by Sphinx. Refer to documentation 68 | # for a list of supported languages. 69 | # 70 | # This is also used if you do content translation via gettext catalogs. 71 | # Usually you set "language" from the command line for these cases. 72 | language = None 73 | 74 | # List of patterns, relative to source directory, that match files and 75 | # directories to ignore when looking for source files. 76 | # This patterns also effect to html_static_path and html_extra_path 77 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 78 | 79 | # The name of the Pygments (syntax highlighting) style to use. 80 | pygments_style = 'sphinx' 81 | 82 | # If true, `todo` and `todoList` produce output, else they produce nothing. 83 | todo_include_todos = True 84 | 85 | 86 | # -- Options for HTML output ---------------------------------------------- 87 | 88 | # The theme to use for HTML and HTML Help pages. See the documentation for 89 | # a list of builtin themes. 90 | # 91 | html_theme = 'sphinx_rtd_theme' 92 | 93 | html_theme_path = ["_themes", ] 94 | 95 | # Theme options are theme-specific and customize the look and feel of a theme 96 | # further. For a list of options available for each theme, see the 97 | # documentation. 98 | # 99 | # html_theme_options = {} 100 | 101 | # Add any paths that contain custom static files (such as style sheets) here, 102 | # relative to this directory. They are copied after the builtin static files, 103 | # so a file named "default.css" will overwrite the builtin "default.css". 104 | html_static_path = ['_static'] 105 | html_favicon = 'data/favicon.ico' 106 | 107 | # -- Options for HTMLHelp output ------------------------------------------ 108 | 109 | # Output file base name for HTML help builder. 110 | htmlhelp_basename = 'probemanagerdoc' 111 | 112 | # -- Options for LaTeX output --------------------------------------------- 113 | 114 | latex_elements = { 115 | # The paper size ('letterpaper' or 'a4paper'). 116 | # 117 | # 'papersize': 'letterpaper', 118 | 119 | # The font size ('10pt', '11pt' or '12pt'). 120 | # 121 | # 'pointsize': '10pt', 122 | 123 | # Additional stuff for the LaTeX preamble. 124 | # 125 | # 'preamble': '', 126 | 127 | # Latex figure (float) alignment 128 | # 129 | # 'figure_align': 'htbp', 130 | } 131 | 132 | # Grouping the document tree into LaTeX files. List of tuples 133 | # (source start file, target name, title, 134 | # author, documentclass [howto, manual, or own class]). 135 | latex_documents = [ 136 | (master_doc, 'probemanager.tex', u'Probe Manager Documentation', 137 | u'BSD', 'manual'), 138 | ] 139 | 140 | 141 | # -- Options for manual page output --------------------------------------- 142 | 143 | # One entry per manual page. List of tuples 144 | # (source start file, name, description, authors, manual section). 145 | man_pages = [ 146 | (master_doc, 'probemanager', u'Probe Manager Documentation', 147 | [author], 1) 148 | ] 149 | 150 | 151 | # -- Options for Texinfo output ------------------------------------------- 152 | 153 | # Grouping the document tree into Texinfo files. List of tuples 154 | # (source start file, target name, title, author, 155 | # dir menu entry, description, category) 156 | texinfo_documents = [ 157 | (master_doc, 'probemanager', u'Probe Manager Documentation', 158 | author, 'probemanager', 'One line description of project.', 159 | 'Miscellaneous'), 160 | ] 161 | 162 | html_theme_options = { 163 | "collapse_navigation": True 164 | } 165 | 166 | suppress_warnings = ['image.nonlocal_uri'] 167 | 168 | # Warn about all references where the target cannot be found 169 | nitpicky = False 170 | -------------------------------------------------------------------------------- /docs/data/Deployement_example_of_Probemanager_in_a_VPS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treussart/ProbeManager/afef59036ca7e279a9125b226721fe3d237f04be/docs/data/Deployement_example_of_Probemanager_in_a_VPS.png -------------------------------------------------------------------------------- /docs/data/Deployement_example_of_Probemanager_in_a_network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treussart/ProbeManager/afef59036ca7e279a9125b226721fe3d237f04be/docs/data/Deployement_example_of_Probemanager_in_a_network.png -------------------------------------------------------------------------------- /docs/data/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treussart/ProbeManager/afef59036ca7e279a9125b226721fe3d237f04be/docs/data/favicon.ico -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | 2 | Welcome to ProbeManager's documentation! 3 | ======================================== 4 | 5 | :Last Rewiewed: |last_reviewed| 6 | 7 | 8 | .. toctree:: 9 | :maxdepth: 2 10 | :caption: ProbeManager 11 | 12 | readme 13 | changelog 14 | 15 | .. toctree:: 16 | :maxdepth: 2 17 | :caption: Internals modules 18 | 19 | internal/index 20 | 21 | .. toctree:: 22 | :maxdepth: 2 23 | :caption: Modules 24 | 25 | modules/index 26 | 27 | 28 | Indices and tables 29 | ================== 30 | 31 | * :ref:`genindex` 32 | * :ref:`modindex` 33 | * :ref:`search` 34 | -------------------------------------------------------------------------------- /docs/internal/core.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../probemanager/core/README.rst 2 | -------------------------------------------------------------------------------- /docs/internal/index.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | Rules 4 | ----- 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | rules 10 | 11 | Core 12 | ---- 13 | 14 | .. toctree:: 15 | :maxdepth: 2 16 | 17 | core 18 | -------------------------------------------------------------------------------- /docs/internal/rules.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../probemanager/rules/README.rst 2 | -------------------------------------------------------------------------------- /docs/modules/.init: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treussart/ProbeManager/afef59036ca7e279a9125b226721fe3d237f04be/docs/modules/.init -------------------------------------------------------------------------------- /docs/readme.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst -------------------------------------------------------------------------------- /merge-develop.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | git submodule foreach git checkout master 4 | git checkout master 5 | 6 | git submodule foreach git pull origin master 7 | git pull origin master 8 | 9 | git submodule foreach git merge develop 10 | git merge develop 11 | 12 | git submodule foreach git checkout develop 13 | git checkout develop 14 | 15 | git submodule foreach git pull origin develop 16 | git pull origin develop 17 | -------------------------------------------------------------------------------- /probemanager/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /probemanager/README.rst: -------------------------------------------------------------------------------- 1 | ../README.rst -------------------------------------------------------------------------------- /probemanager/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treussart/ProbeManager/afef59036ca7e279a9125b226721fe3d237f04be/probemanager/api/__init__.py -------------------------------------------------------------------------------- /probemanager/api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApiConfig(AppConfig): 5 | name = 'api' 6 | -------------------------------------------------------------------------------- /probemanager/api/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treussart/ProbeManager/afef59036ca7e279a9125b226721fe3d237f04be/probemanager/api/migrations/__init__.py -------------------------------------------------------------------------------- /probemanager/api/pagination.py: -------------------------------------------------------------------------------- 1 | from rest_framework.pagination import LimitOffsetPagination 2 | 3 | 4 | class StandardResultsSetPagination(LimitOffsetPagination): 5 | default_limit = 20 6 | max_limit = 100 7 | -------------------------------------------------------------------------------- /probemanager/api/serializers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User, Group 2 | from django_celery_beat.models import PeriodicTask, CrontabSchedule 3 | from rest_framework import serializers 4 | 5 | from core.models import Server, SshKey, Configuration, Job 6 | 7 | 8 | class UserSerializer(serializers.ModelSerializer): 9 | class Meta: 10 | model = User 11 | fields = ('url', 'username', 'email', 'groups') 12 | 13 | 14 | class GroupSerializer(serializers.ModelSerializer): 15 | class Meta: 16 | model = Group 17 | fields = ('url', 'name') 18 | 19 | 20 | class PeriodicTaskSerializer(serializers.ModelSerializer): 21 | class Meta: 22 | model = PeriodicTask 23 | fields = "__all__" 24 | 25 | 26 | class CrontabScheduleSerializer(serializers.ModelSerializer): 27 | class Meta: 28 | model = CrontabSchedule 29 | fields = "__all__" 30 | 31 | 32 | class ServerSerializer(serializers.ModelSerializer): 33 | class Meta: 34 | model = Server 35 | fields = "__all__" 36 | 37 | 38 | class SshKeySerializer(serializers.ModelSerializer): 39 | class Meta: 40 | model = SshKey 41 | fields = "__all__" 42 | 43 | 44 | class ConfigurationSerializer(serializers.ModelSerializer): 45 | class Meta: 46 | model = Configuration 47 | fields = "__all__" 48 | 49 | 50 | class ConfigurationUpdateSerializer(serializers.ModelSerializer): 51 | class Meta: 52 | model = Configuration 53 | fields = ('value', ) 54 | 55 | 56 | class JobSerializer(serializers.ModelSerializer): 57 | class Meta: 58 | model = Job 59 | fields = "__all__" 60 | -------------------------------------------------------------------------------- /probemanager/api/urls.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | import os 3 | 4 | from django.apps.registry import apps 5 | from django.conf import settings 6 | from rest_framework import routers 7 | 8 | from . import views 9 | 10 | router = routers.DefaultRouter() 11 | 12 | router.register(r'^admin/users', views.UserViewSet) 13 | router.register(r'^admin/groups', views.GroupViewSet) 14 | router.register(r'^core/server', views.ServerViewSet, base_name="core") 15 | router.register(r'^core/sshkey', views.SshKeyViewSet, base_name="core") 16 | router.register(r'^core/configuration', views.ConfigurationViewSet, base_name="core") 17 | router.register(r'^core/job', views.JobViewSet, base_name="core") 18 | router.register(r'^celerybeat/crontabschedule', views.CrontabScheduleViewSet) 19 | router.register(r'^celerybeat/periodictask', views.PeriodicTaskViewSet) 20 | 21 | 22 | for app in apps.get_app_configs(): 23 | path = settings.BASE_DIR + "/" + app.label + "/api/urls.py" 24 | if os.path.isfile(path): 25 | my_attr = getattr(importlib.import_module(app.label + ".api.urls"), 'urls_to_register') 26 | for url in my_attr: 27 | router.register(*url, base_name=app.label) 28 | 29 | urlpatterns = list() 30 | urlpatterns += router.urls 31 | -------------------------------------------------------------------------------- /probemanager/api/views.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.conf import settings 4 | from django.contrib.auth.models import User, Group 5 | from django_celery_beat.models import PeriodicTask, CrontabSchedule 6 | from rest_framework import mixins 7 | from rest_framework import status 8 | from rest_framework import viewsets 9 | from rest_framework.response import Response 10 | from rest_framework.decorators import action 11 | 12 | from core.models import Server, SshKey, Configuration, Job 13 | from .serializers import UserSerializer, GroupSerializer, CrontabScheduleSerializer, \ 14 | PeriodicTaskSerializer, ServerSerializer, SshKeySerializer, ConfigurationSerializer, \ 15 | ConfigurationUpdateSerializer, JobSerializer 16 | 17 | 18 | class UserViewSet(viewsets.ModelViewSet): 19 | """ 20 | API endpoint that allows users to be viewed or edited. 21 | """ 22 | queryset = User.objects.all().order_by('-date_joined') 23 | serializer_class = UserSerializer 24 | 25 | 26 | class GroupViewSet(viewsets.ModelViewSet): 27 | """ 28 | API endpoint that allows groups to be viewed or edited. 29 | """ 30 | queryset = Group.objects.all() 31 | serializer_class = GroupSerializer 32 | 33 | 34 | class PeriodicTaskViewSet(viewsets.ModelViewSet): 35 | queryset = PeriodicTask.objects.all() 36 | serializer_class = PeriodicTaskSerializer 37 | 38 | 39 | class CrontabScheduleViewSet(viewsets.ModelViewSet): 40 | queryset = CrontabSchedule.objects.all() 41 | serializer_class = CrontabScheduleSerializer 42 | 43 | 44 | class ServerViewSet(viewsets.ModelViewSet): 45 | queryset = Server.objects.all() 46 | serializer_class = ServerSerializer 47 | 48 | @action(detail=True) 49 | def test_connection(self, request, pk=None): 50 | obj = self.get_object() 51 | if obj.become: 52 | response = obj.test_become() 53 | else: 54 | response = obj.test() 55 | return Response(response) 56 | 57 | 58 | class ConfigurationViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet): 59 | queryset = Configuration.objects.all() 60 | serializer_class = ConfigurationSerializer 61 | 62 | def update(self, request, pk=None): 63 | return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED) 64 | 65 | def partial_update(self, request, pk=None): 66 | conf = self.get_object() 67 | serializer = ConfigurationUpdateSerializer(conf, data=request.data) 68 | if serializer.is_valid(): 69 | serializer.save() 70 | return Response(serializer.data) 71 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 72 | 73 | 74 | class SshKeyViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.DestroyModelMixin, 75 | viewsets.GenericViewSet): 76 | queryset = SshKey.objects.all() 77 | serializer_class = SshKeySerializer 78 | 79 | def create(self, request): 80 | with open(settings.BASE_DIR + "/ssh_keys/" + request.data['name'], 'w', encoding="utf_8") as f: 81 | f.write(request.data['file']) 82 | os.chmod(settings.BASE_DIR + "/ssh_keys/" + request.data['name'], 0o640) 83 | sshkey = SshKey(name=request.data['name'], file="ssh_keys/" + request.data['name']) 84 | sshkey.save() 85 | return Response(status=204) 86 | 87 | 88 | class JobViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.DestroyModelMixin, viewsets.GenericViewSet): 89 | queryset = Job.objects.all() 90 | serializer_class = JobSerializer 91 | -------------------------------------------------------------------------------- /probemanager/core/README.rst: -------------------------------------------------------------------------------- 1 | **** 2 | Core 3 | **** 4 | 5 | Presentation 6 | ============ 7 | 8 | Application for general things about project and probe. 9 | 10 | Features 11 | -------- 12 | 13 | * Jobs. To see the results of asynchronous tasks. 14 | * List of the operating systems supported by this application. 15 | * Server, remote server. 16 | * Ssh Key, to authenticate on the remote server. 17 | * General configuration of this application. (Pushbullet API KEY, MISP API KEY, SPLUNK HOST ...) 18 | * Generic Probe. 19 | * Generic Probe configuration. 20 | 21 | Usage 22 | ===== 23 | 24 | Administration Page of the module : 25 | ----------------------------------- 26 | 27 | .. image:: https://raw.githubusercontent.com/treussart/ProbeManager/master/probemanager/core/data/admin-index.png 28 | :align: center 29 | :width: 80% 30 | 31 | Page to add a remote server : 32 | ----------------------------- 33 | 34 | .. image:: https://raw.githubusercontent.com/treussart/ProbeManager/master/probemanager/core/data/admin-server-add.png 35 | :align: center 36 | :width: 70% 37 | 38 | * Name: Give a unique name for this server, example: server-tap1. 39 | * Host: The IP address or Hostname. 40 | * Os: The Operating system (Debian or Ubuntu). 41 | * Remote User: The user used for the connection 'ssh user@hostname' 42 | * Remote port: The remote port 'ssh -p 22'. 43 | * Become: True or False, if the user needs to elevate his privileges to be root. 44 | * Become method: 45 | * Become user: Often root, but you can use another user with fewer privileges than root. 46 | * Become pass: The password for the user you will want to become, if necessary. 47 | * Ssh private key file: The private key file to authenticate (not encrypted, the tool will encrypt it). 48 | 49 | 50 | Page to add a SSH Key : 51 | ----------------------- 52 | 53 | .. image:: https://raw.githubusercontent.com/treussart/ProbeManager/master/probemanager/core/data/admin-sshkey-add.png 54 | :align: center 55 | :width: 60% 56 | 57 | * Name: A name for this key. (In the API, the name is the name of the file) 58 | * File: The file to upload. (In the API, the file is the text file of the private key) 59 | 60 | The SSH key must not be encrypted, the tool will encrypt it with the secret key of Django. 61 | 62 | Page to see results of asynchronous jobs : 63 | ------------------------------------------ 64 | 65 | .. image:: https://raw.githubusercontent.com/treussart/ProbeManager/master/probemanager/core/data/admin-jobs-full.png 66 | :align: center 67 | :width: 90% 68 | -------------------------------------------------------------------------------- /probemanager/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treussart/ProbeManager/afef59036ca7e279a9125b226721fe3d237f04be/probemanager/core/__init__.py -------------------------------------------------------------------------------- /probemanager/core/admin.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from django.contrib import admin 4 | from django.contrib import messages 5 | from django_celery_beat.models import SolarSchedule, IntervalSchedule 6 | 7 | from .forms import ServerForm 8 | from .models import SshKey, Server, Job, Configuration 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | class ServerAdmin(admin.ModelAdmin): 14 | form = ServerForm 15 | 16 | def save_model(self, request, obj, form, change): 17 | super().save_model(request, obj, form, change) 18 | if obj.become: 19 | response = obj.test_become() 20 | else: 21 | response = obj.test() 22 | if response['status']: 23 | messages.add_message(request, messages.SUCCESS, "Connection to the server OK") 24 | else: 25 | messages.add_message(request, messages.ERROR, "Connection to the server Failed") 26 | 27 | 28 | class JobAdmin(admin.ModelAdmin): 29 | list_filter = ('status', 'completed', 'probe') 30 | list_display = ('name', 'probe', 'status', 'created', 'completed', 'result') 31 | list_display_links = None 32 | 33 | class Media: 34 | js = ( 35 | 'core/js/reload.js', 36 | ) 37 | 38 | def has_add_permission(self, request): 39 | return False 40 | 41 | 42 | admin.site.register(SshKey) 43 | admin.site.register(Server, ServerAdmin) 44 | admin.site.register(Job, JobAdmin) 45 | admin.site.register(Configuration) 46 | 47 | admin.site.unregister(SolarSchedule) 48 | admin.site.unregister(IntervalSchedule) 49 | -------------------------------------------------------------------------------- /probemanager/core/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CoreConfig(AppConfig): 5 | name = 'core' 6 | -------------------------------------------------------------------------------- /probemanager/core/data/admin-index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treussart/ProbeManager/afef59036ca7e279a9125b226721fe3d237f04be/probemanager/core/data/admin-index.png -------------------------------------------------------------------------------- /probemanager/core/data/admin-jobs-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treussart/ProbeManager/afef59036ca7e279a9125b226721fe3d237f04be/probemanager/core/data/admin-jobs-full.png -------------------------------------------------------------------------------- /probemanager/core/data/admin-jobs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treussart/ProbeManager/afef59036ca7e279a9125b226721fe3d237f04be/probemanager/core/data/admin-jobs.png -------------------------------------------------------------------------------- /probemanager/core/data/admin-server-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treussart/ProbeManager/afef59036ca7e279a9125b226721fe3d237f04be/probemanager/core/data/admin-server-add.png -------------------------------------------------------------------------------- /probemanager/core/data/admin-sshkey-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treussart/ProbeManager/afef59036ca7e279a9125b226721fe3d237f04be/probemanager/core/data/admin-sshkey-add.png -------------------------------------------------------------------------------- /probemanager/core/exceptions.py: -------------------------------------------------------------------------------- 1 | class ProbeManagerError(Exception): 2 | def __init__(self, message): 3 | super().__init__(message) 4 | self.message = message 5 | -------------------------------------------------------------------------------- /probemanager/core/fixtures/test-core-probe.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "core.probe", 4 | "pk": 1, 5 | "fields": { 6 | "name": "probe1", 7 | "description": "test", 8 | "created_date": "2017-09-23T21:00:53.094Z", 9 | "rules_updated_date": null, 10 | "type": "", 11 | "secure_deployment": true, 12 | "scheduled_rules_deployment_enabled": true, 13 | "scheduled_rules_deployment_crontab": 1, 14 | "server": 1, 15 | "installed": true 16 | } 17 | } 18 | ] 19 | -------------------------------------------------------------------------------- /probemanager/core/fixtures/test-core-probeconfiguration.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "core.probeconfiguration", 4 | "pk": 1, 5 | "fields": { 6 | "name": "conf1" 7 | } 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /probemanager/core/fixtures/test-core-sshkey.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "core.sshkey", 4 | "pk": 1, 5 | "fields": { 6 | "name": "test", 7 | "file": "ssh_keys/test.com_rsa" 8 | } 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /probemanager/core/forms.py: -------------------------------------------------------------------------------- 1 | from django.forms import ModelForm, PasswordInput 2 | 3 | from .models import Server 4 | 5 | 6 | class ServerForm(ModelForm): 7 | class Meta: 8 | model = Server 9 | fields = ( 10 | 'name', 'host', 'os', 'remote_user', 'remote_port', 'become', 'become_method', 'become_user', 11 | 'become_pass', 'ssh_private_key_file') 12 | widgets = { 13 | 'become_pass': PasswordInput(), 14 | } 15 | -------------------------------------------------------------------------------- /probemanager/core/git.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import subprocess 3 | 4 | from django.conf import settings 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | 9 | def git_tag(git_root): 10 | command = [settings.GIT_BINARY, 'describe', '--tags', '--always'] 11 | command_date = [settings.GIT_BINARY, 'log', '-n 1', '--date=short', '--pretty=%ad'] 12 | p = subprocess.Popen(command, cwd=git_root, stdout=subprocess.PIPE, stderr=subprocess.PIPE, 13 | universal_newlines=True) 14 | out, err = p.communicate() 15 | if err: # pragma: no cover 16 | logger.exception(str(err)) 17 | p_date = subprocess.Popen(command_date, cwd=git_root, stdout=subprocess.PIPE, stderr=subprocess.PIPE, 18 | universal_newlines=True) 19 | out_date, err_date = p_date.communicate() 20 | out_date.replace('-', '.') 21 | if err_date: # pragma: no cover 22 | logger.exception(str(err_date)) 23 | return out.strip() + "-" + out_date.strip() 24 | -------------------------------------------------------------------------------- /probemanager/core/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treussart/ProbeManager/afef59036ca7e279a9125b226721fe3d237f04be/probemanager/core/migrations/__init__.py -------------------------------------------------------------------------------- /probemanager/core/models.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | 4 | from django.conf import settings 5 | from django.db import models 6 | from django.utils import timezone 7 | from django_celery_beat.models import CrontabSchedule 8 | from paramiko.rsakey import RSAKey 9 | 10 | from .modelsmixins import CommonMixin 11 | from .ssh import execute 12 | from .utils import encrypt 13 | 14 | logger = logging.getLogger(__name__) 15 | 16 | 17 | class Job(CommonMixin, models.Model): 18 | STATUS_CHOICES = ( 19 | ('In progress', 'In progress'), 20 | ('Completed', 'Completed'), 21 | ('Cancelled', 'Cancelled'), 22 | ('Error', 'Error'), 23 | ) 24 | name = models.CharField(max_length=255) 25 | probe = models.CharField(max_length=255, verbose_name="Probe / URL") 26 | status = models.CharField(max_length=255, choices=STATUS_CHOICES) 27 | result = models.TextField(null=True, default=None, editable=False) 28 | created = models.DateTimeField(default=timezone.now) 29 | completed = models.DateTimeField(null=True, blank=True) 30 | 31 | class Meta: 32 | ordering = ('-created',) 33 | 34 | def __str__(self): 35 | return str(self.name) 36 | 37 | @classmethod 38 | def create_job(cls, name, probe_name): 39 | job = Job(name=name, probe=probe_name, status='In progress', created=timezone.now()) 40 | job.save() 41 | return job 42 | 43 | def update_job(self, result, status): 44 | self.result = result 45 | self.status = status 46 | self.completed = timezone.now() 47 | self.save() 48 | 49 | 50 | class OsSupported(CommonMixin, models.Model): 51 | """ 52 | Set of operating system name. For now, just debian is available. 53 | """ 54 | name = models.CharField(max_length=100, unique=True) 55 | 56 | def __str__(self): 57 | return str(self.name) 58 | 59 | 60 | class SshKey(models.Model): 61 | """ 62 | Set of Ssh keys, To connect to the remote server. 63 | """ 64 | name = models.CharField(max_length=400, unique=True, blank=False, null=False) 65 | file = models.FileField(upload_to='ssh_keys/') 66 | 67 | def __str__(self): 68 | return str(self.name) 69 | 70 | def save(self, **kwargs): 71 | super().save(**kwargs) 72 | rsakey = RSAKey.from_private_key_file(settings.MEDIA_ROOT + "/" + self.file.name) 73 | rsakey.write_private_key_file(settings.MEDIA_ROOT + "/" + self.file.name, settings.SECRET_KEY) 74 | os.chmod(settings.MEDIA_ROOT + "/" + self.file.name, 0o640) 75 | 76 | 77 | class Server(CommonMixin, models.Model): 78 | """ 79 | Server on which is deployed the Probes. 80 | """ 81 | name = models.CharField(max_length=400, unique=True, default="") 82 | host = models.CharField(max_length=400, unique=True, default="localhost") 83 | os = models.ForeignKey(OsSupported, default=0, on_delete=models.CASCADE) 84 | remote_user = models.CharField(max_length=400, blank=True, default='admin') 85 | remote_port = models.IntegerField(blank=True, default=22) 86 | ssh_private_key_file = models.ForeignKey(SshKey, on_delete=models.CASCADE) 87 | become = models.BooleanField(default=False, blank=True) 88 | become_method = models.CharField(max_length=400, blank=True, default='sudo') 89 | become_user = models.CharField(max_length=400, blank=True, default='root') 90 | become_pass = models.CharField(max_length=400, blank=True, null=True) 91 | 92 | def __str__(self): 93 | return str(self.name) + ' - ' + str(self.host) + ', Os : ' + str(self.os) 94 | 95 | def save(self, **kwargs): 96 | if self.become_pass: 97 | self.become_pass = encrypt(self.become_pass) 98 | super().save(**kwargs) 99 | 100 | def test(self): 101 | command = "cat /etc/hostname" 102 | tasks = {"test_connection": command} 103 | try: 104 | response = execute(self, tasks) 105 | except Exception as e: 106 | logger.exception("Error during the connection to the server.") 107 | return {'status': False, 'errors': str(e)} 108 | else: 109 | logger.debug("output : " + str(response)) 110 | return {'status': True} 111 | 112 | def test_become(self): 113 | command = "service ssh status" 114 | tasks = {"test_connection_and_become": command} 115 | try: 116 | response = execute(self, tasks, become=True) 117 | except Exception as e: 118 | logger.exception("Error during the connection to the server.") 119 | return {'status': False, 'errors': str(e)} 120 | else: 121 | logger.debug("output : " + str(response)) 122 | return {'status': True} 123 | 124 | 125 | class Probe(CommonMixin, models.Model): 126 | """ 127 | A probe is an IDS. 128 | """ 129 | # General 130 | name = models.CharField(max_length=400, unique=True, blank=False, null=False) 131 | description = models.CharField(max_length=400, blank=True, default="") 132 | created_date = models.DateTimeField(default=timezone.now, editable=False) 133 | rules_updated_date = models.DateTimeField(blank=True, null=True, editable=False) 134 | type = models.CharField(max_length=400, blank=True, default='', editable=False) 135 | subtype = models.CharField(max_length=400, blank=True, null=True, editable=False) 136 | secure_deployment = models.BooleanField(default=True) 137 | scheduled_rules_deployment_enabled = models.BooleanField(default=False) 138 | scheduled_rules_deployment_crontab = models.ForeignKey(CrontabSchedule, related_name='crontabschedule_rules', 139 | blank=True, null=True, on_delete=models.CASCADE) 140 | scheduled_check_enabled = models.BooleanField(default=True) 141 | scheduled_check_crontab = models.ForeignKey(CrontabSchedule, related_name='crontabschedule_check', blank=True, 142 | null=True, on_delete=models.CASCADE) 143 | server = models.ForeignKey(Server, on_delete=models.CASCADE) 144 | installed = models.BooleanField('Probe Already installed', default=False) 145 | 146 | def __str__(self): 147 | return str(self.name) 148 | 149 | def uptime(self): 150 | if self.installed: 151 | if self.server.os.name == 'debian' or self.server.os.name == 'ubuntu': 152 | command = "service " + self.type.lower() + " status | grep since" 153 | else: # pragma: no cover 154 | raise NotImplementedError 155 | tasks = {"uptime": command} 156 | try: 157 | response = execute(self.server, tasks, become=True) 158 | except Exception as e: 159 | logger.exception("Error during the uptime") 160 | return 'Failed to get the uptime on the host : ' + str(e) 161 | else: 162 | logger.debug("output : " + str(response)) 163 | return response['uptime'] 164 | else: 165 | return 'Not installed' 166 | 167 | def restart(self): 168 | if self.server.os.name == 'debian' or self.server.os.name == 'ubuntu': 169 | command = "service " + self.__class__.__name__.lower() + " restart" 170 | else: # pragma: no cover 171 | raise NotImplementedError 172 | tasks = {"restart": command} 173 | try: 174 | response = execute(self.server, tasks, become=True) 175 | except Exception: 176 | logger.exception("Error during restart") 177 | return {'status': False, 'errors': "Error during restart"} 178 | else: 179 | logger.debug("output : " + str(response)) 180 | return {'status': True} 181 | 182 | def start(self): 183 | if self.server.os.name == 'debian' or self.server.os.name == 'ubuntu': 184 | command = "service " + self.__class__.__name__.lower() + " start" 185 | else: # pragma: no cover 186 | raise NotImplementedError 187 | tasks = {"start": command} 188 | try: 189 | response = execute(self.server, tasks, become=True) 190 | except Exception: 191 | logger.exception("Error during start") 192 | return {'status': False, 'errors': "Error during start"} 193 | else: 194 | logger.debug("output : " + str(response)) 195 | return {'status': True} 196 | 197 | def stop(self): 198 | if self.server.os.name == 'debian' or self.server.os.name == 'ubuntu': 199 | command = "service " + self.__class__.__name__.lower() + " stop" 200 | else: # pragma: no cover 201 | raise NotImplementedError 202 | tasks = {"stop": command} 203 | try: 204 | response = execute(self.server, tasks, become=True) 205 | except Exception: 206 | logger.exception("Error during stop") 207 | return {'status': False, 'errors': "Error during stop"} 208 | else: 209 | logger.debug("output : " + str(response)) 210 | return {'status': True} 211 | 212 | def status(self): 213 | if self.installed: 214 | if self.server.os.name == 'debian' or self.server.os.name == 'ubuntu': 215 | command = "service " + self.__class__.__name__.lower() + " status" 216 | else: # pragma: no cover 217 | raise NotImplementedError 218 | tasks = {"status": command} 219 | try: 220 | response = execute(self.server, tasks, become=True) 221 | except Exception: 222 | logger.exception('Failed to get status') 223 | return 'Failed to get status' 224 | else: 225 | logger.debug("output : " + str(response)) 226 | return response['status'] 227 | else: 228 | return 'Not installed' 229 | 230 | def reload(self): 231 | if self.server.os.name == 'debian' or self.server.os.name == 'ubuntu': 232 | command = "service " + self.__class__.__name__.lower() + " reload" 233 | else: # pragma: no cover 234 | raise NotImplementedError 235 | tasks = {"reload": command} 236 | try: 237 | response = execute(self.server, tasks, become=True) 238 | except Exception: 239 | logger.exception("Error during reload") 240 | return {'status': False, 'errors': "Error during reload"} 241 | else: 242 | logger.debug("output : " + str(response)) 243 | return {'status': True} 244 | 245 | @classmethod 246 | def get_by_name(cls, name): 247 | try: 248 | probe = cls.objects.get(name=name) 249 | except cls.DoesNotExist as e: 250 | logger.debug('Tries to access an object that does not exist : ' + str(e)) 251 | return None 252 | return probe 253 | 254 | 255 | class ProbeConfiguration(CommonMixin, models.Model): 256 | """ 257 | Configuration for a probe, Allows you to reuse the configuration. 258 | """ 259 | # General 260 | name = models.CharField(max_length=100, unique=True, blank=False, null=False) 261 | 262 | def __str__(self): 263 | return str(self.name) 264 | 265 | 266 | class Configuration(models.Model): 267 | """ 268 | Configuration for the application. 269 | """ 270 | key = models.CharField(max_length=100, unique=True, blank=False, null=False) 271 | value = models.CharField(max_length=300, blank=True, null=False) 272 | 273 | def __str__(self): 274 | return str(self.key) 275 | 276 | @classmethod 277 | def get_value(cls, key): 278 | try: 279 | if cls.objects.get(key=key).value != "": 280 | return cls.objects.get(key=key).value 281 | else: 282 | return None 283 | except cls.DoesNotExist: 284 | return None 285 | -------------------------------------------------------------------------------- /probemanager/core/modelsmixins.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import logging 3 | import os 4 | import shutil 5 | import time 6 | from contextlib import contextmanager 7 | 8 | from django.conf import settings 9 | 10 | 11 | class CommonMixin: 12 | 13 | @classmethod 14 | def get_logger(cls): 15 | return logging.getLogger(__name__.split('.')[0] + '.' + 16 | os.path.basename(inspect.getsourcefile(cls)) + ':' + cls.__name__) 17 | 18 | @classmethod 19 | def get_all(cls): 20 | return cls.objects.all() 21 | 22 | @classmethod 23 | def get_by_id(cls, key): 24 | try: 25 | probe = cls.objects.get(id=key) 26 | except cls.DoesNotExist: 27 | cls.get_logger().warning('Tries to access an object that does not exist', exc_info=True) 28 | return None 29 | return probe 30 | 31 | @classmethod 32 | def get_last(cls): 33 | try: 34 | obj = cls.objects.last() 35 | except cls.DoesNotExist: 36 | cls.get_logger().warning('Tries to access an object that does not exist', exc_info=True) 37 | return None 38 | return obj 39 | 40 | @classmethod 41 | def get_nbr(cls, nbr): 42 | return cls.objects.all()[:nbr] 43 | 44 | @classmethod 45 | @contextmanager 46 | def get_tmp_dir(cls, folder_name=None): 47 | if folder_name: 48 | tmp_dir = settings.BASE_DIR + '/tmp/' + os.path.basename(os.path.dirname(inspect.getsourcefile(cls))) + \ 49 | '/' + cls.__name__ + '/' + str(folder_name) + '/' + str(time.time()) + '/' 50 | else: 51 | tmp_dir = settings.BASE_DIR + '/tmp/' + os.path.basename(os.path.dirname(inspect.getsourcefile(cls))) + \ 52 | '/' + cls.__name__ + '/' + str(time.time()) + '/' 53 | try: 54 | if not os.path.exists(tmp_dir): 55 | os.makedirs(tmp_dir) 56 | yield tmp_dir 57 | finally: 58 | shutil.rmtree(tmp_dir, ignore_errors=True) 59 | -------------------------------------------------------------------------------- /probemanager/core/notifications.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from smtplib import SMTPException 3 | 4 | import requests 5 | from django.conf import settings 6 | from django.contrib.auth.models import User 7 | from django.db.models.signals import post_save 8 | from django.dispatch import receiver 9 | from lxml import html as html_lxml 10 | from pushbullet import Pushbullet 11 | from pushbullet.errors import InvalidKeyError, PushError 12 | 13 | from .models import Configuration 14 | 15 | logger = logging.getLogger('core.notifications') 16 | 17 | 18 | def pushbullet(title, plain_body): # pragma: no cover 19 | if Configuration.get_value("PUSHBULLET_API_KEY"): 20 | try: 21 | pb = Pushbullet(Configuration.get_value("PUSHBULLET_API_KEY")) 22 | push = pb.push_note(title, plain_body) 23 | logger.debug(push) 24 | except InvalidKeyError: 25 | logger.exception('Wrong PUSHBULLET_API_KEY') 26 | except PushError: 27 | logger.exception('Pushbullet pro required - too many notifications generated') 28 | 29 | 30 | def splunk(html_body): # pragma: no cover 31 | if Configuration.get_value("SPLUNK_HOST"): 32 | if Configuration.get_value("SPLUNK_USER") and Configuration.get_value("SPLUNK_PASSWORD"): 33 | url = "https://" + Configuration.get_value( 34 | "SPLUNK_HOST") + ":8089/services/receivers/simple?source=ProbeManager&sourcetype=notification" 35 | r = requests.post(url, verify=False, data=html_body, 36 | auth=(Configuration.get_value("SPLUNK_USER"), Configuration.get_value("SPLUNK_PASSWORD"))) 37 | else: 38 | url = "https://" + Configuration.get_value( 39 | "SPLUNK_HOST") + ":8089/services/receivers/simple?source=ProbeManager&sourcetype=notification" 40 | r = requests.post(url, verify=False, data=html_body) 41 | logger.debug("Splunk " + str(r.text)) 42 | 43 | 44 | def email(title, plain_body, html_body): # pragma: no cover 45 | users = User.objects.all() 46 | if settings.DEFAULT_FROM_EMAIL: 47 | try: 48 | for user in users: 49 | if user.is_superuser: 50 | try: 51 | user.email_user(title, plain_body, html_message=html_body) 52 | except AttributeError: 53 | logger.exception("Error in sending email") 54 | except SMTPException: 55 | logger.exception("Error in sending email") 56 | except ConnectionRefusedError: 57 | logger.exception("Error in sending email") 58 | 59 | 60 | def send_notification(title, body, html=False): # pragma: no cover 61 | if html: 62 | plain_body = html_lxml.fromstring(body).text_content() 63 | html_body = body 64 | else: 65 | plain_body = body 66 | html_body = '
' + body + '
' 67 | # Pushbullet 68 | pushbullet(title, plain_body) 69 | # Splunk 70 | splunk(html_body) 71 | # Email 72 | email(title, plain_body, html_body) 73 | 74 | 75 | @receiver(post_save, sender=User) 76 | def my_handler(sender, instance, **kwargs): # pragma: no cover 77 | send_notification(sender.__name__ + " created", str(instance.username) + " - " + str(instance.email)) 78 | -------------------------------------------------------------------------------- /probemanager/core/ssh.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | 4 | import paramiko 5 | from django.conf import settings 6 | 7 | from .utils import decrypt 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | def connection(server): 13 | client = paramiko.SSHClient() 14 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 15 | client.connect(hostname=server.host, 16 | username=server.remote_user, 17 | port=server.remote_port, 18 | key_filename=settings.MEDIA_ROOT + "/" + server.ssh_private_key_file.file.name, 19 | passphrase=settings.SECRET_KEY, 20 | ) 21 | return client 22 | 23 | 24 | def execute(server, commands, become=False): 25 | result = dict() 26 | for command_name, command in commands.items(): 27 | if become: 28 | if server.become: 29 | if server.become_pass is not None: 30 | password = decrypt(server.become_pass) 31 | command = " echo '" + password + \ 32 | "' | " + server.become_method + " -S " + command 33 | else: 34 | command = server.become_method + " " + command 35 | else: 36 | raise Exception("Server cannot become", server.name) 37 | client = connection(server) 38 | stdin, stdout, stderr = client.exec_command(command) 39 | if stdout.channel.recv_exit_status() != 0: 40 | raise Exception("Command Failed", 41 | "Command: " + command_name + 42 | " Exitcode: " + str(stdout.channel.recv_exit_status()) + 43 | " Message: " + stdout.read().decode('utf-8') + 44 | " Error: " + str(stderr.readlines()) 45 | ) 46 | else: 47 | result[command_name] = stdout.read().decode('utf-8').replace('\n', '') 48 | if result[command_name] == '': 49 | result[command_name] = 'OK' 50 | 51 | client.close() 52 | return result 53 | 54 | 55 | def execute_copy(server, src, dest, put=True, become=False): 56 | result = dict() 57 | client = connection(server) 58 | ftp_client = client.open_sftp() 59 | try: 60 | if put: 61 | if become: 62 | if server.become: 63 | ftp_client.put(src, os.path.basename(dest)) 64 | else: 65 | raise Exception("Server cannot become", server.name) 66 | else: 67 | ftp_client.put(src, dest) 68 | else: 69 | ftp_client.get(dest, src) 70 | except Exception as e: 71 | raise Exception("Command scp Failed", 72 | " Message: " + str(e) 73 | ) 74 | result['copy'] = "OK" 75 | if become: 76 | if server.become: 77 | commands = {"mv": "mv " + os.path.basename(dest) + " " + dest} 78 | result['mv'] = execute(server, commands, become=True) 79 | ftp_client.close() 80 | client.close() 81 | return result 82 | -------------------------------------------------------------------------------- /probemanager/core/static/core/bootstrap/css/bootstrap-reboot.min.css: -------------------------------------------------------------------------------- 1 | html{box-sizing:border-box;font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}*,::after,::before{box-sizing:inherit}@-ms-viewport{width:device-width}article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent;-webkit-text-decoration-skip:objects}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg:not(:root){overflow:hidden}[role=button],a,area,button,input,label,select,summary,textarea{-ms-touch-action:manipulation;touch-action:manipulation}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#868e96;text-align:left;caption-side:bottom}th{text-align:left}label{display:inline-block;margin-bottom:.5rem}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item}template{display:none}[hidden]{display:none!important} 2 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */ -------------------------------------------------------------------------------- /probemanager/core/static/core/css/style.css: -------------------------------------------------------------------------------- 1 | /* The switch - the box around the slider */ 2 | .switch { 3 | position: relative; 4 | display: inline-block; 5 | width: 60px; 6 | height: 34px; 7 | } 8 | 9 | /* Hide default HTML checkbox */ 10 | .switch input {display:none;} 11 | 12 | /* The slider */ 13 | .slider { 14 | position: absolute; 15 | cursor: pointer; 16 | top: 0; 17 | left: 0; 18 | right: 0; 19 | bottom: 0; 20 | background-color: #ccc; 21 | -webkit-transition: .4s; 22 | transition: .4s; 23 | } 24 | 25 | .slider:before { 26 | position: absolute; 27 | content: ""; 28 | height: 26px; 29 | width: 26px; 30 | left: 4px; 31 | bottom: 4px; 32 | background-color: white; 33 | -webkit-transition: .4s; 34 | transition: .4s; 35 | } 36 | 37 | input:checked + .slider { 38 | background-color: #2196F3; 39 | } 40 | 41 | input:focus + .slider { 42 | box-shadow: 0 0 1px #2196F3; 43 | } 44 | 45 | input:checked + .slider:before { 46 | -webkit-transform: translateX(26px); 47 | -ms-transform: translateX(26px); 48 | transform: translateX(26px); 49 | } 50 | 51 | /* Rounded sliders */ 52 | .slider.round { 53 | border-radius: 34px; 54 | } 55 | 56 | .slider.round:before { 57 | border-radius: 50%; 58 | } -------------------------------------------------------------------------------- /probemanager/core/static/core/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treussart/ProbeManager/afef59036ca7e279a9125b226721fe3d237f04be/probemanager/core/static/core/images/favicon.ico -------------------------------------------------------------------------------- /probemanager/core/static/core/js/reload.js: -------------------------------------------------------------------------------- 1 | 2 | django.jQuery(document).ready(function(){ 3 | django.jQuery(".object-tools").after("
"); 4 | }); 5 | -------------------------------------------------------------------------------- /probemanager/core/tasks.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | import reprlib 3 | 4 | from celery import task 5 | from celery.utils.log import get_task_logger 6 | 7 | from .models import Probe, Job 8 | from .notifications import send_notification 9 | 10 | logger = get_task_logger(__name__) 11 | 12 | repr_instance = reprlib.Repr() 13 | repr_instance.maxstring = 200 14 | 15 | 16 | @task 17 | def deploy_rules(probe_name): 18 | job = Job.create_job('deploy_rules', probe_name) 19 | probe = Probe.get_by_name(probe_name) 20 | if probe is None: 21 | job.update_job("Error - probe is None - param id not set : " + str(probe_name), 'Error') 22 | return {"message": "Error - probe is None - param id not set : " + str(probe_name)} 23 | if probe.subtype: 24 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.subtype) 25 | else: 26 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.type) 27 | probe = my_class.get_by_name(probe_name) 28 | try: 29 | response_deploy_rules = probe.deploy_rules() 30 | response_reload = probe.reload() 31 | if response_deploy_rules['status'] and response_reload['status']: 32 | job.update_job('Deployed rules successfully', 'Completed') 33 | elif not response_deploy_rules['status']: 34 | if 'errors' in response_deploy_rules: 35 | job.update_job('Error during the rules deployed', 36 | 'Error: ' + str(probe_name) + " - " + 37 | repr_instance.repr(response_deploy_rules['errors'])) 38 | logger.error("task - deploy_rules : " + str(probe_name) + " - " + str(response_deploy_rules['errors'])) 39 | return {"message": "Error for probe " + str(probe.name) + " to deploy rules", 40 | "exception": str(response_deploy_rules['errors'])} 41 | else: 42 | job.update_job('Error during the rules deployed', 'Error: ' + str(probe_name)) 43 | logger.error("task - deploy_rules : " + str(probe_name)) 44 | return {"message": "Error for probe " + str(probe.name) + " to deploy rules", "exception": " "} 45 | elif not response_reload['status']: 46 | job.update_job('Error during the rules deployed', 47 | 'Error: ' + str(probe_name) + repr_instance.repr(response_reload['errors'])) 48 | logger.error("task - deploy_rules : " + str(probe_name) + " - " + str(response_reload['errors'])) 49 | return {"message": "Error for probe " + str(probe.name) + " to deploy rules", 50 | "exception": str(response_reload['errors'])} 51 | except Exception as e: 52 | logger.exception('Error during the rules deployed') 53 | job.update_job(repr_instance.repr(e), 'Error') 54 | send_notification("Probe " + str(probe.name), str(e)) 55 | return {"message": "Error for probe " + str(probe.name) + " to deploy rules", "exception": str(e)} 56 | return {"message": "Probe " + probe.name + ' deployed rules successfully'} 57 | 58 | 59 | @task 60 | def reload_probe(probe_name): 61 | job = Job.create_job('reload_probe', probe_name) 62 | probe = Probe.get_by_name(probe_name) 63 | if probe is None: 64 | job.update_job("Error - probe is None - param id not set : " + str(probe_name), 'Error') 65 | return {"message": "Error - probe is None - param id not set : " + str(probe_name)} 66 | if probe.subtype: 67 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.subtype) 68 | else: 69 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.type) 70 | probe = my_class.get_by_name(probe_name) 71 | if probe.scheduled_rules_deployment_enabled: 72 | try: 73 | response = probe.reload() 74 | if response['status']: 75 | job.update_job("task - reload_probe : " + str(probe_name), 'Completed') 76 | logger.info("task - reload_probe : " + str(probe_name)) 77 | return {"message": "Probe " + str(probe.name) + " reloaded successfully"} 78 | else: 79 | job.update_job(repr_instance.repr(response['errors']), 'Error') 80 | return {"message": "Error for probe " + str(probe.name) + " to reload", 81 | "exception": str(response['errors'])} 82 | except Exception as e: 83 | logger.exception("Error for probe to reload") 84 | job.update_job(repr_instance.repr(e), 'Error') 85 | send_notification("Probe " + str(probe.name), str(e)) 86 | return {"message": "Error for probe " + str(probe.name) + " to reload", "exception": str(e)} 87 | else: 88 | job.update_job("Not enabled to reload", 'Error') 89 | send_notification("Probe " + str(probe.name), "Not enabled to reload") 90 | return {"message": probe.name + " not enabled to reload"} 91 | 92 | 93 | @task 94 | def install_probe(probe_name): 95 | job = Job.create_job('install_probe', probe_name) 96 | probe = Probe.get_by_name(probe_name) 97 | if probe is None: 98 | job.update_job("Error - probe is None - param id not set : " + str(probe_name), 'Error') 99 | return {"message": "Error - probe is None - param id not set : " + str(probe_name)} 100 | if probe.subtype: 101 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.subtype) 102 | else: 103 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.type) 104 | probe = my_class.get_by_name(probe_name) 105 | try: 106 | response_install = probe.install() 107 | response_deploy_conf = probe.deploy_conf() 108 | response_deploy_rules = probe.deploy_rules() 109 | response_start = probe.start() 110 | except Exception as e: 111 | logger.exception("Error for probe to install") 112 | job.update_job(repr_instance.repr(e), 'Error') 113 | send_notification("Error for probe " + str(probe.name), str(e)) 114 | return {"message": "Error for probe " + str(probe.name) + " to install", "exception": str(e)} 115 | if response_install['status'] and response_start['status'] and response_deploy_conf['status'] \ 116 | and response_deploy_rules['status']: 117 | job.update_job('Probe ' + str(probe.name) + ' installed successfully', 'Completed') 118 | return {"message": "Probe " + str(probe.name) + " installed successfully"} 119 | else: 120 | job.update_job("Error for probe " + str(probe.name) + " to install", 'Error') 121 | return {"message": "Error for probe " + str(probe.name) + " to install"} 122 | 123 | 124 | @task 125 | def update_probe(probe_name): 126 | job = Job.create_job('update_probe', probe_name) 127 | probe = Probe.get_by_name(probe_name) 128 | if probe is None: 129 | job.update_job("Error - probe is None - param id not set : " + str(probe_name), 'Error') 130 | return {"message": "Error - probe is None - param id not set : " + str(probe_name)} 131 | if probe.subtype: 132 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.subtype) 133 | else: 134 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.type) 135 | probe = my_class.get_by_name(probe_name) 136 | try: 137 | response_update = probe.update() 138 | response_restart = probe.restart() 139 | except Exception as e: 140 | logger.exception("Error for probe to install") 141 | job.update_job(repr_instance.repr(e), 'Error') 142 | send_notification("Error for probe " + str(probe.name), str(e)) 143 | return {"message": "Error for probe " + str(probe.name) + " to install", "exception": str(e)} 144 | if response_update['status'] and response_restart['status']: 145 | job.update_job(response_update + response_restart, 'Completed') 146 | return {"message": "Probe " + str(probe.name) + " updated successfully"} 147 | else: 148 | job.update_job("Error for probe " + str(probe.name) + " to update", 'Error') 149 | return {"message": "Error for probe " + str(probe.name) + " to update"} 150 | 151 | 152 | @task 153 | def check_probe(probe_name): 154 | job = Job.create_job('check_probe', probe_name) 155 | probe = Probe.get_by_name(probe_name) 156 | if probe is None: 157 | job.update_job("Error - probe is None - param id not set : " + str(probe_name), 'Error') 158 | return {"message": "Error - probe is None - param id not set : " + str(probe_name)} 159 | if probe.subtype: 160 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.subtype) 161 | else: 162 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.type) 163 | probe = my_class.get_by_name(probe_name) 164 | if probe.installed: 165 | try: 166 | response_status = probe.status() 167 | except Exception as e: 168 | logger.exception("Error for probe to check status") 169 | job.update_job(repr_instance.repr(e), 'Error') 170 | send_notification("Error for probe " + str(probe.name), str(e)) 171 | return {"message": "Error for probe " + str(probe.name) + " to check status", "exception": str(e)} 172 | if response_status: 173 | if 'active (running)' in response_status: 174 | job.update_job("OK probe " + str(probe.name) + " is running", 'Completed') 175 | return {"message": "OK probe " + str(probe.name) + " is running"} 176 | else: 177 | job.update_job("KO probe " + str(probe.name) + " is not running", 'Completed') 178 | send_notification("probe KO", "Probe " + str(probe.name) + " is not running") 179 | return {"message": "KO probe " + str(probe.name) + " is not running"} 180 | else: 181 | job.update_job("Error for probe " + str(probe.name) + " to check status", 'Error') 182 | return {"message": "Error for probe " + str(probe.name) + " to check status"} 183 | else: 184 | job.update_job("Probe " + str(probe.name) + " not installed", 'Completed') 185 | return {"message": "Probe " + str(probe.name) + " not installed"} 186 | -------------------------------------------------------------------------------- /probemanager/core/templates/core/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% block header %} 5 | {% load static %} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% endblock %} 17 | 18 | {% block title %}Probe Manager{% endblock %} 19 | 20 | 21 | 22 | 23 | 24 | 55 | 56 |
57 | {% block content %}{% endblock %} 58 |
59 | 60 | 61 | -------------------------------------------------------------------------------- /probemanager/core/templates/core/index.html: -------------------------------------------------------------------------------- 1 | {% extends "core/base.html" %} 2 | 3 | {% block title %}Home{% endblock %} 4 | 5 | {% block content %} 6 | 7 | {% if messages %} 8 | 13 | {% endif %} 14 |
15 | {% for instance in instances %} 16 |
17 | {% with instance|add:"/home.html" as template %} 18 | {% include template %} 19 | {% endwith %} 20 |
21 | {% endfor %} 22 |
23 | 24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /probemanager/core/templates/core/index_probe.html: -------------------------------------------------------------------------------- 1 | {% extends "core/base.html" %} 2 | 3 | {% block title %}{{ probe.type }}{% endblock %} 4 | 5 | {% block content %} 6 | 7 |

8 | Instance {% if probe.subtype %} {{ probe.subtype }} {% else %} {{ probe.type }} {% endif %} : {{ probe.name }}

9 |
10 |
11 | {% if probe.description != " " %} 12 | Description: 13 |
{{ probe.description }}
14 | {% endif %} 15 | Os : {{ probe.server.os }}
16 | Host : {{ probe.server.host }}
17 | Created date : {{ probe.created_date }}
18 | {% if probe.installed %} 19 | {% if probe.rules_updated_date %} 20 | Rules updated date : {{ probe.rules_updated_date }} 21 | {% else %} 22 | Rules updated date : Never 23 | {% endif %} 24 |
25 | Uptime {{ probe.type }} : {{ probe.uptime }} 26 |

{% load status %} 27 | Refresh Instance Status 29 |
30 |
31 | 34 | 37 | 40 | 43 |
44 |
45 | {% endif %} 46 | {% if messages %} 47 |
48 |
    49 | {% for message in messages %} 50 |
    51 | × 52 | {{ message }} 53 |
    54 | {% endfor %} 55 |
56 | {% endif %} 57 |
58 |
59 | {% if probe.installed %} 60 | Update {{ probe.type }} Instance 62 | Deploy Configuration 64 | Deploy Rules 66 | {% else %} 67 | Install {{ probe.type }} Instance 69 | {% endif %} 70 |
71 |
72 |
73 | {% endblock %} 74 | -------------------------------------------------------------------------------- /probemanager/core/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treussart/ProbeManager/afef59036ca7e279a9125b226721fe3d237f04be/probemanager/core/templatetags/__init__.py -------------------------------------------------------------------------------- /probemanager/core/templatetags/status.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | import logging 3 | 4 | from django import template 5 | 6 | from core.models import Probe 7 | 8 | logger = logging.getLogger(__name__) 9 | register = template.Library() 10 | 11 | 12 | @register.filter 13 | def status(probe_id): 14 | probe = Probe.get_by_id(probe_id) 15 | if probe is None: # pragma: no cover 16 | return {"message": "Error - probe is None - param id not set : " + str(probe_id)} 17 | if probe.subtype: 18 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.subtype) 19 | else: 20 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.type) 21 | probe = my_class.get_by_id(probe_id) 22 | response = probe.status() 23 | if 'running' in response: 24 | return 'success' 25 | else: 26 | return 'danger' 27 | -------------------------------------------------------------------------------- /probemanager/core/templatetags/version.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django import template 4 | from django.conf import settings 5 | 6 | register = template.Library() 7 | 8 | 9 | @register.simple_tag 10 | def version(app): 11 | if app == 'ProbeManager': 12 | with open(os.path.join(settings.BASE_DIR + '/version.txt')) as f: 13 | return f.read().strip() 14 | else: 15 | with open(os.path.join(settings.BASE_DIR + "/" + app['app_label'] + '/version.txt')) as f: 16 | return f.read().strip() 17 | 18 | 19 | @register.filter 20 | def test_version(app): 21 | if os.path.isfile(os.path.join(settings.BASE_DIR + "/" + app['app_label'] + '/version.txt')): 22 | return True 23 | else: 24 | return False 25 | -------------------------------------------------------------------------------- /probemanager/core/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treussart/ProbeManager/afef59036ca7e279a9125b226721fe3d237f04be/probemanager/core/tests/__init__.py -------------------------------------------------------------------------------- /probemanager/core/tests/test_api.py: -------------------------------------------------------------------------------- 1 | """ venv/bin/python probemanager/manage.py test core.tests.test_api --settings=probemanager.settings.dev """ 2 | from django.contrib.auth.models import User 3 | from rest_framework import status 4 | from rest_framework.test import APIClient 5 | from rest_framework.test import APITestCase 6 | 7 | from core.models import Configuration, Server 8 | 9 | 10 | class APITest(APITestCase): 11 | fixtures = ['init', 'crontab', 'test-core-secrets'] 12 | 13 | def setUp(self): 14 | self.client = APIClient() 15 | User.objects.create_superuser(username='testuser', password='12345', email='testuser@test.com') 16 | if not self.client.login(username='testuser', password='12345'): 17 | self.assertRaises(Exception("Not logged")) 18 | 19 | def tearDown(self): 20 | self.client.logout() 21 | 22 | def test_server(self): 23 | response = self.client.get('/api/v1/core/server/1/test_connection/') 24 | self.assertEqual(response.status_code, status.HTTP_200_OK) 25 | self.assertEqual(response.data, {'status': True}) 26 | server = Server.get_by_id(1) 27 | server.become = False 28 | server.save() 29 | response = self.client.get('/api/v1/core/server/1/test_connection/') 30 | self.assertEqual(response.status_code, status.HTTP_200_OK) 31 | self.assertEqual(response.data, {'status': True}) 32 | 33 | def test_configuration(self): 34 | response = self.client.get('/api/v1/core/configuration/') 35 | self.assertEqual(response.status_code, status.HTTP_200_OK) 36 | self.assertEqual(response.data['count'], 6) 37 | 38 | data_put = {'value': 'test'} 39 | data_patch = {'value': 'test'} 40 | data_patch_2 = {'key': 'test'} 41 | 42 | self.assertEqual(Configuration.objects.get(key="SPLUNK_USER").value, "") 43 | 44 | response = self.client.put('/api/v1/core/configuration/' + str(Configuration.objects.get(key="SPLUNK_USER").id) 45 | + '/', data_put) 46 | self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED) 47 | self.assertEqual(Configuration.objects.get(key="SPLUNK_USER").value, "") 48 | 49 | response = self.client.patch('/api/v1/core/configuration/' + 50 | str(Configuration.objects.get(key="SPLUNK_USER").id) + '/', data_patch) 51 | self.assertEqual(response.status_code, status.HTTP_200_OK) 52 | self.assertEqual(Configuration.objects.get(key="SPLUNK_USER").value, "test") 53 | 54 | response = self.client.patch('/api/v1/core/configuration/' + 55 | str(Configuration.objects.get(key="SPLUNK_USER").id) + '/', data_patch_2) 56 | self.assertEqual(response.status_code, status.HTTP_200_OK) 57 | self.assertEqual(Configuration.objects.get(key="SPLUNK_USER").key, "SPLUNK_USER") 58 | 59 | response = self.client.get('/api/v1/core/configuration/') 60 | self.assertEqual(response.status_code, status.HTTP_200_OK) 61 | self.assertEqual(response.data['count'], 6) 62 | 63 | def test_sshkey(self): 64 | response = self.client.get('/api/v1/core/sshkey/') 65 | self.assertEqual(response.status_code, status.HTTP_200_OK) 66 | self.assertEqual(response.data['count'], 1) 67 | 68 | data = {"name": "test.com_rsa", "file": """-----BEGIN RSA PRIVATE KEY----- 69 | MIIJKAIBAAKCAgEA2GXuC9yTY9y7fNSolEYvdFjyjw7dBcU4TPgTU6ppxddNYH5Z 70 | SQ5qZOosKsGCjKeSP1gVaMYIULebPPB/Hs1t257nC//Gx46s2MxqWK9l4j6gLFzl 71 | eQETlUX8Yq7Ig95MfMUZXOf2lADtmPoxwW9hSalitPM1ilmXyZ8Br+6tfuKwfORJ 72 | Egnc7AiZGIsMPyatfIVHDQ1jT29w0CicbTZLkznIM9Ujk0Dy/LPs83a/w5OheVX2 73 | I7tqi3w+fhG/hd8GsntIr87L/R0MuegGnpwECtqkOvk6v6G3UiwScA1X73inXf56 74 | 3+KRaNjqH4SSX1F8KN9vsyt0Ua7ICfE+LJ2DwC8OUDORiVEVItX4AfTt7t7N/gTW 75 | L9TwImqybqflR4+q7emJUzYn7Vv9JxtHnhoym8Uuj8jU3X0MCHOrvcXhHZ5sHDEr 76 | sRdeSNpDam69vD7w4YYV/khXpUQGfCQw4l+htpAqjBSCNrMpIAv87yAUQUxCl3Wv 77 | Y9MUMNCx466nqxmG8cnZ8pTBmc8bUWj0WVstyuIjxS4yxbNWxkCu5GbfCPBUaMGP 78 | WIl/wNodbtjDxW2N1OD3loHdlq9A79fy6mhQj4dLt8PvIDYt7Of/tBqSn8ZEFsLS 79 | 2iwgPLFuww46bjw22euqY4RGfGspHUzGEfA5NfPZYz5hH6/sSV/7D52mbN0CAwEA 80 | AQKCAgEAsz6zEDY0C/rRfhP0U2VTd28Z86+fGmGDQhYWhC3bEVpGqI/fyyjarh5e 81 | WUgSqAlBlaCTk0a9qoZ7Wt3mnhARWGJmBUVnVPL0b1vbFvyqSt4O9NA576IZo4Lm 82 | DKO0Sa5/8rWcTZ2CXJPsOtO7FPv6PPbGYRY3mhKeLQ69agoswbZp8/lwITX0Pbrd 83 | fTvn+ANEnqkS8lfNlAW+D14kPD5GGXw6Pdzla9rXqsQqmHwbWZfWn9e5W12pYkKW 84 | zPxMhUn4lSyTR7TmuREv8mmj2gtnOcpjUMoShJsiazlASHp1BVIOaEgbZYmZYpyS 85 | SWsZh0TKsFxrfKCY5/P7lGi0VGZgsu/y67Q00Z72/rzQ2ROls/ysrZladA16ip0H 86 | PyCLglYlw12AZQjje1UJXVljdMfh8vbZS2p6V6z2hHVS9v2jHnRrwnu3hbc8kNcR 87 | dxfggUs4XZKKwaRb9z4mVw6qFL4c2ajpM8wlsplrWuIyEgyHGeAwewkG+adowUz/ 88 | xQE709saNngbjuddfF8UNfCNA4nQv62f/rV04kUvjECthPIclhqmvbE09HYmdcrP 89 | qePvmniN1wOIqmfVS1oHMRCKw9UxZ+cosaulhd650c0/QjCO57A2c0gjl2cl5+Qp 90 | gESMgAmN3df7WeoW2cuK1oyOSOMk9ozfxP7w2wu5o5MVZBbyTyECggEBAP45gkH9 91 | YsAYE/l7dSwn67NnXJWpWpS2P/1EF/ExwKPzYVfivjTivVLlk8P8JQi34WvE/bRZ 92 | uWzlzrOTTJ++3R4u0G+QWUjDk0hntTd8Qy+YIVrzFE4VBlHfiZfcw6Yclnj6k+B5 93 | sC8ytuE73gpnbLoArJc5yi+qPk/jWsA+pRzPnUsYOLqSCuuFYpcwYQ3w/Yr7PW8b 94 | 6sCDUPHVSLhroxnCYsV9gJaAC7KExal0mYpxndcBkTPR1ujwcKJZWV4jz0IhF++8 95 | rgsNLUoB5sHRgDX/i6qTkNbnYI/z05IV/DHOZLXXpD0N49MJTav2L4JvBzaMxjZB 96 | N4h3X4syoZ+oBbUCggEBANnoy+3tGhM5KIQyxCfMhtWWj6yagyrGjtJ5N6uS1ln6 97 | U+4ppuLZwAR3nxlJePyMCSvENUJL8aPHNDNYG/V/qvak3uEQ9EyLZre9PiJfAx4n 98 | 87aT7bFwndBFTWr6OXtEmqlRcHcawhSya8NJ4cIec8IbiLmZqLfCxY66RovV6XdN 99 | 6yytXhsS9S/zkOeCsVQ1/XbGYePiX7RC/G4kCkns2Be65N/5bSrt4cn3NdxMa5CG 100 | Vny2nctWiqnJIyvd/eaRqkSgRrUGPLVZ2t/ctX11GQ9RHQwrpt0VlwA0DKvNRmBX 101 | Op3TYS0QODDhwTW/tP/BaZuyg56PnbKQ/XvDD+xpQ4kCggEAUZ/BVNK4TBjvAOFE 102 | w8KliNqc/Wh8rta9QOIGFej1gy53iLJCg9RxGRahFQH2GhCADgwXsTpFsNMwRLP8 103 | nCW59SDux4M/R3+T4GF76664G6Xqv7rgQBm8B7mQAfRd1Q3Eul8p757ilKTh1vtT 104 | 1V9Tp3zj7UIeyqMMkrXaw3LZrKB0TlIelLijTO9sskJURxejMGZuWShLfTgsWxkx 105 | 2hSlL3YcJHChQrEmEFFU7Y2EZtEH7qqQJmUvbWcVouqxKOqydvcNKmoYL3AxpFtr 106 | 7bsIQU4lV8U9ceKkPFP7ECKC8LLl3wS3tOqqxW1tRNMseeKQHFGiqnTSEbzSLm05 107 | O3vFKQKCAQAwfPu70rGlq2dXm1BIptst9dW8i5k6UHqBXRXFKORnmytH6J7JBbkT 108 | hWayosW4NJTp1zwep3V6gx4berSl+SWawm8R18r0qWRO6F5GGaxA7pTtgJc4j52e 109 | NX2Xm1xlEIv1tzh2WE7tehI+n1cL8ejCPYw7+HQxh7acHtkJzqynrn/xLhatoZdL 110 | d0A8M7mvyl+/KT+pDLtNCkbPX1emwXwIM78wE3l2Pv6qCUdD4QFiZHIkSCJul7A9 111 | PZOE9F3GC42+vYdeSqgBlp/8hkkgRIkx/lOfXKtBsMcr9WkIZaIOV/qkGeAavewy 112 | /FkY07K74lbUnXFqO/zUOi0dd/c4HOg5AoIBAGG43VP1Sc2WlNQiK4yr5GcVoR6p 113 | /BUIcOaKXxqPGR4OsZ7CxxXWBAldBCZo2lxvWV/9PiXSZ3VglM6dDtlRdKZp/CU9 114 | /WXM/TQ3HUi8yhG3p5y1LOvo3qQCvtlxbjZAlTKnwMIZ/mIjelv/hVk722ItsSdZ 115 | GBmZVkvKGO8kFr+RQgm5LVhmkn9PYMUdje2yMTP+Kuxa7R9sTmUQeIUMBnEtZ5R5 116 | NDpSpImXLJlcTszxPDdNIsAcrc+VQETi901khBMFxe7FzU7Fe0Nsf5wUkbo2BuhL 117 | yA5V366AarS/0vGFYUPb800cNsCqkBC6DeeKJ5+PbN+IsbCIwMOq9NSZJq0= 118 | -----END RSA PRIVATE KEY-----"""} 119 | 120 | response = self.client.post('/api/v1/core/sshkey/', data) 121 | self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) 122 | 123 | response = self.client.get('/api/v1/core/sshkey/') 124 | self.assertEqual(response.status_code, status.HTTP_200_OK) 125 | self.assertEqual(response.data['count'], 2) 126 | -------------------------------------------------------------------------------- /probemanager/core/tests/test_models.py: -------------------------------------------------------------------------------- 1 | """ venv/bin/python probemanager/manage.py test core.tests.test_models --settings=probemanager.settings.dev """ 2 | from datetime import timedelta, datetime 3 | 4 | import pytz 5 | from django.db.utils import IntegrityError 6 | from django.test import TestCase 7 | 8 | from core.models import OsSupported, Probe, ProbeConfiguration, SshKey, Job, Server, Configuration 9 | 10 | 11 | class JobTest(TestCase): 12 | fixtures = ['init'] 13 | 14 | @classmethod 15 | def setUpTestData(cls): 16 | now = datetime(year=2017, month=5, day=5, hour=12, tzinfo=pytz.UTC) 17 | before = now - timedelta(minutes=30) 18 | cls.job1 = Job.objects.create(name="test", probe="test", status='Error', result="", 19 | created=before, completed=now) 20 | cls.job2 = Job.create_job('test2', 'probe1') 21 | 22 | def test_job(self): 23 | self.assertEqual(str(self.job1), 'test') 24 | self.assertEqual(str(self.job2), 'test2') 25 | self.assertEqual(self.job2.status, 'In progress') 26 | self.job2.update_job('test model', 'Completed') 27 | self.assertEqual(self.job2.status, 'Completed') 28 | jobs = Job.get_all() 29 | self.assertTrue(jobs[0].created > jobs[1].created) 30 | for job in Job.get_all(): 31 | job.delete() 32 | self.assertEqual(Job.get_last(), None) 33 | 34 | 35 | class OsSupportedTest(TestCase): 36 | fixtures = ['init'] 37 | multi_db = False 38 | 39 | @classmethod 40 | def setUpTestData(cls): 41 | pass 42 | 43 | def test_os_supported(self): 44 | all_os_supported = OsSupported.get_all() 45 | os_supported = OsSupported.get_by_id(1) 46 | self.assertEqual(len(all_os_supported), 2) 47 | self.assertEqual(os_supported.name, "debian") 48 | self.assertEqual(str(os_supported), "debian") 49 | # Mixins 50 | self.assertEqual(OsSupported.get_last(), OsSupported.get_by_id(2)) 51 | self.assertEqual(OsSupported.get_nbr(1)[0], os_supported) 52 | OsSupported.get_by_id(1).delete() 53 | OsSupported.get_by_id(2).delete() 54 | self.assertEqual(OsSupported.get_last(), None) 55 | os_supported = OsSupported.get_by_id(99) 56 | self.assertEqual(os_supported, None) 57 | with self.assertRaises(AttributeError): 58 | os_supported.name 59 | with self.assertLogs('core.models', level='DEBUG'): 60 | OsSupported.get_by_id(99) 61 | OsSupported.objects.create(name='debian') 62 | with self.assertRaises(IntegrityError): 63 | OsSupported.objects.create(name="debian") 64 | 65 | 66 | class SshKeyTest(TestCase): 67 | fixtures = ['init', 'crontab', 'test-core-sshkey'] 68 | 69 | @classmethod 70 | def setUpTestData(cls): 71 | pass 72 | 73 | def test_sshKey(self): 74 | ssh_key = SshKey.objects.get(id=1) 75 | self.assertEqual(ssh_key.name, 'test') 76 | self.assertEqual(str(ssh_key), "test") 77 | with self.assertRaises(SshKey.DoesNotExist): 78 | SshKey.objects.get(id=199) 79 | with self.assertRaises(IntegrityError): 80 | SshKey.objects.create(name="test") 81 | 82 | 83 | class ServerTest(TestCase): 84 | fixtures = ['init', 'crontab', 'test-core-secrets'] 85 | 86 | @classmethod 87 | def setUpTestData(cls): 88 | pass 89 | 90 | def test_server(self): 91 | self.assertEqual(str(Server.get_by_id(1)), ' - server.treussart.com, Os : debian') 92 | self.assertTrue(Server.get_by_id(1).test()['status']) 93 | self.assertTrue(Server.get_by_id(1).test_become()['status']) 94 | Server.objects.create(name="test", 95 | host="test", 96 | os=OsSupported.get_by_id(1), 97 | remote_user="test", 98 | remote_port=22, 99 | become=True, 100 | become_method="sudo", 101 | become_user="root", 102 | become_pass="not encrypted", 103 | ssh_private_key_file=SshKey.objects.get(id=1)) 104 | self.assertNotEqual(Server.get_by_id(2).become_pass, "not encrypted") 105 | self.assertGreater(len(Server.get_by_id(2).become_pass), 99) 106 | self.assertEqual(str(Server.get_by_id(2)), 'test - test, Os : debian') 107 | self.assertFalse(Server.get_by_id(2).test()['status']) 108 | self.assertFalse(Server.get_by_id(2).test_become()['status']) 109 | self.assertEqual(Server.get_by_id(3), None) 110 | 111 | 112 | class ProbeTest(TestCase): 113 | fixtures = ['init', 'crontab', 'test-core-secrets', 'test-core-probe'] 114 | 115 | @classmethod 116 | def setUpTestData(cls): 117 | pass 118 | 119 | def test_probe(self): 120 | all_probe = Probe.get_all() 121 | probe = Probe.get_by_id(1) 122 | self.assertEqual(Probe.get_by_name("probe1"), Probe.get_by_id(1)) 123 | self.assertEqual(len(all_probe), 1) 124 | self.assertEqual(probe.name, "probe1") 125 | self.assertEqual(str(probe), "probe1") 126 | self.assertEqual(probe.description, "test") 127 | self.assertIn('Failed to get the uptime on the host :', probe.uptime()) 128 | self.assertFalse(probe.start()['status']) 129 | self.assertFalse(probe.restart()['status']) 130 | self.assertFalse(probe.stop()['status']) 131 | self.assertFalse(probe.reload()['status']) 132 | self.assertEqual('Failed to get status', probe.status()) 133 | probe.installed = False 134 | self.assertEqual('Not installed', probe.uptime()) 135 | probe = Probe.get_by_id(99) 136 | self.assertEqual(probe, None) 137 | with self.assertRaises(AttributeError): 138 | probe.name 139 | probe = Probe.get_by_name("probe99") 140 | self.assertEqual(probe, None) 141 | with self.assertRaises(AttributeError): 142 | probe.name 143 | with self.assertLogs('core.models', level='DEBUG'): 144 | Probe.get_by_id(99) 145 | with self.assertLogs('core.models', level='DEBUG'): 146 | Probe.get_by_name('probe99') 147 | with self.assertRaises(IntegrityError): 148 | Probe.objects.create(name="suricata1") 149 | 150 | 151 | class ProbeConfigurationTest(TestCase): 152 | fixtures = ['init', 'test-core-probeconfiguration'] 153 | 154 | @classmethod 155 | def setUpTestData(cls): 156 | pass 157 | 158 | def test_probe_configuration(self): 159 | all_probe_configuration = ProbeConfiguration.get_all() 160 | probe_configuration = ProbeConfiguration.get_by_id(1) 161 | self.assertEqual(len(all_probe_configuration), 1) 162 | self.assertEqual(probe_configuration.name, "conf1") 163 | self.assertEqual(str(probe_configuration), "conf1") 164 | probe_configuration = ProbeConfiguration.get_by_id(99) 165 | self.assertEqual(probe_configuration, None) 166 | with self.assertRaises(AttributeError): 167 | probe_configuration.name 168 | with self.assertLogs('core.models', level='DEBUG'): 169 | ProbeConfiguration.get_by_id(99) 170 | with self.assertRaises(IntegrityError): 171 | ProbeConfiguration.objects.create(name="conf1") 172 | 173 | 174 | class ConfigurationTest(TestCase): 175 | fixtures = ['init', 'crontab', 'test-core-probeconfiguration'] 176 | 177 | @classmethod 178 | def setUpTestData(cls): 179 | pass 180 | 181 | def test_conf(self): 182 | conf = Configuration.objects.get(id=1) 183 | self.assertEqual(str(conf), "PUSHBULLET_API_KEY") 184 | self.assertEqual(conf.get_value('inexist'), None) 185 | self.assertEqual(conf.get_value("PUSHBULLET_API_KEY"), None) 186 | conf.value = "test" 187 | conf.save() 188 | self.assertEqual(conf.get_value("PUSHBULLET_API_KEY"), "test") 189 | -------------------------------------------------------------------------------- /probemanager/core/tests/test_ssh.py: -------------------------------------------------------------------------------- 1 | """ venv/bin/python probemanager/manage.py test core.tests.test_ssh --settings=probemanager.settings.dev """ 2 | from django.conf import settings 3 | from django.test import TestCase 4 | 5 | from core.models import Server 6 | from core.ssh import connection, execute, execute_copy 7 | 8 | 9 | class SshCoreTest(TestCase): 10 | fixtures = ['init', 'test-core-secrets'] 11 | 12 | @classmethod 13 | def setUpTestData(cls): 14 | pass 15 | 16 | def test_connection(self): 17 | server = Server.get_by_id(1) 18 | client = connection(server) 19 | stdin, stdout, stderr = client.exec_command("hostname") 20 | self.assertEqual(stdout.channel.recv_exit_status(), 0) 21 | self.assertEqual(stdout.read().decode('utf-8'), 'test-travis\n') 22 | self.assertEqual(stderr.readlines(), []) 23 | client.close() 24 | 25 | def test_execute(self): 26 | server = Server.get_by_id(1) 27 | self.assertEqual(execute(server, {'test_hostame': "hostname"}, become=False), 28 | {'test_hostame': 'test-travis'}) 29 | self.assertEqual(execute(server, {'test_ok': "hostname 1>/dev/null"}, become=False), 30 | {'test_ok': 'OK'}) 31 | with self.assertRaises(Exception): 32 | execute(server, {'test_fail': "service ssh status"}, become=False) 33 | 34 | def test_execute_become(self): 35 | server = Server.get_by_id(1) 36 | self.assertEqual(execute(server, {'test_hostame': "hostname"}, become=True), 37 | {'test_hostame': 'test-travis'}) 38 | self.assertIn('ssh.service', execute(server, {'test_status': "service ssh status"}, become=True)['test_status']) 39 | server.become_pass = None 40 | server.save() 41 | with self.assertRaises(Exception): 42 | execute(server, {'test_hostame': "hostname"}, become=True) 43 | server.become = False 44 | server.save() 45 | with self.assertRaises(Exception): 46 | execute(server, {'test_hostame': "hostname"}, become=True) 47 | with self.assertRaises(Exception): 48 | execute_copy(server, src=settings.ROOT_DIR + '/LICENSE', dest='/tmp/LICENSE', become=True) 49 | 50 | def test_execute_copy_put(self): 51 | server = Server.get_by_id(1) 52 | result = execute_copy(server, src=settings.ROOT_DIR + '/LICENSE', dest='LICENSE') 53 | self.assertEqual(result, {'copy': 'OK'}) 54 | with self.assertRaises(Exception): 55 | execute_copy(server, src=settings.ROOT_DIR + '/LICENSE', dest='/') 56 | 57 | def test_execute_copy_put_become(self): 58 | server = Server.get_by_id(1) 59 | result = execute_copy(server, src=settings.ROOT_DIR + '/LICENSE', dest='/tmp/LICENSE', become=True) 60 | self.assertEqual(result, {'copy': 'OK', 'mv': {'mv': 'OK'}}) 61 | -------------------------------------------------------------------------------- /probemanager/core/tests/test_views.py: -------------------------------------------------------------------------------- 1 | """ venv/bin/python probemanager/manage.py test core.tests.test_views --settings=probemanager.settings.dev """ 2 | from django.contrib.auth.models import User 3 | from django.test import Client, TestCase 4 | 5 | from core.models import Server 6 | 7 | 8 | class ViewsCoreTest(TestCase): 9 | fixtures = ['init', 'crontab', 'test-rules-source', 'test-core-secrets'] 10 | 11 | def setUp(self): 12 | self.client = Client() 13 | User.objects.create_superuser(username='testuser', password='12345', email='testuser@test.com') 14 | if not self.client.login(username='testuser', password='12345'): 15 | self.assertRaises(Exception("Not logged")) 16 | 17 | def tearDown(self): 18 | self.client.logout() 19 | 20 | def test_login(self): 21 | """ 22 | Django Admin login 23 | """ 24 | response = self.client.get('/admin/login/', follow=True) 25 | self.assertEqual(response.status_code, 200) 26 | self.assertIn('Site administration | Probe Manager site admin', str(response.content)) 27 | self.assertEqual('admin/index.html', response.templates[0].name) 28 | self.assertIn('admin', response.resolver_match.app_names) 29 | self.assertIn('function AdminSite.index', str(response.resolver_match.func)) 30 | 31 | response = self.client.get('/admin/') 32 | self.assertEqual(response.status_code, 200) 33 | self.assertIn('Change', str(response.content)) 34 | self.assertEqual(str(response.context['user']), 'testuser') 35 | with self.assertTemplateUsed('admin/index.html'): 36 | self.client.get('/admin/login/', follow=True) 37 | 38 | client_not_logged = Client() 39 | response = client_not_logged.get('/admin/login/', follow=True) 40 | self.assertEqual(response.status_code, 200) 41 | self.assertIn('Log in | Probe Manager site admin', str(response.content)) 42 | 43 | response = client_not_logged.get('/admin/', follow=True) 44 | self.assertEqual(response.status_code, 200) 45 | self.assertIn('Log in | Probe Manager site admin', str(response.content)) 46 | self.assertEqual(str(response.context['user']), 'AnonymousUser') 47 | response = client_not_logged.get('/admin/') 48 | self.assertRedirects(response, expected_url='/admin/login/?next=/admin/', status_code=302, 49 | target_status_code=200) 50 | with self.assertTemplateUsed('admin/login.html'): 51 | client_not_logged.get('/admin/', follow=True) 52 | 53 | def test_index(self): 54 | """ 55 | Home page 56 | """ 57 | response = self.client.get('/') 58 | self.assertEqual(response.status_code, 200) 59 | self.assertIn('Home', str(response.content)) 60 | self.assertEqual('core/index.html', response.templates[0].name) 61 | self.assertIn('core', response.resolver_match.app_names) 62 | self.assertIn('function index', str(response.resolver_match.func)) 63 | self.assertEqual(str(response.context['user']), 'testuser') 64 | with self.assertTemplateUsed('core/index.html'): 65 | self.client.get('/') 66 | 67 | def test_admin(self): 68 | response = self.client.get('/admin/core/server/', follow=True) 69 | self.assertEqual(response.status_code, 200) 70 | self.assertEqual(len(Server.get_all()), 1) 71 | response = self.client.post('/admin/core/server/add/', {'name': 'test-server', 72 | 'host': 'test.com', 73 | 'os': '1', 74 | 'remote_user': 'test', 75 | 'remote_port': 22, 76 | 'become_method': 'sudo', 77 | 'become_user': 'root', 78 | 'become_pass': 'test', 79 | 'become': True, 80 | 'ssh_private_key_file': '1', 81 | }, 82 | follow=True) 83 | self.assertEqual(response.status_code, 200) 84 | self.assertIn(' was added successfully', str(response.content)) 85 | self.assertIn('Connection to the server Failed', str(response.content)) 86 | self.assertNotEqual(Server.objects.get(name='test-server').become_pass, 'test') 87 | self.assertEqual(len(Server.get_all()), 2) 88 | Server.get_by_id(1).delete() 89 | self.assertEqual(len(Server.get_all()), 1) 90 | response = self.client.post('/admin/core/server/add/', {'name': 'test-server-ok', 91 | 'host': 'server.treussart.com', 92 | 'os': '1', 93 | 'remote_user': 'travis', 94 | 'remote_port': 33003, 95 | 'become_method': 'sudo', 96 | 'become_user': 'root', 97 | 'become_pass': 'test', 98 | 'become': False, 99 | 'ssh_private_key_file': '1', 100 | }, 101 | follow=True) 102 | self.assertEqual(response.status_code, 200) 103 | self.assertIn(' was added successfully', str(response.content)) 104 | self.assertIn('Connection to the server OK', str(response.content)) 105 | self.assertEqual(len(Server.get_all()), 2) 106 | -------------------------------------------------------------------------------- /probemanager/core/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from . import views 4 | 5 | app_name = 'core' 6 | urlpatterns = [ 7 | url(r'^$', views.index, name='index'), 8 | ] 9 | -------------------------------------------------------------------------------- /probemanager/core/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | import os 4 | import shutil 5 | import subprocess 6 | import time 7 | from contextlib import contextmanager 8 | 9 | import psutil 10 | from cryptography.fernet import Fernet 11 | from django.conf import settings 12 | from django.db.utils import IntegrityError 13 | from django_celery_beat.models import PeriodicTask, CrontabSchedule 14 | 15 | fernet_key = Fernet(settings.FERNET_KEY) 16 | logger = logging.getLogger(__name__) 17 | 18 | 19 | def create_reload_task(probe): 20 | try: 21 | PeriodicTask.objects.get(name=probe.name + "_reload_task") 22 | except PeriodicTask.DoesNotExist: 23 | PeriodicTask.objects.create(crontab=probe.scheduled_rules_deployment_crontab, 24 | name=probe.name + "_reload_task", 25 | task='core.tasks.reload_probe', 26 | args=json.dumps([probe.name, ])) 27 | 28 | 29 | def create_check_task(probe): 30 | try: 31 | PeriodicTask.objects.get(name=probe.name + "_check_task") 32 | except PeriodicTask.DoesNotExist: 33 | if probe.scheduled_check_crontab: 34 | PeriodicTask.objects.create(crontab=probe.scheduled_check_crontab, 35 | name=probe.name + "_check_task", 36 | task='core.tasks.check_probe', 37 | enabled=probe.scheduled_check_enabled, 38 | args=json.dumps([probe.name, ])) 39 | else: 40 | PeriodicTask.objects.create(crontab=CrontabSchedule.objects.get(id=4), 41 | name=probe.name + "_check_task", 42 | task='core.tasks.check_probe', 43 | enabled=probe.scheduled_check_enabled, 44 | args=json.dumps([probe.name, ])) 45 | 46 | 47 | def create_deploy_rules_task(probe, schedule=None, source=None): 48 | if schedule is None: 49 | try: 50 | PeriodicTask.objects.get( 51 | name=probe.name + "_deploy_rules_" + str(probe.scheduled_rules_deployment_crontab)) 52 | except PeriodicTask.DoesNotExist: 53 | try: 54 | if probe.scheduled_rules_deployment_crontab: 55 | PeriodicTask.objects.create(crontab=probe.scheduled_rules_deployment_crontab, 56 | name=probe.name + "_deploy_rules_" + str( 57 | probe.scheduled_rules_deployment_crontab), 58 | task='core.tasks.deploy_rules', 59 | enabled=probe.scheduled_rules_deployment_enabled, 60 | args=json.dumps([probe.name, ])) 61 | else: 62 | PeriodicTask.objects.create(crontab=CrontabSchedule.objects.get(id=4), 63 | name=probe.name + "_deploy_rules_" + str( 64 | CrontabSchedule.objects.get(id=4)), 65 | task='core.tasks.deploy_rules', 66 | enabled=probe.scheduled_rules_deployment_enabled, 67 | args=json.dumps([probe.name, ])) 68 | except IntegrityError: 69 | pass 70 | elif source is not None: 71 | try: 72 | PeriodicTask.objects.get(name=probe.name + "_" + source.uri + "_deploy_rules_" + str(schedule)) 73 | except PeriodicTask.DoesNotExist: 74 | PeriodicTask.objects.create(crontab=schedule, 75 | name=probe.name + "_" + source.uri + "_deploy_rules_" + str(schedule), 76 | task='core.tasks.deploy_rules', 77 | enabled=probe.scheduled_rules_deployment_enabled, 78 | args=json.dumps([probe.name, ])) 79 | 80 | 81 | def decrypt(cipher_text): 82 | if isinstance(cipher_text, bytes): 83 | return fernet_key.decrypt(cipher_text) 84 | else: 85 | return fernet_key.decrypt(cipher_text.encode('utf-8')).decode('utf-8') 86 | 87 | 88 | def encrypt(plain_text): 89 | if isinstance(plain_text, bytes): 90 | return fernet_key.encrypt(plain_text) 91 | else: 92 | return fernet_key.encrypt(plain_text.encode('utf-8')).decode('utf-8') 93 | 94 | 95 | def add_10_min(crontab): 96 | schedule = crontab 97 | try: 98 | if schedule.minute == '*': 99 | # print("* -> 10") 100 | schedule.minute = '10' 101 | return schedule 102 | elif schedule.minute.isdigit(): 103 | if int(schedule.minute) in range(0, 49): 104 | # print("0-50 -> +10") 105 | minute = int(schedule.minute) 106 | minute += 10 107 | schedule.minute = str(minute) 108 | return schedule 109 | elif schedule.hour.isdigit(): 110 | hour = schedule.hour 111 | if int(hour) in range(0, 22): 112 | # print("50+ H0-22 -> H + 1 - 50") 113 | hour = int(schedule.hour) 114 | hour += 1 115 | schedule.hour = str(hour) 116 | minute = int(schedule.minute) 117 | schedule.minute = str(minute - 50) 118 | return schedule 119 | else: 120 | # print("50+ H23 -> ?") 121 | return schedule 122 | elif schedule.hour == '*': 123 | # print("50+ H* -> -50 +1H") 124 | minute = int(schedule.minute) 125 | schedule.minute = str(minute - 50) 126 | schedule.hour = '*/1' 127 | return schedule 128 | else: 129 | hour = int(schedule.hour[2:]) 130 | # print("50+ H*/0+ -> +1h -50min") 131 | schedule.hour = '*/' + str(hour + 1) 132 | schedule.minute = str(int(schedule.minute) - 50) 133 | return schedule 134 | elif '/' in schedule.minute and int(schedule.minute[2:]) in range(10, 49): 135 | # print("*/0-49 -> +10min") 136 | minute = int(schedule.minute[2:]) 137 | minute += 10 138 | schedule.minute = '*/' + str(minute) 139 | return schedule 140 | elif '/' in schedule.minute and int(schedule.minute[2:]) not in range(10, 49): 141 | if schedule.hour.isdigit(): 142 | hour = int(schedule.hour) 143 | if hour in range(0, 22): 144 | # print("*/50+ H0-22 -> +1H -50min") 145 | hour += 1 146 | schedule.hour = str(hour) 147 | schedule.minute = '10' 148 | return schedule 149 | else: 150 | # print("*/50+ H23 -> ?") 151 | return schedule 152 | else: # pragma: no cover 153 | raise ValueError() 154 | except ValueError: 155 | return schedule 156 | 157 | 158 | def add_1_hour(crontab): 159 | schedule = crontab 160 | if schedule.minute.isdigit(): 161 | if schedule.hour.isdigit(): 162 | hour = schedule.hour 163 | if int(hour) in range(0, 22): 164 | # 2 1 -> 2 2 165 | hour = int(schedule.hour) 166 | hour += 1 167 | schedule.hour = str(hour) 168 | return schedule 169 | else: 170 | # 2 23 -> 2 0 + 1 jour 171 | if schedule.day_of_week.isdigit(): 172 | schedule.hour = str(0) 173 | if int(schedule.day_of_week) in range(0, 5): 174 | schedule.day_of_week = str(int(schedule.day_of_week) + 1) 175 | else: 176 | schedule.day_of_week = str(0) 177 | else: 178 | # 2 23 * -> 2 0 + 1 jour illogic 179 | pass 180 | return schedule 181 | else: 182 | # 50 * -> equal illogic 183 | # 10 */2 -> equal illogic 184 | return schedule 185 | else: 186 | # */2 1 -> */2 2 illogic 187 | # */2 * -> equal illogic 188 | # * 23 -> * 0 + 1 jour illogic 189 | # * 1 -> * 2 illogic 190 | return schedule 191 | 192 | 193 | @contextmanager 194 | def get_tmp_dir(folder_name=None): 195 | if folder_name: 196 | tmp_dir = settings.BASE_DIR + '/tmp/' + str(folder_name) + '/' + str(time.time()) + '/' 197 | else: 198 | tmp_dir = settings.BASE_DIR + '/tmp/' + str(time.time()) + '/' 199 | try: 200 | if not os.path.exists(tmp_dir): 201 | os.makedirs(tmp_dir) 202 | yield tmp_dir 203 | finally: 204 | shutil.rmtree(tmp_dir, ignore_errors=True) 205 | 206 | 207 | def process_cmd(cmd, tmp_dir, value=None): 208 | try: 209 | process = subprocess.Popen(cmd, cwd=tmp_dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE, 210 | universal_newlines=True) 211 | outdata, errdata = process.communicate() 212 | logger.debug("outdata : " + str(outdata), "errdata : " + str(errdata)) 213 | except Exception as e: 214 | return {'status': False, 'errors': str(e)} 215 | else: 216 | if process.returncode != 0: 217 | return {'status': False, 'errors': outdata + errdata} 218 | elif value: 219 | if value in outdata or value in errdata: 220 | return {'status': False, 'errors': outdata + errdata} 221 | return {'status': True} 222 | 223 | 224 | def find_procs_by_name(name): 225 | """Return a list of processes matching 'name'.""" 226 | ls = [] 227 | for p in psutil.process_iter(attrs=["name", "exe", "cmdline"]): 228 | if name == p.info['name'] or \ 229 | p.info['exe'] and os.path.basename(p.info['exe']) == name or \ 230 | p.info['cmdline'] and p.info['cmdline'][0] == name: 231 | ls.append(p) 232 | return ls 233 | -------------------------------------------------------------------------------- /probemanager/core/views.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | import logging 3 | 4 | from django.apps.registry import apps 5 | from django.contrib import messages 6 | from django.contrib.auth.decorators import login_required 7 | from django.http import HttpResponseNotFound 8 | from django.shortcuts import render 9 | from django.utils.safestring import mark_safe 10 | 11 | from .models import Probe 12 | from .tasks import deploy_rules as deploy_rules_probe, install_probe, update_probe 13 | from .utils import get_tmp_dir 14 | 15 | logger = logging.getLogger(__name__) 16 | 17 | 18 | @login_required 19 | def index(request): 20 | """ 21 | Display all probes instances. 22 | """ 23 | instances = dict() 24 | for app in apps.get_app_configs(): 25 | for model in app.get_models(): 26 | if issubclass(model, Probe): 27 | if app.verbose_name != "Core": 28 | my_class = getattr(importlib.import_module(app.label + ".models"), app.verbose_name) 29 | instances[app.label] = my_class.get_all() 30 | return render(request, 'core/index.html', {'instances': instances}) 31 | 32 | 33 | @login_required 34 | def probe_index(request, pk): 35 | """ 36 | Display an individual Probe instance. 37 | """ 38 | probe = Probe.get_by_id(pk) 39 | if probe is None: 40 | return HttpResponseNotFound('

Page not found

') 41 | if probe.subtype: 42 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.subtype) 43 | else: 44 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.type) 45 | probe = my_class.get_by_id(pk) 46 | if probe is None: 47 | return HttpResponseNotFound('

Page not found

') 48 | else: 49 | return render(request, probe.type.lower() + '/index.html', {'probe': probe}) 50 | 51 | 52 | @login_required 53 | def start(request, pk): 54 | """ 55 | Start a probe instance. 56 | """ 57 | probe = Probe.get_by_id(pk) 58 | if probe is None: 59 | return HttpResponseNotFound() 60 | if probe.subtype: 61 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.subtype) 62 | else: 63 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.type) 64 | probe = my_class.get_by_id(pk) 65 | try: 66 | response_start = probe.start() 67 | if response_start['status']: 68 | messages.add_message(request, messages.SUCCESS, 'Probe started successfully') 69 | else: 70 | messages.add_message(request, messages.ERROR, 71 | 'Error during the start: ' + str(response_start['errors'])) 72 | except Exception as e: 73 | logger.exception('Error during the start : ' + str(e)) 74 | messages.add_message(request, messages.ERROR, 'Error during the start : ' + str(e)) 75 | return render(request, probe.type.lower() + '/index.html', {'probe': probe}) 76 | 77 | 78 | @login_required 79 | def stop(request, pk): 80 | """ 81 | Stop a probe instance. 82 | """ 83 | probe = Probe.get_by_id(pk) 84 | if probe is None: 85 | return HttpResponseNotFound() 86 | if probe.subtype: 87 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.subtype) 88 | else: 89 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.type) 90 | probe = my_class.get_by_id(pk) 91 | try: 92 | response_stop = probe.stop() 93 | if response_stop['status']: 94 | messages.add_message(request, messages.SUCCESS, 'Probe stopped successfully') 95 | else: 96 | messages.add_message(request, messages.ERROR, 'Error during the stop: ' + str(response_stop['errors'])) 97 | except Exception as e: 98 | logger.exception('Error during the stop : ' + str(e)) 99 | messages.add_message(request, messages.ERROR, 'Error during the stop : ' + str(e)) 100 | return render(request, probe.type.lower() + '/index.html', {'probe': probe}) 101 | 102 | 103 | @login_required 104 | def restart(request, pk): 105 | """ 106 | Restart a probe instance. 107 | """ 108 | probe = Probe.get_by_id(pk) 109 | if probe is None: 110 | return HttpResponseNotFound() 111 | if probe.subtype: 112 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.subtype) 113 | else: 114 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.type) 115 | probe = my_class.get_by_id(pk) 116 | try: 117 | response_restart = probe.restart() 118 | if response_restart['status']: 119 | messages.add_message(request, messages.SUCCESS, 'Probe restarted successfully') 120 | else: 121 | messages.add_message(request, messages.ERROR, 122 | 'Error during the restart: ' + str(response_restart['errors'])) 123 | except Exception as e: 124 | logger.exception('Error during the restart : ' + str(e)) 125 | messages.add_message(request, messages.ERROR, 'Error during the restart : ' + str(e)) 126 | return render(request, probe.type.lower() + '/index.html', {'probe': probe}) 127 | 128 | 129 | @login_required 130 | def reload(request, pk): 131 | """ 132 | Reload a probe instance. 133 | """ 134 | probe = Probe.get_by_id(pk) 135 | if probe is None: 136 | return HttpResponseNotFound() 137 | if probe.subtype: 138 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.subtype) 139 | else: 140 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.type) 141 | probe = my_class.get_by_id(pk) 142 | try: 143 | response_reload = probe.reload() 144 | if response_reload['status']: 145 | messages.add_message(request, messages.SUCCESS, 'Probe reloaded successfully') 146 | else: 147 | messages.add_message(request, messages.ERROR, 148 | 'Error during the reload: ' + str(response_reload['errors'])) 149 | except Exception as e: 150 | logger.exception('Error during the reload : ' + str(e)) 151 | messages.add_message(request, messages.ERROR, 'Error during the reload : ' + str(e)) 152 | return render(request, probe.type.lower() + '/index.html', {'probe': probe}) 153 | 154 | 155 | @login_required 156 | def status(request, pk): 157 | """ 158 | Status of a probe instance. 159 | """ 160 | probe = Probe.get_by_id(pk) 161 | if probe is None: 162 | return HttpResponseNotFound() 163 | if probe.subtype: 164 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.subtype) 165 | else: 166 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.type) 167 | probe = my_class.get_by_id(pk) 168 | try: 169 | response_status = probe.status() 170 | if response_status: 171 | messages.add_message(request, messages.SUCCESS, 172 | "OK probe " + str(probe.name) + " get status successfully") 173 | else: 174 | messages.add_message(request, messages.ERROR, 'Error during the status') 175 | except Exception as e: 176 | logger.exception('Error during the status : ' + str(e)) 177 | messages.add_message(request, messages.ERROR, 'Error during the status : ' + str(e)) 178 | return render(request, probe.type.lower() + '/index.html', {'probe': probe}) 179 | 180 | 181 | @login_required 182 | def install(request, pk): 183 | """ 184 | Install a probe instance. 185 | """ 186 | probe = Probe.get_by_id(pk) 187 | if probe is None: 188 | return HttpResponseNotFound() 189 | if probe.subtype: 190 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.subtype) 191 | else: 192 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.type) 193 | probe = my_class.get_by_id(pk) 194 | try: 195 | install_probe.delay(probe.name) 196 | except Exception as e: 197 | logger.exception('Error during the install : ' + str(e)) 198 | messages.add_message(request, messages.ERROR, 'Error during the install : ' + str(e)) 199 | messages.add_message(request, messages.SUCCESS, mark_safe("Install probe launched with succeed. " + 200 | "View Job")) 201 | return render(request, probe.type.lower() + '/index.html', {'probe': probe}) 202 | 203 | 204 | @login_required 205 | def update(request, pk): 206 | """ 207 | Update a probe instance. 208 | """ 209 | probe = Probe.get_by_id(pk) 210 | if probe is None: 211 | return HttpResponseNotFound() 212 | if probe.subtype: 213 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.subtype) 214 | else: 215 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.type) 216 | probe = my_class.get_by_id(pk) 217 | try: 218 | update_probe.delay(probe.name) 219 | except Exception as e: 220 | logger.exception('Error during the update : ' + str(e)) 221 | messages.add_message(request, messages.ERROR, 'Error during the update : ' + str(e)) 222 | messages.add_message(request, messages.SUCCESS, mark_safe("Update probe launched with succeed. " + 223 | "View Job")) 224 | return render(request, probe.type.lower() + '/index.html', {'probe': probe}) 225 | 226 | 227 | @login_required 228 | def deploy_conf(request, pk): 229 | """ 230 | Deploy the configuration of a probe instance. 231 | """ 232 | probe = Probe.get_by_id(pk) 233 | if probe is None: 234 | return HttpResponseNotFound() 235 | if probe.subtype: 236 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.subtype) 237 | else: 238 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.type) 239 | probe = my_class.get_by_id(pk) 240 | response_test = probe.configuration.test() 241 | logger.debug(str(response_test)) 242 | if probe.secure_deployment: 243 | if not response_test['status']: 244 | messages.add_message(request, messages.ERROR, 'Error during the test configuration') 245 | return render(request, probe.type.lower() + '/index.html', {'probe': probe}) 246 | if response_test['status']: 247 | messages.add_message(request, messages.SUCCESS, "Test configuration OK") 248 | else: 249 | messages.add_message(request, messages.ERROR, "Test configuration failed ! " + str(response_test['errors'])) 250 | try: 251 | response_deploy_conf = probe.deploy_conf() 252 | response_restart = probe.restart() 253 | if response_deploy_conf['status'] and response_restart['status']: 254 | messages.add_message(request, messages.SUCCESS, 'Deployed configuration successfully') 255 | elif not response_deploy_conf['status']: 256 | messages.add_message(request, messages.ERROR, 257 | 'Error during the configuration deployed: ' + str(response_deploy_conf['errors'])) 258 | elif not response_restart['status']: 259 | messages.add_message(request, messages.ERROR, 260 | 'Error during the configuration deployed: ' + str(response_restart['errors'])) 261 | except Exception as e: 262 | logger.exception('Error during the configuration deployed : ' + str(e)) 263 | messages.add_message(request, messages.ERROR, 'Error during the configuration deployed : ' + str(e)) 264 | return render(request, probe.type.lower() + '/index.html', {'probe': probe}) 265 | 266 | 267 | @login_required 268 | def deploy_rules(request, pk): 269 | """ 270 | Deploy the rules of a probe instance. 271 | """ 272 | probe = Probe.get_by_id(pk) 273 | if probe is None: 274 | return HttpResponseNotFound() 275 | if probe.subtype: 276 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.subtype) 277 | else: 278 | my_class = getattr(importlib.import_module(probe.type.lower() + ".models"), probe.type) 279 | probe = my_class.get_by_id(pk) 280 | try: 281 | deploy_rules_probe.delay(probe.name) 282 | messages.add_message(request, messages.SUCCESS, mark_safe( 283 | "Deployed rules launched with succeed. " + 284 | "View Job")) 285 | except Exception as e: 286 | logger.exception('Error during the rules deployment : ' + str(e)) 287 | messages.add_message(request, messages.ERROR, 'Error during the rules deployment : ' + str(e)) 288 | return render(request, probe.type.lower() + '/index.html', {'probe': probe}) 289 | 290 | 291 | def generic_import_csv(cls, request): 292 | if request.method == 'GET': 293 | return render(request, 'import_csv.html') 294 | elif request.method == 'POST': 295 | if request.FILES['file']: 296 | try: 297 | with get_tmp_dir('csv') as tmp_dir: 298 | with open(tmp_dir + 'imported.csv', 'wb+') as destination: 299 | for chunk in request.FILES['file'].chunks(): 300 | destination.write(chunk) 301 | cls.import_from_csv(tmp_dir + 'imported.csv') 302 | except Exception as e: 303 | messages.add_message(request, messages.ERROR, 'Error during the import : ' + str(e)) 304 | return render(request, 'import_csv.html') 305 | messages.add_message(request, messages.SUCCESS, 'CSV file imported successfully !') 306 | return render(request, 'import_csv.html') 307 | else: # pragma: no cover 308 | messages.add_message(request, messages.ERROR, 'No file submitted') 309 | return render(request, 'import_csv.html') 310 | -------------------------------------------------------------------------------- /probemanager/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "probemanager.settings.dev") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /probemanager/probemanager/__init__.py: -------------------------------------------------------------------------------- 1 | from .celery import app as celery_app 2 | import platform 3 | import sys 4 | import warnings 5 | 6 | 7 | if not (3) <= sys.version_info[0]: 8 | sys.exit( 9 | 'ERROR: Probe Manager requires Python 3, but found %s.' % 10 | platform.python_version()) 11 | 12 | warnings.filterwarnings('ignore', 'could not open display') 13 | 14 | 15 | __all__ = ['celery_app'] 16 | __author__ = 'TREUSSART Matthieu' 17 | __author_email__ = 'matthieu@treussart.com' 18 | __title__ = 'ProbeManager' 19 | __licence__ = 'GNU GPLv3' 20 | -------------------------------------------------------------------------------- /probemanager/probemanager/celery.py: -------------------------------------------------------------------------------- 1 | from celery import Celery 2 | 3 | 4 | app = Celery('probemanager') 5 | 6 | # Using a string here means the worker doesn't have to serialize 7 | # the configuration object to child processes. 8 | # - namespace='CELERY' means all celery-related configuration keys 9 | # should have a `CELERY_` prefix. 10 | app.config_from_object('django.conf:settings', namespace='CELERY') 11 | 12 | # Load task modules from all registered Django app configs. 13 | app.autodiscover_tasks() 14 | -------------------------------------------------------------------------------- /probemanager/probemanager/fixtures/crontab.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "django_celery_beat.crontabschedule", 4 | "pk": 1, 5 | "fields": { 6 | "minute": "*/30", 7 | "hour": "*", 8 | "day_of_week": "*", 9 | "day_of_month": "*", 10 | "month_of_year": "*" 11 | } 12 | }, 13 | { 14 | "model": "django_celery_beat.crontabschedule", 15 | "pk": 2, 16 | "fields": { 17 | "minute": "0", 18 | "hour": "*/1", 19 | "day_of_week": "*", 20 | "day_of_month": "*", 21 | "month_of_year": "*" 22 | } 23 | }, 24 | { 25 | "model": "django_celery_beat.crontabschedule", 26 | "pk": 3, 27 | "fields": { 28 | "minute": "0", 29 | "hour": "4", 30 | "day_of_week": "0", 31 | "day_of_month": "*", 32 | "month_of_year": "*" 33 | } 34 | }, 35 | { 36 | "model": "django_celery_beat.crontabschedule", 37 | "pk": 4, 38 | "fields": { 39 | "minute": "0", 40 | "hour": "4", 41 | "day_of_week": "*", 42 | "day_of_month": "*", 43 | "month_of_year": "*" 44 | } 45 | } 46 | ] 47 | -------------------------------------------------------------------------------- /probemanager/probemanager/fixtures/init.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "core.ossupported", 4 | "pk": 1, 5 | "fields": { 6 | "name": "debian" 7 | } 8 | }, 9 | { 10 | "model": "core.ossupported", 11 | "pk": 2, 12 | "fields": { 13 | "name": "ubuntu" 14 | } 15 | }, 16 | { 17 | "model": "core.configuration", 18 | "pk": 1, 19 | "fields": { 20 | "key": "PUSHBULLET_API_KEY", 21 | "value": "" 22 | } 23 | }, 24 | { 25 | "model": "core.configuration", 26 | "pk": 2, 27 | "fields": { 28 | "key": "SPLUNK_HOST", 29 | "value": "" 30 | } 31 | }, 32 | { 33 | "model": "core.configuration", 34 | "pk": 3, 35 | "fields": { 36 | "key": "SPLUNK_USER", 37 | "value": "" 38 | } 39 | }, 40 | { 41 | "model": "core.configuration", 42 | "pk": 4, 43 | "fields": { 44 | "key": "SPLUNK_PASSWORD", 45 | "value": "" 46 | } 47 | }, 48 | { 49 | "model": "core.configuration", 50 | "pk": 5, 51 | "fields": { 52 | "key": "MISP_HOST", 53 | "value": "" 54 | } 55 | }, 56 | { 57 | "model": "core.configuration", 58 | "pk": 6, 59 | "fields": { 60 | "key": "MISP_API_KEY", 61 | "value": "" 62 | } 63 | }, 64 | { 65 | "model": "rules.datatypeupload", 66 | "pk": 0, 67 | "fields": { 68 | "name": "multiple files in compressed file" 69 | } 70 | }, 71 | { 72 | "model": "rules.datatypeupload", 73 | "pk": 1, 74 | "fields": { 75 | "name": "one file not compressed" 76 | } 77 | }, 78 | { 79 | "model": "rules.methodupload", 80 | "pk": 0, 81 | "fields": { 82 | "name": "URL HTTP" 83 | } 84 | }, 85 | { 86 | "model": "rules.methodupload", 87 | "pk": 1, 88 | "fields": { 89 | "name": "Upload file" 90 | } 91 | }, 92 | { 93 | "model": "rules.methodupload", 94 | "pk": 2, 95 | "fields": { 96 | "name": "MISP" 97 | } 98 | } 99 | ] 100 | -------------------------------------------------------------------------------- /probemanager/probemanager/settings/__init__.py: -------------------------------------------------------------------------------- 1 | from probemanager.settings.base import * 2 | -------------------------------------------------------------------------------- /probemanager/probemanager/settings/base.py: -------------------------------------------------------------------------------- 1 | import os 2 | import configparser 3 | 4 | 5 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 6 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 7 | 8 | ROOT_DIR = os.path.abspath(os.path.join(BASE_DIR, os.pardir)) 9 | 10 | config = configparser.ConfigParser() 11 | config.read(os.path.join(ROOT_DIR, 'conf.ini')) 12 | 13 | MEDIA_ROOT = BASE_DIR 14 | FILE_UPLOAD_PERMISSIONS = 0o640 15 | 16 | BASE_APPS = [ 17 | 'django.contrib.admin', 18 | 'django.contrib.auth', 19 | 'django.contrib.contenttypes', 20 | 'django.contrib.sessions', 21 | 'django.contrib.messages', 22 | 'django.contrib.staticfiles', 23 | 'django_celery_beat', 24 | 'django_extensions', 25 | 'rest_framework', 26 | 'rest_framework.authtoken', 27 | 'rest_framework_swagger', 28 | 'select2', 29 | 'api', 30 | 'rules', 31 | 'core', 32 | ] 33 | 34 | MIDDLEWARE = [ 35 | 'django.middleware.security.SecurityMiddleware', 36 | 'django.contrib.sessions.middleware.SessionMiddleware', 37 | 'django.middleware.common.CommonMiddleware', 38 | 'django.middleware.csrf.CsrfViewMiddleware', 39 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 40 | 'django.contrib.messages.middleware.MessageMiddleware', 41 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 42 | 'django.contrib.admindocs.middleware.XViewMiddleware', 43 | ] 44 | 45 | ROOT_URLCONF = 'probemanager.urls' 46 | 47 | TEMPLATES = [ 48 | { 49 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 50 | 'DIRS': [os.path.join(BASE_DIR, 'templates')], 51 | 'APP_DIRS': True, 52 | 'OPTIONS': { 53 | 'context_processors': [ 54 | 'django.template.context_processors.debug', 55 | 'django.template.context_processors.request', 56 | 'django.contrib.auth.context_processors.auth', 57 | 'django.contrib.messages.context_processors.messages', 58 | ], 59 | }, 60 | }, 61 | ] 62 | 63 | WSGI_APPLICATION = 'probemanager.wsgi.application' 64 | 65 | # Password validation 66 | # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators 67 | 68 | AUTH_PASSWORD_VALIDATORS = [ 69 | { 70 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 71 | }, 72 | { 73 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 74 | }, 75 | { 76 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 77 | }, 78 | { 79 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 80 | }, 81 | ] 82 | 83 | 84 | # Internationalization 85 | # https://docs.djangoproject.com/en/1.11/topics/i18n/ 86 | 87 | LANGUAGE_CODE = 'en-us' 88 | 89 | USE_I18N = True 90 | 91 | USE_L10N = True 92 | 93 | USE_TZ = True 94 | 95 | 96 | # Static files (CSS, JavaScript, Images) 97 | # https://docs.djangoproject.com/en/1.11/howto/static-files/ 98 | STATIC_URL = '/static/' 99 | 100 | LOGIN_URL = '/admin/login/' 101 | 102 | #: Only add pickle to this list if your broker is secured 103 | #: from unwanted access (see userguide/security.html) 104 | CELERY_ACCEPT_CONTENT = ['json'] 105 | CELERY_TASK_SERIALIZER = 'json' 106 | 107 | # Security 108 | CSRF_COOKIE_HTTPONLY = True 109 | SECURE_BROWSER_XSS_FILTER = True 110 | SECURE_CONTENT_TYPE_NOSNIFF = True 111 | X_FRAME_OPTIONS = 'DENY' 112 | 113 | DATA_UPLOAD_MAX_NUMBER_FIELDS = None 114 | 115 | PASSWORD_HASHERS = [ 116 | 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher', 117 | ] 118 | 119 | REST_FRAMEWORK = { 120 | 'DEFAULT_PAGINATION_CLASS': 'api.pagination.StandardResultsSetPagination', 121 | 'DEFAULT_PERMISSION_CLASSES': [ 122 | 'rest_framework.permissions.IsAdminUser', 123 | ], 124 | 'TEST_REQUEST_DEFAULT_FORMAT': 'json', 125 | 'DEFAULT_AUTHENTICATION_CLASSES': ( 126 | 'rest_framework.authentication.TokenAuthentication', 127 | 'rest_framework.authentication.SessionAuthentication', 128 | ) 129 | } 130 | 131 | FIXTURE_DIRS = [BASE_DIR + '/probemanager/fixtures', ] 132 | 133 | 134 | LOGGING = { 135 | 'version': 1, 136 | 'disable_existing_loggers': False, 137 | 'filters': { 138 | 'require_debug_true': { 139 | '()': 'django.utils.log.RequireDebugTrue' 140 | }, 141 | 'require_debug_false': { 142 | '()': 'django.utils.log.RequireDebugFalse' 143 | }, 144 | }, 145 | 'formatters': { 146 | 'full': { 147 | 'format': '%(asctime)s %(levelname)s - %(name)s.%(funcName)s %(filename)s:%(lineno)d - %(message)s', 148 | 'datefmt': '%Y-%m-%d %H:%M:%S' 149 | }, 150 | 'simple': { 151 | 'format': '%(levelname)s %(message)s' 152 | }, 153 | }, 154 | 'handlers': { 155 | 'mail_admins': { 156 | 'level': 'ERROR', 157 | 'filters': ['require_debug_false'], 158 | 'class': 'django.utils.log.AdminEmailHandler', 159 | 'include_html': True 160 | }, 161 | 'file': { 162 | 'level': 'INFO', 163 | 'class': 'logging.handlers.RotatingFileHandler', 164 | 'formatter': 'full', 165 | 'filters': ['require_debug_false'] 166 | }, 167 | 'file-error': { 168 | 'level': 'ERROR', 169 | 'class': 'logging.handlers.RotatingFileHandler', 170 | 'formatter': 'full', 171 | 'filters': ['require_debug_false'] 172 | }, 173 | 'console': { 174 | 'level': 'DEBUG', 175 | 'class': 'logging.StreamHandler', 176 | 'formatter': 'full', 177 | 'filters': ['require_debug_true'] 178 | }, 179 | }, 180 | 'loggers': { 181 | 'django': { 182 | 'handlers': ['console', 'file', 'file-error'], 183 | 'level': 'DEBUG', 184 | }, 185 | 'django.template': { 186 | 'handlers': ['console', 'file', 'file-error'], 187 | 'level': 'INFO', 188 | 'propagate': True, 189 | }, 190 | 'django.security': { 191 | 'handlers': ['console', 'file', 'file-error'], 192 | 'level': 'DEBUG', 193 | 'propagate': True 194 | }, 195 | 'django.db.backends': { 196 | 'handlers': ['console', 'file', 'file-error'], 197 | 'level': 'INFO', 198 | 'propagate': True 199 | }, 200 | 'core': { 201 | 'handlers': ['console', 'file', 'file-error'], 202 | 'propagate': True 203 | }, 204 | 'rules': { 205 | 'handlers': ['console', 'file', 'file-error'], 206 | 'propagate': True 207 | }, 208 | }, 209 | 210 | } 211 | 212 | SWAGGER_SETTINGS = { 213 | "VALIDATOR_URL": False, 214 | } 215 | 216 | if os.path.isfile(os.path.join(BASE_DIR, 'core/fixtures/test-core-secrets.ini')): 217 | config_secrets = configparser.ConfigParser() 218 | config_secrets.read(os.path.join(BASE_DIR, 'core/fixtures/test-core-secrets.ini')) 219 | EMAIL_HOST = config_secrets['EMAIL']['EMAIL_HOST'] 220 | EMAIL_PORT = int(config_secrets['EMAIL']['EMAIL_PORT']) 221 | EMAIL_HOST_USER = config_secrets['EMAIL']['EMAIL_HOST_USER'] 222 | DEFAULT_FROM_EMAIL = config_secrets['EMAIL']['DEFAULT_FROM_EMAIL'] 223 | EMAIL_USE_TLS = config_secrets.getboolean('EMAIL', 'EMAIL_USE_TLS') 224 | EMAIL_HOST_PASSWORD = config_secrets['EMAIL']['EMAIL_HOST_PASSWORD'] 225 | -------------------------------------------------------------------------------- /probemanager/probemanager/settings/dev.py: -------------------------------------------------------------------------------- 1 | from probemanager.settings.base import * # noqa 2 | from core.git import git_tag 3 | import configparser 4 | import os 5 | 6 | 7 | config = configparser.ConfigParser() 8 | config.read(os.path.join(ROOT_DIR, 'conf.ini')) 9 | 10 | # SECURITY WARNING: don't run with debug turned on in production! 11 | DEBUG = True 12 | ALLOWED_HOSTS = ['*'] 13 | 14 | SECRET_KEY = 'j-93#)n%t8d0%tyo$f2e+$!%5-#wj65d#85@9y8jf)%_69_1ek' 15 | FERNET_KEY = b'ly8WTzGyN6Xz23t5yq_s_1Ob-qmccqdi52Baj4ta_qQ=' 16 | 17 | GIT_BINARY = config['GIT']['GIT_BINARY'] 18 | 19 | VERSION = git_tag(ROOT_DIR) 20 | 21 | # Celery settings 22 | CELERY_BROKER_URL = 'amqp://guest:guest@localhost//' 23 | 24 | TIME_ZONE = 'UTC' 25 | 26 | CACHES = { 27 | 'default': { 28 | 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', 29 | } 30 | } 31 | 32 | DATABASES = { 33 | 'default': { 34 | 'ENGINE': 'django.db.backends.postgresql', 35 | 'NAME': 'probemanager', 36 | 'USER': 'probemanager', 37 | 'PASSWORD': 'probemanager', 38 | 'HOST': '127.0.0.1', 39 | 'PORT': '5432', 40 | } 41 | } 42 | 43 | SPECIFIC_APPS = ['suricata', 'checkcve', 'bro'] 44 | INSTALLED_APPS = BASE_APPS + SPECIFIC_APPS 45 | 46 | for app in SPECIFIC_APPS: 47 | LOGGING['loggers'].update({app: {'handlers': ['console'], 'propagate': True}}) 48 | if os.path.isfile(BASE_DIR + "/" + app + "/settings.py"): 49 | exec(open(BASE_DIR + "/" + app + "/settings.py").read()) 50 | 51 | LOGGING['handlers']['file'].update({'filename': os.path.join(BASE_DIR, 'probemanager.log')}) 52 | LOGGING['handlers']['file-error'].update({'filename': os.path.join(BASE_DIR, 'probemanager-error.log')}) 53 | 54 | TEMPLATES[0]['OPTIONS']['debug'] = True 55 | -------------------------------------------------------------------------------- /probemanager/probemanager/settings/prod.py: -------------------------------------------------------------------------------- 1 | from probemanager.settings.base import * # noqa 2 | from cryptography.fernet import Fernet 3 | import configparser 4 | import ast 5 | import os 6 | 7 | 8 | config = configparser.ConfigParser() 9 | config.read(os.path.join(ROOT_DIR, 'conf.ini')) 10 | 11 | # SECURITY WARNING: don't run with debug turned on in production! 12 | DEBUG = False 13 | 14 | HOST = config['DEFAULT']['HOST'] 15 | ALLOWED_HOSTS = [HOST, 'localhost', '127.0.0.1'] 16 | GIT_BINARY = config['GIT']['GIT_BINARY'] 17 | 18 | # Specific for installation 19 | PROJECT_NAME = 'probemanager' 20 | APACHE_PORT = 80 21 | 22 | with open(os.path.join(ROOT_DIR, 'secret_key.txt'), encoding='utf_8') as f: 23 | SECRET_KEY = f.read().strip() 24 | with open(os.path.join(ROOT_DIR, 'fernet_key.txt'), encoding='utf_8') as f: 25 | FERNET_KEY = bytes(f.read().strip(), 'utf-8') 26 | 27 | if os.path.isfile(os.path.join(BASE_DIR, 'version.txt')): 28 | with open(os.path.join(BASE_DIR, 'version.txt'), encoding='utf_8') as f: 29 | VERSION = f.read().strip() 30 | else: 31 | VERSION = "" 32 | 33 | 34 | def decrypt(cipher_text): 35 | fernet_key = Fernet(FERNET_KEY) 36 | if isinstance(cipher_text, bytes): 37 | return fernet_key.decrypt(cipher_text).decode('utf-8') 38 | else: 39 | return fernet_key.decrypt(cipher_text.encode('utf-8')).decode('utf-8') 40 | 41 | 42 | if os.path.isfile(os.path.join(ROOT_DIR, 'password_db.txt')): 43 | with open(os.path.join(ROOT_DIR, 'password_db.txt'), encoding='utf_8') as f: 44 | PASSWORD_DB = decrypt(f.read().strip()) 45 | else: 46 | PASSWORD_DB = "probemanager" 47 | 48 | # Celery settings 49 | CELERY_BROKER_URL = 'amqp://guest:guest@localhost//' 50 | 51 | STATIC_ROOT = os.path.join(BASE_DIR, 'static') 52 | 53 | DATABASES = { 54 | 'default': { 55 | 'ENGINE': 'django.db.backends.postgresql', 56 | 'NAME': 'probemanager', 57 | 'USER': 'probemanager', 58 | 'PASSWORD': PASSWORD_DB, 59 | 'HOST': '127.0.0.1', 60 | 'PORT': '5432', 61 | } 62 | } 63 | 64 | SPECIFIC_APPS = ast.literal_eval(config['APPS']['PROD_APPS']) 65 | INSTALLED_APPS = BASE_APPS + SPECIFIC_APPS 66 | 67 | for app in SPECIFIC_APPS: 68 | LOGGING['loggers'].update({app: {'handlers': ['file', 'file-error'], 'propagate': True}}) 69 | if os.path.isfile(BASE_DIR + "/" + app + "/settings.py"): 70 | exec(open(BASE_DIR + "/" + app + "/settings.py", encoding='utf_8').read()) 71 | 72 | LOGGING['handlers']['file'].update({'filename': config['LOG']['FILE_PATH']}) 73 | LOGGING['handlers']['file-error'].update({'filename': config['LOG']['FILE_ERROR_PATH']}) 74 | 75 | TIME_ZONE = config['DEFAULT']['TIME_ZONE'] 76 | 77 | if not os.path.isfile(os.path.join(BASE_DIR, 'core/fixtures/test-core-secrets.ini')): 78 | EMAIL_HOST = config['EMAIL']['EMAIL_HOST'] 79 | EMAIL_PORT = int(config['EMAIL']['EMAIL_PORT']) 80 | EMAIL_HOST_USER = config['EMAIL']['EMAIL_HOST_USER'] 81 | DEFAULT_FROM_EMAIL = config['EMAIL']['DEFAULT_FROM_EMAIL'] 82 | EMAIL_USE_TLS = config.getboolean('EMAIL', 'EMAIL_USE_TLS') 83 | if os.path.isfile(os.path.join(ROOT_DIR, 'password_email.txt')): 84 | with open(os.path.join(ROOT_DIR, 'password_email.txt'), encoding='utf_8') as f: 85 | EMAIL_HOST_PASSWORD = decrypt(f.read().strip()) 86 | elif config.has_option('EMAIL','EMAIL_HOST_PASSWORD'): 87 | EMAIL_HOST_PASSWORD = config['EMAIL']['EMAIL_HOST_PASSWORD'] 88 | else: 89 | EMAIL_HOST_PASSWORD = "" 90 | -------------------------------------------------------------------------------- /probemanager/probemanager/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url, include 2 | from django.contrib import admin 3 | from rest_framework_swagger.views import get_swagger_view 4 | from django.conf import settings 5 | 6 | 7 | schema_view = get_swagger_view(title='ProbeManager API', url='/') 8 | 9 | urlpatterns_modules = list() 10 | for app in settings.SPECIFIC_APPS: 11 | urlpatterns_modules.append(url(r'^' + app + '/', include(app + '.urls'))) 12 | 13 | urlpatterns = [ 14 | url(r'^api/v1/doc/$', schema_view), 15 | url(r'^api/v1/', include('api.urls')), 16 | url(r'^admin/', admin.site.urls), 17 | url(r'^select2/', include('select2.urls')), 18 | url(r'^rules/', include('rules.urls')), 19 | url(r'^', include('core.urls')), 20 | ] 21 | urlpatterns += urlpatterns_modules 22 | -------------------------------------------------------------------------------- /probemanager/probemanager/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for probemanager project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "probemanager.settings.prod") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /probemanager/rules/README.rst: -------------------------------------------------------------------------------- 1 | ***** 2 | Rules 3 | ***** 4 | 5 | Presentation 6 | ============ 7 | 8 | Application with generic things about rules. 9 | 10 | Features 11 | -------- 12 | 13 | * DataTypeUpload 14 | * MethodUpload 15 | * Generic Rule 16 | * Generic RuleSet 17 | * Generic Source 18 | -------------------------------------------------------------------------------- /probemanager/rules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treussart/ProbeManager/afef59036ca7e279a9125b226721fe3d237f04be/probemanager/rules/__init__.py -------------------------------------------------------------------------------- /probemanager/rules/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class RulesConfig(AppConfig): 5 | name = 'rules' 6 | -------------------------------------------------------------------------------- /probemanager/rules/fixtures/test-rules-rule.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "rules.rule", 4 | "pk": 1, 5 | "fields": { 6 | "rev": 0, 7 | "reference": "http://www.exemple.com", 8 | "rule_full": "test", 9 | "enabled": true, 10 | "created_date": "2017-09-23T21:07:36.840Z", 11 | "updated_date": "2017-09-23T21:07:36.840Z" 12 | } 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /probemanager/rules/fixtures/test-rules-ruleset.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "rules.ruleset", 4 | "pk": 1, 5 | "fields": { 6 | "name": "ruleset1", 7 | "description": "test", 8 | "created_date": "2017-09-23T21:09:11.291Z", 9 | "updated_date": null 10 | } 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /probemanager/rules/fixtures/test-rules-source.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "rules.source", 4 | "pk": 1, 5 | "fields": { 6 | "method": 0, 7 | "data_type": 1, 8 | "uri": "https://sslbl.abuse.ch/blacklist/sslblacklist.rules", 9 | "scheduled_rules_deployment_enabled": true, 10 | "scheduled_rules_deployment_crontab": 1, 11 | "scheduled_deploy": true, 12 | "file": "", 13 | "type": "SourceSuricata" 14 | } 15 | }, 16 | { 17 | "model": "rules.source", 18 | "pk": 2, 19 | "fields": { 20 | "method": 0, 21 | "data_type": 0, 22 | "uri": "https://rules.emergingthreats.net/open/suricata-3.3.1/emerging.rules.tar.gz", 23 | "scheduled_rules_deployment_enabled": true, 24 | "scheduled_rules_deployment_crontab": 1, 25 | "scheduled_deploy": true, 26 | "file": "", 27 | "type": "SourceSuricata" 28 | } 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /probemanager/rules/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treussart/ProbeManager/afef59036ca7e279a9125b226721fe3d237f04be/probemanager/rules/migrations/__init__.py -------------------------------------------------------------------------------- /probemanager/rules/models.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import time 3 | 4 | from django.db import models 5 | from django.utils import timezone 6 | from django_celery_beat.models import CrontabSchedule 7 | 8 | from core.modelsmixins import CommonMixin 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | class Rule(CommonMixin, models.Model): 14 | """ 15 | Represent a rule, who can be a script or a signature. 16 | """ 17 | id = models.AutoField(primary_key=True) 18 | rev = models.IntegerField(default=0) 19 | reference = models.CharField(max_length=1000, blank=True, null=True) 20 | rule_full = models.TextField(max_length=10000) 21 | enabled = models.BooleanField(default=True) 22 | created_date = models.DateTimeField(default=timezone.now, editable=False) 23 | updated_date = models.DateTimeField(default=timezone.now, editable=False) 24 | 25 | @classmethod 26 | def find(cls, pattern): 27 | """Search the pattern in all the scripts and signatures""" 28 | return cls.objects.filter(rule_full__icontains=pattern) 29 | 30 | 31 | class DataTypeUpload(CommonMixin, models.Model): 32 | """ 33 | Data type, for differentiate the uploaded file (compressed, uncompressed, multiple files, one file). 34 | """ 35 | name = models.CharField(max_length=100, unique=True, blank=False, null=False) 36 | 37 | def __str__(self): 38 | return self.name 39 | 40 | @classmethod 41 | def get_by_name(cls, name): 42 | try: 43 | obj = cls.objects.get(name=name) 44 | except cls.DoesNotExist as e: 45 | logger.debug('Tries to access an object that does not exist : ' + str(e)) 46 | return None 47 | return obj 48 | 49 | 50 | class MethodUpload(CommonMixin, models.Model): 51 | """ 52 | Method use for the upload. By URL, File 53 | """ 54 | name = models.CharField(max_length=100, unique=True, blank=False, null=False) 55 | 56 | def __str__(self): 57 | return self.name 58 | 59 | @classmethod 60 | def get_by_name(cls, name): 61 | try: 62 | obj = cls.objects.get(name=name) 63 | except cls.DoesNotExist as e: 64 | logger.debug('Tries to access an object that does not exist : ' + str(e)) 65 | return None 66 | return obj 67 | 68 | 69 | class RuleSet(CommonMixin, models.Model): 70 | """ 71 | Set of Rules. Scripts and signatures. 72 | """ 73 | name = models.CharField(max_length=100, unique=True, blank=False, null=False, db_index=True, 74 | verbose_name="Ruleset name") 75 | description = models.CharField(max_length=400, blank=True) 76 | created_date = models.DateTimeField(default=timezone.now, editable=False) 77 | updated_date = models.DateTimeField(blank=True, null=True, editable=False) 78 | 79 | def __str__(self): 80 | return self.name 81 | 82 | @classmethod 83 | def get_by_name(cls, name): 84 | try: 85 | obj = cls.objects.get(name=name) 86 | except cls.DoesNotExist as e: 87 | logger.debug('Tries to access an object that does not exist : ' + str(e)) 88 | return None 89 | return obj 90 | 91 | 92 | class Source(CommonMixin, models.Model): 93 | """ 94 | Set of Source. For scheduled upload of rules. 95 | """ 96 | method = models.ForeignKey(MethodUpload, on_delete=models.CASCADE) 97 | data_type = models.ForeignKey(DataTypeUpload, on_delete=models.CASCADE) 98 | uri = models.CharField(max_length=1000, unique=True, blank=True, null=False) 99 | scheduled_rules_deployment_enabled = models.BooleanField(default=False) 100 | scheduled_rules_deployment_crontab = models.ForeignKey(CrontabSchedule, blank=True, null=True, 101 | on_delete=models.CASCADE) 102 | scheduled_deploy = models.BooleanField(default=False) 103 | file = models.FileField(name='file', upload_to='tmp/source/' + str(time.time()), blank=True) 104 | type = models.CharField(max_length=100, blank=True, default='', editable=False) 105 | 106 | def __str__(self): 107 | return self.uri 108 | 109 | @classmethod 110 | def get_by_uri(cls, uri): 111 | try: 112 | obj = cls.objects.get(uri=uri) 113 | except cls.DoesNotExist as e: 114 | logger.debug('Tries to access an object that does not exist : ' + str(e)) 115 | return None 116 | return obj 117 | -------------------------------------------------------------------------------- /probemanager/rules/templates/rules/search.html: -------------------------------------------------------------------------------- 1 | {% extends "core/base.html" %} 2 | 3 | {% block title %}Result Rules{% endblock %} 4 | 5 | {% block content %} 6 | 7 |

Search result for the pattern : {{ pattern }}

8 | 9 | {% if message %} 10 |
11 |

{{ message }}

12 | 13 | {% else %} 14 | 15 |
16 |
17 | {% for app in data %} 18 |

{{ app.name }} rules :

19 |
20 | {% for signature in app.signatures %} 21 | {{ signature }} 23 | {% endfor %} 24 |
25 |
26 | {% for script in app.scripts %} 27 | {{ script }} 29 | {% endfor %} 30 |
31 |
32 | {% for rule in app.rules %} 33 | {{ rule }} 35 | {% endfor %} 36 |
37 | {% endfor %} 38 | 39 |
40 |
41 | 42 | {% endif %} 43 | 44 | {% endblock %} 45 | -------------------------------------------------------------------------------- /probemanager/rules/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treussart/ProbeManager/afef59036ca7e279a9125b226721fe3d237f04be/probemanager/rules/tests/__init__.py -------------------------------------------------------------------------------- /probemanager/rules/tests/test_models.py: -------------------------------------------------------------------------------- 1 | """ venv/bin/python probemanager/manage.py test rules.tests.test_models --settings=probemanager.settings.dev """ 2 | from django.db.utils import IntegrityError 3 | from django.test import TestCase 4 | from django.utils import timezone 5 | 6 | from rules.models import DataTypeUpload, MethodUpload, Source, RuleSet, Rule 7 | 8 | 9 | class DataTypeUploadTest(TestCase): 10 | fixtures = ['init'] 11 | 12 | @classmethod 13 | def setUpTestData(cls): 14 | pass 15 | 16 | def test_data_type_upload(self): 17 | all_data_type_upload = DataTypeUpload.get_all() 18 | data_type_upload = DataTypeUpload.get_by_id(1) 19 | self.assertEqual(len(all_data_type_upload), 2) 20 | self.assertEqual(data_type_upload.name, "one file not compressed") 21 | self.assertEqual(str(data_type_upload), "one file not compressed") 22 | 23 | data_type_upload = DataTypeUpload.get_by_name("one file not compressed") 24 | self.assertEqual(data_type_upload.name, "one file not compressed") 25 | with self.assertLogs('rules.models', level='DEBUG'): 26 | DataTypeUpload.get_by_name("https") 27 | 28 | data_type_upload = DataTypeUpload.get_by_id(99) 29 | self.assertEqual(data_type_upload, None) 30 | with self.assertRaises(AttributeError): 31 | data_type_upload.name 32 | with self.assertRaises(IntegrityError): 33 | DataTypeUpload.objects.create(name="one file not compressed") 34 | 35 | 36 | class MethodUploadTest(TestCase): 37 | fixtures = ['init'] 38 | 39 | @classmethod 40 | def setUpTestData(cls): 41 | pass 42 | 43 | def test_method_upload(self): 44 | all_method_upload = MethodUpload.get_all() 45 | method_upload = MethodUpload.get_by_id(1) 46 | self.assertEqual(len(all_method_upload), 3) 47 | self.assertEqual(method_upload.name, "Upload file") 48 | self.assertEqual(str(method_upload), "Upload file") 49 | 50 | method_upload = MethodUpload.get_by_name("Upload file") 51 | self.assertEqual(method_upload.name, "Upload file") 52 | with self.assertLogs('rules.models', level='DEBUG'): 53 | MethodUpload.get_by_name("https") 54 | 55 | method_upload = MethodUpload.get_by_id(99) 56 | self.assertEqual(method_upload, None) 57 | with self.assertRaises(AttributeError): 58 | method_upload.name 59 | with self.assertRaises(IntegrityError): 60 | MethodUpload.objects.create(name="Upload file") 61 | 62 | 63 | class RuleTest(TestCase): 64 | fixtures = ['init', 'test-rules-rule'] 65 | 66 | @classmethod 67 | def setUpTestData(cls): 68 | pass 69 | 70 | def test_rule(self): 71 | self.assertEqual(Rule.get_by_id(1).rev, 0) 72 | self.assertEqual(Rule.get_by_id(1).reference, "http://www.exemple.com") 73 | self.assertEqual(Rule.get_by_id(1).rule_full, """test""") 74 | self.assertTrue(Rule.get_by_id(1).enabled) 75 | 76 | all_rule = Rule.get_all() 77 | rule = Rule.get_by_id(1) 78 | self.assertEqual(len(all_rule), 1) 79 | self.assertEqual(rule.reference, "http://www.exemple.com") 80 | rule = Rule.get_by_id(99) 81 | self.assertEqual(rule, None) 82 | with self.assertRaises(AttributeError): 83 | rule.reference 84 | rules = Rule.find("tes") 85 | self.assertEqual(len(rules), 1) 86 | self.assertEqual(rules[0].reference, "http://www.exemple.com") 87 | 88 | 89 | class RuleSetTest(TestCase): 90 | fixtures = ['init', 'test-rules-ruleset'] 91 | 92 | @classmethod 93 | def setUpTestData(cls): 94 | cls.date_now = timezone.now() 95 | 96 | def test_ruleset(self): 97 | self.assertEqual(RuleSet.get_by_id(1).name, "ruleset1") 98 | all_ruleset = RuleSet.get_all() 99 | ruleset = RuleSet.get_by_id(1) 100 | self.assertEqual(len(all_ruleset), 1) 101 | self.assertEqual(ruleset.name, "ruleset1") 102 | self.assertEqual(str(ruleset), "ruleset1") 103 | self.assertEqual(RuleSet.get_by_name('inexist'), None) 104 | ruleset = RuleSet.get_by_id(99) 105 | self.assertEqual(ruleset, None) 106 | with self.assertRaises(AttributeError): 107 | ruleset.name 108 | with self.assertRaises(IntegrityError): 109 | RuleSet.objects.create(name="ruleset1", 110 | description="", 111 | created_date=self.date_now 112 | ) 113 | 114 | 115 | class SourceTest(TestCase): 116 | fixtures = ['init', 'crontab', 'test-rules-source'] 117 | 118 | @classmethod 119 | def setUpTestData(cls): 120 | pass 121 | 122 | def test_source(self): 123 | all_source = Source.get_all() 124 | source = Source.get_by_id(1) 125 | self.assertEqual(len(all_source), 2) 126 | self.assertEqual(source.method.name, "URL HTTP") 127 | self.assertEqual(source.data_type.name, "one file not compressed") 128 | self.assertEqual(source.uri, "https://sslbl.abuse.ch/blacklist/sslblacklist.rules") 129 | self.assertEqual(str(source), "https://sslbl.abuse.ch/blacklist/sslblacklist.rules") 130 | source = Source.get_by_uri("https://sslbl.abuse.ch/blacklist/sslblacklist.rules") 131 | self.assertEqual(source.data_type.name, "one file not compressed") 132 | 133 | with self.assertLogs('rules.models', level='DEBUG'): 134 | Source.get_by_uri("https://sslbl.abuse.ch/lacklist.rules") 135 | source = Source.get_by_id(99) 136 | self.assertEqual(source, None) 137 | with self.assertRaises(AttributeError): 138 | source.uri 139 | with self.assertRaises(IntegrityError): 140 | Source.objects.create(method=MethodUpload.get_by_id(1), 141 | uri="https://sslbl.abuse.ch/blacklist/sslblacklist.rules", 142 | data_type=DataTypeUpload.get_by_id(1), 143 | ) 144 | -------------------------------------------------------------------------------- /probemanager/rules/tests/test_views.py: -------------------------------------------------------------------------------- 1 | """ venv/bin/python probemanager/manage.py test rules.tests.test_views --settings=probemanager.settings.dev """ 2 | from django.contrib.auth.models import User 3 | from django.shortcuts import reverse 4 | from django.test import Client, TestCase 5 | from django.utils import timezone 6 | 7 | 8 | class ViewsRulesTest(TestCase): 9 | fixtures = ['init', 'init-suricata', 'test-suricata-signature', 'test-suricata-script'] 10 | 11 | def setUp(self): 12 | self.client = Client() 13 | User.objects.create_superuser(username='testuser', password='12345', email='testuser@test.com') 14 | if not self.client.login(username='testuser', password='12345'): 15 | self.assertRaises(Exception("Not logged")) 16 | self.date_now = timezone.now() 17 | 18 | def test_search(self): 19 | """ 20 | Search page 21 | """ 22 | response = self.client.get(reverse('rules:search'), {'pattern': 'DNS'}) 23 | self.assertEqual(response.status_code, 200) 24 | self.assertIn('Result Rules', str(response.content)) 25 | self.assertEqual('rules/search.html', response.templates[0].name) 26 | self.assertIn('rules', response.resolver_match.app_names) 27 | self.assertIn('function search', str(response.resolver_match.func)) 28 | self.assertEqual(str(response.context['user']), 'testuser') 29 | with self.assertTemplateUsed('rules/search.html'): 30 | self.client.get(reverse('rules:search'), {'pattern': 'DNS'}) 31 | response = self.client.get(reverse('rules:search')) 32 | self.assertEqual(response.status_code, 200) 33 | self.assertIn('Pattern not found', str(response.content)) 34 | response = self.client.get(reverse('rules:search'), {'pattern': ''}) 35 | self.assertEqual(response.status_code, 200) 36 | self.assertIn('Pattern not found', str(response.content)) 37 | response = self.client.get(reverse('rules:search'), {'pattern': ' '}) 38 | self.assertEqual(response.status_code, 200) 39 | self.assertIn('Pattern not found', str(response.content)) 40 | response = self.client.get(reverse('rules:search'), {'pattern': '-oywz'}) 41 | self.assertEqual(response.status_code, 200) 42 | self.assertIn('Result Rules', str(response.content)) 43 | -------------------------------------------------------------------------------- /probemanager/rules/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from rules import views 4 | 5 | app_name = 'rules' 6 | 7 | urlpatterns = [ 8 | url(r'^search$', views.search, name='search'), 9 | ] 10 | -------------------------------------------------------------------------------- /probemanager/rules/views.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | import logging 3 | 4 | from django.apps.registry import apps 5 | from django.contrib.auth.decorators import login_required 6 | from django.shortcuts import render 7 | 8 | from rules.models import Rule 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | @login_required 14 | def search(request): 15 | """ 16 | Search rules (Scripts and Signatures) from all the probes. 17 | """ 18 | if request.GET.get("pattern"): 19 | pattern = request.GET.get("pattern") 20 | if pattern != "" and pattern != " ": 21 | data = list() 22 | for app in apps.get_app_configs(): 23 | first = True 24 | for model in app.get_models(): 25 | if issubclass(model, Rule): 26 | if app.verbose_name != "Rules": 27 | if first: 28 | first = False 29 | probe_data = dict() 30 | try: 31 | my_class = getattr(importlib.import_module(app.label + ".models"), 32 | 'Signature' + app.verbose_name) 33 | signatures = my_class.find(pattern) 34 | probe_data.update({'signatures': signatures}) 35 | 36 | my_class = getattr(importlib.import_module(app.label + ".models"), 37 | 'Script' + app.verbose_name) 38 | scripts = my_class.find(pattern) 39 | probe_data.update({'scripts': scripts}) 40 | 41 | my_class = getattr(importlib.import_module(app.label + ".models"), 42 | 'Rule' + app.verbose_name) 43 | rules = my_class.find(pattern) 44 | probe_data.update({'rules': rules}) 45 | except AttributeError: 46 | pass 47 | probe_data.update({'name': app.label}) 48 | data.append(probe_data) 49 | return render(request, 'rules/search.html', {'data': data, 'pattern': pattern}) 50 | else: 51 | return render(request, 'rules/search.html', {'message': 'Pattern ' + pattern + ' not found'}) 52 | else: 53 | return render(request, 'rules/search.html', {'message': 'Pattern not found'}) 54 | -------------------------------------------------------------------------------- /probemanager/runtests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import argparse 3 | import glob 4 | import os 5 | import sys 6 | 7 | import django 8 | from django.conf import settings 9 | from django.test.utils import get_runner 10 | 11 | 12 | def runtests(): 13 | """ By default tests are executed by alphabetical order """ 14 | parser = argparse.ArgumentParser() 15 | parser.add_argument("-a", "--all", help="run all tests - default action", action="store_true", default=False) 16 | parser.add_argument("--app", help="run the tests for this app", nargs='+', action='append', dest='app', default=[]) 17 | args = parser.parse_args() 18 | tests = [] 19 | tests_all = [] 20 | for test in glob.glob(os.path.dirname(os.path.abspath(__file__)) + '/*/tests/test*.py'): 21 | file = os.path.basename(test) 22 | directory = os.path.basename(os.path.normpath(os.path.join(os.path.dirname(test), os.pardir))) 23 | tests_all.append(directory + '.tests.' + str(file.split('.')[0])) 24 | 25 | tests_app = [] 26 | for test in tests_all: 27 | for app in args.app: 28 | for ap in app: 29 | if ap in test: 30 | tests_app.append(test) 31 | 32 | if args.all or len(sys.argv) == 1: 33 | tests = tests_all 34 | else: 35 | tests += tests_app 36 | 37 | tests = sorted(tests) 38 | print("Tests that will be launched : " + str(tests)) 39 | django.setup() 40 | testrunner = get_runner(settings) 41 | test_runner = testrunner() 42 | failures = test_runner.run_tests(tests) 43 | sys.exit(bool(failures)) 44 | 45 | 46 | if __name__ == "__main__": 47 | runtests() 48 | -------------------------------------------------------------------------------- /probemanager/scripts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treussart/ProbeManager/afef59036ca7e279a9125b226721fe3d237f04be/probemanager/scripts/__init__.py -------------------------------------------------------------------------------- /probemanager/scripts/apache.py: -------------------------------------------------------------------------------- 1 | import os 2 | from string import Template 3 | 4 | from probemanager.settings.prod import APACHE_PORT, PROJECT_NAME 5 | 6 | 7 | def generate_apache_conf(project_name, dest, port): 8 | template_conf = """ 9 | WSGIPythonHome ${dest}/venv 10 | WSGIPythonPath ${dest}/${project_name} 11 | 12 | 13 | 14 | Alias /static/ ${dest}/${project_name}/static/ 15 | 16 | 17 | Require all granted 18 | 19 | 20 | Alias /docs/ ${dest}/docs/_build/html/ 21 | 22 | 23 | Require all granted 24 | 25 | 26 | WSGIScriptAlias / ${dest}/${project_name}/${project_name}/wsgi.py 27 | SetEnv DJANGO_SETTINGS_MODULE ${dest}/${project_name}/${project_name}/settings/prod.py 28 | 29 | LogFormat "%v %h %l %u %t \\"%r\\" %>s %b" serveur_virtuel_commun 30 | CustomLog /var/log/apache2/${project_name}-access.log serveur_virtuel_commun 31 | ErrorLog /var/log/apache2/${project_name}-error.log 32 | 33 | 34 | 35 | Require all granted 36 | 37 | 38 | 39 | 40 | """ 41 | t = Template(template_conf) 42 | apache_conf = t.safe_substitute(project_name=project_name, dest=dest, port=port) 43 | if os.path.isdir('/etc/apache2/sites-enabled/'): 44 | install_dir = '/etc/apache2/sites-enabled/' 45 | else: 46 | install_dir = dest 47 | with open(install_dir + 'probemanager.conf', 'w') as f: 48 | f.write(apache_conf) 49 | 50 | 51 | def run(*args): 52 | dest = args[0].rstrip('/') 53 | port = APACHE_PORT 54 | generate_apache_conf(PROJECT_NAME, dest, port) 55 | exit() 56 | -------------------------------------------------------------------------------- /probemanager/scripts/db_password.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from getpass import getpass 3 | 4 | from cryptography.fernet import Fernet 5 | 6 | 7 | def encrypt(plain_text, dest): 8 | with open(dest + 'fernet_key.txt') as f: 9 | fernet_key_bytes = bytes(f.read().strip(), 'utf-8') 10 | fernet_key = Fernet(fernet_key_bytes) 11 | return fernet_key.encrypt(plain_text.encode('utf-8')).decode('utf-8') 12 | 13 | 14 | if __name__ == "__main__": 15 | parser = argparse.ArgumentParser() 16 | parser.add_argument("-d", "--dest", help="", default='/etc/') 17 | args = parser.parse_args() 18 | password = getpass('Type the password for the database, followed by [ENTER]: ') 19 | password_encrypted = encrypt(password, args.dest) 20 | 21 | with open(args.dest + 'password_db.txt', 'w') as f: 22 | f.write(password_encrypted) 23 | exit(password) 24 | -------------------------------------------------------------------------------- /probemanager/scripts/generate_doc.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.apps.registry import apps 4 | from django.conf import settings 5 | from jinja2 import Template 6 | 7 | from core.models import Probe 8 | 9 | 10 | def run(*args): 11 | template = """ 12 | 13 | {{ name }} 14 | {% for char in range(name|length) -%} 15 | = 16 | {%- endfor %} 17 | 18 | .. toctree:: 19 | 20 | {{ module }} 21 | 22 | """ 23 | template_include = """.. include:: ../../probemanager/{{ module }}/README.rst 24 | 25 | """ 26 | t = Template(template) 27 | t_include = Template(template_include) 28 | with open(settings.ROOT_DIR + '/docs/modules/index.rst', 'w') as f: 29 | for app in apps.get_app_configs(): 30 | for model in app.get_models(): 31 | if issubclass(model, Probe): 32 | if app.verbose_name != "Core": 33 | path = settings.BASE_DIR + "/" + app.label + "/README.rst" 34 | if os.path.isfile(path): 35 | template_rendered = t.render(module=app.label, name=app.verbose_name) 36 | template_include_rendered = t_include.render(module=app.label) 37 | f_include = open(settings.ROOT_DIR + '/docs/modules/' + app.label + '.rst', 'w') 38 | f_include.write(template_include_rendered) 39 | f_include.close() 40 | f.write(template_rendered) 41 | break 42 | exit(0) 43 | -------------------------------------------------------------------------------- /probemanager/scripts/remove_in_file.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import re 3 | 4 | 5 | if __name__ == "__main__": 6 | parser = argparse.ArgumentParser() 7 | parser.add_argument("-p", "--remove", help="pattern to remove", default=[], type=str, action='append') 8 | parser.add_argument("-r", "--replace", help="pattern to replace separated by : " 9 | "example= 'ProbeManager:ProbeManager/probemanager/checkcve'", 10 | default=[], type=str, action='append') 11 | parser.add_argument("-f", "--file", help="file") 12 | args = parser.parse_args() 13 | for pattern in args.remove: 14 | with open(args.file, 'r') as f: 15 | replaced = re.sub(pattern, '', f.read()) 16 | with open(args.file, 'w') as f: 17 | f.write(replaced) 18 | for pattern in args.replace: 19 | old, new = pattern.split(':') 20 | with open(args.file, 'r') as f: 21 | replaced = re.sub(old, new, f.read()) 22 | with open(args.file, 'w') as f: 23 | f.write(replaced) 24 | exit() 25 | -------------------------------------------------------------------------------- /probemanager/scripts/secrets.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | from cryptography.fernet import Fernet 4 | from django.utils.crypto import get_random_string 5 | 6 | if __name__ == "__main__": 7 | parser = argparse.ArgumentParser() 8 | parser.add_argument("-d", "--dest", help="", default='/etc/') 9 | args = parser.parse_args() 10 | fernet_key = Fernet.generate_key() 11 | 12 | with open(args.dest + 'fernet_key.txt', 'w') as f: 13 | f.write(fernet_key.decode('utf8')) 14 | 15 | chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)' 16 | secret_key = get_random_string(50, chars) 17 | 18 | with open(args.dest + 'secret_key.txt', 'w') as f: 19 | f.write(secret_key) 20 | exit() 21 | -------------------------------------------------------------------------------- /probemanager/scripts/setup_smtp.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | from getpass import getpass 4 | from string import Template 5 | 6 | from cryptography.fernet import Fernet 7 | 8 | template_smtp = """ 9 | [EMAIL] 10 | EMAIL_HOST = ${host} 11 | EMAIL_PORT = ${port} 12 | EMAIL_HOST_USER = ${host_user} 13 | DEFAULT_FROM_EMAIL = ${default_from_email} 14 | EMAIL_USE_TLS = ${use_tls} 15 | """ 16 | 17 | 18 | def encrypt(plain_text, dest): 19 | with open(dest + 'fernet_key.txt') as f: 20 | fernet_key_bytes = bytes(f.read().strip(), 'utf-8') 21 | fernet_key = Fernet(fernet_key_bytes) 22 | return fernet_key.encrypt(plain_text.encode('utf-8')).decode('utf-8') 23 | 24 | 25 | if __name__ == "__main__": 26 | parser = argparse.ArgumentParser() 27 | parser.add_argument("-d", "--dest", help="", default='/etc/') 28 | args = parser.parse_args() 29 | print("Server SMTP :") 30 | host = input('host : ') 31 | port = input('port : ') 32 | host_user = input('user : ') 33 | host_password = getpass('password : ') 34 | default_from_email = input('default from email : ') 35 | use_tls = input('The SMTP host use TLS ? : (True/False) ') 36 | 37 | t = Template(template_smtp) 38 | final = t.substitute(host=host, 39 | port=port, 40 | host_user=host_user, 41 | default_from_email=default_from_email, 42 | use_tls=str(use_tls) 43 | ) 44 | 45 | with open(args.dest + 'conf.ini', 'a', encoding='utf_8') as f: 46 | f.write(final) 47 | with open(args.dest + 'password_email.txt', 'w', encoding='utf_8') as f: 48 | f.write(encrypt(host_password, args.dest)) 49 | sys.exit(0) 50 | -------------------------------------------------------------------------------- /probemanager/scripts/setup_tests.py: -------------------------------------------------------------------------------- 1 | import os 2 | from getpass import getpass 3 | from shutil import copyfile 4 | from string import Template 5 | 6 | from django.conf import settings 7 | 8 | from core.utils import encrypt 9 | 10 | template_server_test = """ 11 | [ 12 | { 13 | "model": "core.configuration", 14 | "pk": 1, 15 | "fields": { 16 | "key": "PUSHBULLET_API_KEY", 17 | "value": "${pushbullet_key}" 18 | } 19 | }, 20 | { 21 | "model": "core.sshkey", 22 | "pk": 1, 23 | "fields": { 24 | "name": "test", 25 | "file": "ssh_keys/${ssh_private_key_file}" 26 | } 27 | }, 28 | { 29 | "model": "core.server", 30 | "pk": 1, 31 | "fields": { 32 | "host": "${host}", 33 | "os": 1, 34 | "remote_user": "${remote_user}", 35 | "remote_port": ${remote_port}, 36 | "become": ${become }}, 37 | "become_method": "${become_method}", 38 | "become_user": "${become_user}", 39 | "become_pass": "${become_pass}", 40 | "ssh_private_key_file": 1 41 | } 42 | } 43 | ] 44 | """ 45 | 46 | 47 | def run(): 48 | skip = input('Add datas Tests ? (y/N) ') 49 | if skip.lower() == 'n' or not skip: 50 | exit(0) 51 | else: 52 | print("Conf for tests") 53 | pushbullet_key = input("PushBullet API key : ") 54 | print("Server for tests") 55 | host = input('host : ') 56 | become = input('become : (true/false) ') 57 | become_method = input('become_method : ') 58 | become_user = input('become_user : ') 59 | become_pass = getpass('become_pass : ') 60 | remote_user = input('remote_user : ') 61 | remote_port = input('remote_port : (0-65535) ') 62 | ssh_private_key_file = input('ssh_private_key_file : (Absolute file path) ') 63 | ssh_private_key_file_basename = os.path.basename(ssh_private_key_file) 64 | ssh_dir = settings.BASE_DIR + '/ssh_keys/' 65 | if not os.path.exists(ssh_dir): 66 | os.makedirs(ssh_dir) 67 | try: 68 | copyfile(ssh_private_key_file, ssh_dir + ssh_private_key_file_basename) 69 | os.chmod(ssh_dir + ssh_private_key_file_basename, 0o600) 70 | except Exception as e: 71 | print("Error in the path of the file : " + str(e)) 72 | exit(1) 73 | 74 | t = Template(template_server_test) 75 | server_test = t.substitute(host=host, 76 | become=become, 77 | become_user=become_user, 78 | become_method=become_method, 79 | become_pass=encrypt(become_pass), 80 | remote_user=remote_user, 81 | remote_port=remote_port, 82 | ssh_private_key_file=ssh_private_key_file_basename, 83 | pushbullet_key=pushbullet_key 84 | ) 85 | with open(settings.BASE_DIR + '/core/fixtures/test-core-secrets.json', 'w') as f: 86 | f.write(server_test) 87 | exit(0) 88 | -------------------------------------------------------------------------------- /probemanager/scripts/utilities.py: -------------------------------------------------------------------------------- 1 | # sudo /usr/local/share/ProbeManager/venv/bin/python /usr/local/share/ProbeManager/probemanager/scripts/utilities.py 2 | # -d /usr/local/share/ProbeManager/ 3 | # venv/bin/python probemanager/scripts/utilities.py -d ~/git/Probemanager/ 4 | import argparse 5 | import os 6 | import sys 7 | from getpass import getpass 8 | 9 | from cryptography.fernet import Fernet 10 | 11 | 12 | def encrypt(plain_text, dest): 13 | if os.path.exists(dest + 'fernet_key.txt'): 14 | with open(dest + 'fernet_key.txt') as f: 15 | fernet_key_bytes = f.read().strip().encode('utf-8') 16 | else: 17 | from django.conf import settings 18 | fernet_key_bytes = settings.FERNET_KEY 19 | fernet_key = Fernet(fernet_key_bytes) 20 | return fernet_key.encrypt(plain_text.encode('utf-8')).decode('utf-8') 21 | 22 | 23 | if __name__ == "__main__": 24 | parser = argparse.ArgumentParser() 25 | parser.add_argument("-d", "--dest", help="destination", default='/usr/local/share/ProbeManager/') 26 | args = parser.parse_args() 27 | password = getpass('Type the password, followed by [ENTER]: ') 28 | password_encrypted = encrypt(password, args.dest) 29 | print("Password encrypted : " + password_encrypted) 30 | sys.exit(0) 31 | -------------------------------------------------------------------------------- /probemanager/scripts/version.py: -------------------------------------------------------------------------------- 1 | from django.apps.registry import apps 2 | from django.conf import settings 3 | 4 | from core.git import git_tag 5 | from core.models import Probe 6 | 7 | 8 | def run(*args): 9 | if args[0] and args[1]: 10 | source = args[0] + '/probemanager' 11 | dest = args[1].rstrip('/') + '/probemanager' 12 | else: 13 | source = settings.BASE_DIR 14 | dest = settings.BASE_DIR 15 | # en prod git_tag prendre des sources ou copier git_root = settings.BASE_DIR 16 | with open(dest + '/version.txt', 'w') as f: 17 | f.write(git_tag(source)) 18 | for app in apps.get_app_configs(): 19 | for model in app.get_models(): 20 | if issubclass(model, Probe): 21 | if app.verbose_name != "Core": 22 | dest_app = dest + "/" + app.label + '/' 23 | source_app = source + "/" + app.label + '/' 24 | with open(dest_app + 'version.txt', 'w') as f: 25 | f.write(git_tag(source_app)) 26 | exit(0) 27 | -------------------------------------------------------------------------------- /probemanager/templates/admin/base.html: -------------------------------------------------------------------------------- 1 | {% load i18n static %} 2 | {% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %} 3 | 4 | 5 | {% block title %}{% endblock %} 6 | 7 | {% block extrastyle %}{% endblock %} 8 | {% if LANGUAGE_BIDI %}{% endif %} 9 | {% block extrahead %}{% endblock %} 10 | {% block blockbots %}{% endblock %} 11 | 12 | 13 | 14 | {% load i18n %} 15 | 16 | 18 | 19 | 20 |
21 | 22 | {% if not is_popup %} 23 | 24 | 52 | 53 | {% block breadcrumbs %} 54 | 58 | {% endblock %} 59 | {% endif %} 60 | 61 | {% block messages %} 62 | {% if messages %} 63 |
    {% for message in messages %} 64 |
  • {{ message|capfirst }}
  • 65 | {% endfor %}
66 | {% endif %} 67 | {% endblock messages %} 68 | 69 | 70 |
71 | {% block pretitle %}{% endblock %} 72 | {% block content_title %}{% if title %}

{{ title }}

{% endif %}{% endblock %} 73 | {% block content %} 74 | {% block object-tools %}{% endblock %} 75 | {{ content }} 76 | {% endblock %} 77 | {% block sidebar %}{% endblock %} 78 |
79 |
80 | 81 | 82 | {% block footer %}{% endblock %} 83 |
84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /probemanager/templates/admin/base_site.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/base.html" %} 2 | 3 | {% block title %}{{ title }} | Probe Manager site admin{% endblock %} 4 | 5 | {% block branding %} 6 |

Probe Manager Administration

7 | {% endblock %} 8 | 9 | {% block nav-global %}{% endblock %} 10 | -------------------------------------------------------------------------------- /probemanager/templates/admin/index.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/base_site.html" %} 2 | {% load i18n static %} 3 | 4 | {% block extrastyle %}{{ block.super }}{% endblock %} 5 | 6 | {% block coltype %}colMS{% endblock %} 7 | 8 | {% block bodyclass %}{{ block.super }} dashboard{% endblock %} 9 | 10 | {% block breadcrumbs %}{% endblock %} 11 | 12 | {% block content %} 13 |
14 | {% load version %}

Probe Manager {% version 'ProbeManager' %}


15 | 16 | {% if app_list %} 17 | {% for app in app_list %} 18 |
19 | 20 | 28 | {% for model in app.models %} 29 | 30 | {% if model.admin_url %} 31 | 32 | {% else %} 33 | 34 | {% endif %} 35 | 36 | {% if model.add_url %} 37 | 38 | {% else %} 39 | 40 | {% endif %} 41 | 42 | {% if model.admin_url %} 43 | 44 | {% else %} 45 | 46 | {% endif %} 47 | 48 | {% endfor %} 49 |
21 | 22 | {{ app.name }} 23 | {% if app|test_version %} 24 | - {% version app %} 25 | {% endif %} 26 | 27 |
{{ model.name }}{{ model.name }}{% trans 'Add' %} {% trans 'Change' %} 
50 |
51 | {% endfor %} 52 | {% else %} 53 |

{% trans "You don't have permission to edit anything." %}

54 | {% endif %} 55 |
56 | {% endblock %} 57 | 58 | {% block sidebar %} 59 | 88 | {% endblock %} 89 | -------------------------------------------------------------------------------- /requirements/base.txt: -------------------------------------------------------------------------------- 1 | amqp==2.3.2 2 | bcrypt==3.1.4 3 | celery==4.2.0 4 | Django==2.0.11 5 | django-celery-beat==1.1.1 6 | django-celery-results==1.0.1 7 | django-extensions==2.0.7 8 | django-rest-swagger==2.2.0 9 | django-select2-forms==2.0.2 10 | djangorestframework==3.8.2 11 | Jinja2==2.10 12 | lxml==4.2.3 13 | paramiko==2.4.2 14 | psutil==5.4.6 15 | psycopg2-binary==2.7.5 16 | pushbullet.py==0.11.0 17 | pymisp==2.4.92.1 18 | PyYAML==4.2b1 19 | Sphinx==1.7.5 20 | sphinx-rtd-theme==0.4.0 21 | -------------------------------------------------------------------------------- /requirements/dev.txt: -------------------------------------------------------------------------------- 1 | -r base.txt 2 | -------------------------------------------------------------------------------- /requirements/os/debian.txt: -------------------------------------------------------------------------------- 1 | build-essential 2 | libpq-dev 3 | postgresql 4 | python3-venv 5 | python3-dev 6 | python3-wheel 7 | rabbitmq-server 8 | wget 9 | -------------------------------------------------------------------------------- /requirements/os/debian_prod.txt: -------------------------------------------------------------------------------- 1 | apache2 2 | libapache2-mod-wsgi-py3 3 | -------------------------------------------------------------------------------- /requirements/os/osx.txt: -------------------------------------------------------------------------------- 1 | postgresql 2 | python3 3 | rabbitmq 4 | -------------------------------------------------------------------------------- /requirements/os/osx_prod.txt: -------------------------------------------------------------------------------- 1 | apache2 2 | mod_wsgi 3 | -------------------------------------------------------------------------------- /requirements/prod.txt: -------------------------------------------------------------------------------- 1 | -r base.txt 2 | -------------------------------------------------------------------------------- /requirements/test.txt: -------------------------------------------------------------------------------- 1 | coverage==4.5.1 2 | django-coverage-plugin==1.5.0 3 | flake8==3.5.0 4 | codacy-coverage==1.3.11 5 | -------------------------------------------------------------------------------- /secrets.tar.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treussart/ProbeManager/afef59036ca7e279a9125b226721fe3d237f04be/secrets.tar.enc -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ -z $1 ] || [[ "$1" = 'dev' ]]; then 4 | arg="dev" 5 | elif [[ "$1" = 'prod' ]]; then 6 | arg=$1 7 | else 8 | echo 'Bad argument' 9 | exit 1 10 | fi 11 | 12 | export DJANGO_SETTINGS_MODULE="probemanager.settings.$arg" 13 | export PYTHONPATH=$PYTHONPATH:"$destfull"/probemanager 14 | 15 | # Virtualenv 16 | if [[ "$VIRTUAL_ENV" = "" ]]; then 17 | if [ ! -d venv ]; then 18 | echo 'install before starting the server' 19 | exit 20 | else 21 | source venv/bin/activate 22 | fi 23 | fi 24 | # Celery 25 | if [ ! -f probemanager/celery.pid ]; then 26 | (cd probemanager/ && celery -A probemanager worker -D --pidfile celery.pid -B -l debug -f probemanager-celery.log --scheduler django_celery_beat.schedulers:DatabaseScheduler) 27 | else 28 | kill $( cat probemanager/celery.pid) 29 | sleep 3 30 | pkill -f celery 31 | sleep 3 32 | (cd probemanager/ && celery -A probemanager worker -D --pidfile celery.pid -B -l debug -f probemanager-celery.log --scheduler django_celery_beat.schedulers:DatabaseScheduler) 33 | fi 34 | # Server 35 | python probemanager/manage.py runserver --settings=probemanager.settings.$arg 36 | -------------------------------------------------------------------------------- /stop_celery.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | pkill -f celery 4 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Get args 4 | if [ -z $1 ]; then 5 | arg="" 6 | else 7 | arg='--app '$@ 8 | fi 9 | source='' 10 | sourcecoverage='' 11 | for app in $@ 12 | do 13 | source="$source"" probemanager/""$app" 14 | sourcecoverage="$sourcecoverage""probemanager/""$app""," 15 | done 16 | sourcecoverage="--source=""$sourcecoverage" 17 | 18 | if [[ "$VIRTUAL_ENV" = "" ]]; then 19 | if [ ! -d venv ]; then 20 | echo 'Install before testing' 21 | exit 22 | else 23 | source venv/bin/activate 24 | fi 25 | fi 26 | 27 | if [[ "$TRAVIS" = true ]]; then 28 | export DJANGO_SETTINGS_MODULE="probemanager.settings.dev" 29 | LOG_PATH="/var/log/" 30 | TRAVIS_JOB_NUM_MIN=$( echo $TRAVIS_JOB_NUMBER | cut -f2 -d '.' ) 31 | else 32 | export DJANGO_SETTINGS_MODULE="probemanager.settings.dev" 33 | LOG_PATH="probemanager/" 34 | fi 35 | 36 | # test if fixtures secrets files are here 37 | if [ ! -f probemanager/core/fixtures/test-core-secrets.json ]; then 38 | echo 'Secrets fixtures not found' 39 | exit 1 40 | fi 41 | FAIL_UNDER="90" 42 | flake8 $source --config=.flake8 43 | result_flake8="$?" 44 | if [ "$result_flake8" -ne 0 ]; then 45 | echo "Tests failed : PEP-8 Not Compliant" 46 | exit "$result_flake8" 47 | fi 48 | coverage erase 49 | coverage run $sourcecoverage probemanager/runtests.py $arg 50 | result_run="$?" 51 | if [ "$result_run" -ne 0 ]; then 52 | echo "Tests failed" 53 | exit "$result_run" 54 | fi 55 | coverage report --fail-under="$FAIL_UNDER" 56 | result_report="$?" 57 | coverage html --skip-covered 58 | if [ "$result_report" -ne 0 ]; then 59 | echo "Tests failed : Coverage under $FAIL_UNDER %" 60 | exit "$result_report" 61 | fi 62 | 63 | if [[ "$CODACY_PROJECT_TOKEN" != "" && "$TRAVIS_JOB_NUM_MIN" = "1" ]]; then 64 | coverage xml 65 | python-codacy-coverage -r coverage.xml 66 | 67 | coverage xml -o coverage-suricata.xml --include='probemanager/suricata/*' 68 | python probemanager/scripts/remove_in_file.py -p probemanager/suricata/ -p probemanager.suricata -r ProbeManager:ProbeManager/probemanager/suricata -f coverage-suricata.xml 69 | ( cd probemanager/suricata && python-codacy-coverage -r ../../coverage-suricata.xml -t $CODACY_SURICATA_TOKEN ) 70 | 71 | coverage xml -o coverage-checkcve.xml --include='probemanager/checkcve/*' 72 | python probemanager/scripts/remove_in_file.py -p probemanager/checkcve/ -p probemanager.checkcve -r ProbeManager:ProbeManager/probemanager/checkcve -f coverage-checkcve.xml 73 | ( cd probemanager/checkcve && python-codacy-coverage -r ../../coverage-checkcve.xml -t $CODACY_CHECKCVE_TOKEN ) 74 | 75 | coverage xml -o coverage-bro.xml --include='probemanager/bro/*' 76 | python probemanager/scripts/remove_in_file.py -p probemanager/bro/ -p probemanager.bro -r ProbeManager:ProbeManager/probemanager/bro -f coverage-bro.xml 77 | ( cd probemanager/bro && python-codacy-coverage -r ../../coverage-bro.xml -t $CODACY_BRO_TOKEN ) 78 | fi 79 | if [[ -f "$LOG_PATH"probemanager-error.log && "$TRAVIS" = true ]]; then 80 | echo "#### ERROR LOGS ####" 81 | cat "$LOG_PATH"probemanager-error.log 82 | fi 83 | if [[ -f "$LOG_PATH"probemanager-celery.log && "$TRAVIS" = true ]]; then 84 | echo "#### CELERY LOGS ####" 85 | cat "$LOG_PATH"probemanager-celery.log 86 | fi 87 | 88 | exit 89 | --------------------------------------------------------------------------------