├── .dockerignore
├── .env
├── .github
├── FUNDING.yml
└── workflows
│ ├── codeql-analysis.yml
│ ├── github-sync.sh
│ ├── main.yml
│ └── secret.env.pgp
├── .gitignore
├── .gitlab-ci-old.yml
├── .gitlab-ci.yml
├── .travis.yml
├── AUTHORS
├── Dockerfile
├── Dockerfile_nginx
├── LICENSE
├── README.md
├── SECURITY.md
├── VERSION
├── app
├── __init__.py
├── assets_detection_rules.py.sample
├── celery.py
├── context_processors.py
├── settings.py.sample
├── urls.py
├── views.py
└── wsgi.py
├── assets
├── __init__.py
├── admin.py
├── apis.py
├── apps.py
├── forms.py
├── migrations
│ ├── 0001_initial.py
│ ├── 0002_auto_20200616_1735.py
│ ├── 0003_asset_owner.py
│ ├── 0004_auto_20200617_0958.py
│ ├── 0005_auto_20200617_1011.py
│ ├── 0006_assetgroup_teams.py
│ ├── 0007_auto_20200709_0007.py
│ ├── 0008_asset_exposure.py
│ ├── 0009_auto_20200921_1151.py
│ ├── 0010_auto_20210402_1439.py
│ ├── 0011_auto_20211123_1140.py
│ ├── 0012_dynamicassetgroup.py
│ └── __init__.py
├── models.py
├── serializers.py
├── templates
│ ├── add-asset-group.html
│ ├── add-asset-owner.html
│ ├── add-asset.html
│ ├── add-assets-bulk.html
│ ├── add-dyn-asset-group.html
│ ├── delete-asset-group.html
│ ├── delete-asset-owner.html
│ ├── delete-asset.html
│ ├── details-asset-group.html
│ ├── details-asset-owner.html
│ ├── details-asset.html
│ ├── details-dyn-asset-group.html
│ ├── edit-asset-group.html
│ ├── edit-asset.html
│ ├── edit-dyn-asset-group.html
│ ├── list-asset-owners.html
│ ├── list-assets.html
│ ├── report-asset-findings.html
│ └── report-assetgroup-findings.html
├── tests.py
├── urls.py
├── utils.py
└── views.py
├── common
├── __init__.py
└── utils
│ ├── __init__.py
│ ├── cpe.py
│ ├── date.py
│ ├── encoding.py
│ ├── net.py
│ ├── pagination.py
│ ├── password.py
│ └── settings.py
├── docker-compose.with-engines.yml
├── docker-compose.yml
├── docker-entrypoint.sh
├── docker-entrypoint.with-engines.sh
├── engines
├── __init__.py
├── admin.py
├── apis.py
├── apps.py
├── forms.py
├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
├── models.py
├── parsers
│ └── nessus.py
├── serializers.py
├── tasks.py
├── templates
│ ├── add-engine-policy.html
│ ├── add-engine.html
│ ├── add-scan-engine.html
│ ├── delete-engine-policy.html
│ ├── delete-engine.html
│ ├── delete-scan-engine.html
│ ├── edit-engine-policy.html
│ ├── edit-engine.html
│ ├── edit-scan-engine.html
│ ├── import-engine-policies.html
│ ├── info-scan-engine.html
│ ├── list-engine-policies.html
│ ├── list-engines.html
│ └── list-scan-engines.html
├── tests.py
├── urls.py
├── utils.py
└── views.py
├── events
├── __init__.py
├── admin.py
├── apis.py
├── apps.py
├── migrations
│ ├── 0001_initial.py
│ ├── 0002_alert.py
│ ├── 0003_alert_metadata.py
│ ├── 0004_auto_20200602_1813.py
│ ├── 0005_auto_20200616_1631.py
│ ├── 0006_auto_20200624_0923.py
│ ├── 0007_auditlog.py
│ ├── 0008_auditlog_owner_username.py
│ ├── 0009_auditlog_type.py
│ ├── 0010_auditlog_metadata.py
│ ├── 0011_auto_20200723_1528.py
│ ├── 0012_alert_type.py
│ ├── 0013_alertoverride.py
│ ├── 0014_alertoverride_teams.py
│ └── __init__.py
├── models.py
├── serializers.py
├── templates
│ ├── delete-event.html
│ └── list-alerts.html
├── tests.py
├── urls.py
├── utils.py
└── views.py
├── findings
├── __init__.py
├── admin.py
├── apis.py
├── apps.py
├── forms.py
├── migrations
│ ├── 0001_initial.py
│ ├── 0002_auto_20200306_0822.py
│ ├── 0003_auto_20200602_2323.py
│ ├── 0004_auto_20200709_0024.py
│ ├── 0005_auto_20200709_0910.py
│ ├── 0006_auto_20200720_1829.py
│ ├── 0007_auto_20210113_1644.py
│ ├── 0008_findingoverride.py
│ ├── 0009_auto_20210504_1320.py
│ ├── 0010_auto_20211104_1152.py
│ └── __init__.py
├── models.py
├── serializers.py
├── templates
│ ├── add-finding.html
│ ├── compare-findings.html
│ ├── delete-findings.html
│ ├── details-finding.html
│ ├── edit-finding.html
│ ├── import-findings.html
│ ├── list-findings.html
│ └── report-finding.html
├── tests.py
├── urls.py
├── utils.py
└── views.py
├── install-patrowl.sh
├── manage.py
├── media
└── .gitignore
├── nginx.conf
├── nginx_docker.conf
├── reportings
├── __init__.py
├── admin.py
├── api.py
├── apps.py
├── migrations
│ └── __init__.py
├── models.py
├── templates
│ ├── home-dashboard.html
│ └── patch-management-dashboard.html
├── urls.py
└── views.py
├── requirements.macos.txt
├── requirements.txt
├── rules
├── __init__.py
├── admin.py
├── apis.py
├── apps.py
├── forms.py
├── migrations
│ ├── 0001_initial.py
│ ├── 0002_auto_20200720_1829.py
│ ├── 0003_auto_20210113_1644.py
│ ├── 0004_auto_20211104_1152.py
│ └── __init__.py
├── models.py
├── templates
│ └── list-rules.html
├── tests.py
├── urls.py
└── views.py
├── scans
├── __init__.py
├── admin.py
├── apis.py
├── apps.py
├── forms.py
├── migrations
│ ├── 0001_initial.py
│ ├── 0002_scandefinition_teams.py
│ ├── 0003_scanjob.py
│ ├── 0004_auto_20201202_1448.py
│ ├── 0005_auto_20201202_1909.py
│ ├── 0006_scandefinition_taggroups_list.py
│ ├── 0007_auto_20220114_1547.py
│ └── __init__.py
├── models.py
├── templates
│ ├── add-scan-campaign.html
│ ├── add-scan-definition.html
│ ├── compare-scans.html
│ ├── delete-scan-campaign.html
│ ├── delete-scan-definition.html
│ ├── details-scan-def.html
│ ├── details-scan.html
│ ├── edit-scan-campaign.html
│ ├── edit-scan-definition.html
│ ├── email_send_report.html
│ ├── email_send_report.txt
│ ├── list-scan-campaigns.html
│ ├── list-scan-definitions.html
│ ├── list-scans-performed.html
│ └── report-scan.html
├── tests.py
├── urls.py
├── utils.py
└── views.py
├── search
├── __init__.py
├── admin.py
├── apis.py
├── apps.py
├── migrations
│ └── __init__.py
├── models.py
├── templates
│ └── search-results.html
├── tests.py
├── urls.py
└── views.py
├── settings
├── __init__.py
├── admin.py
├── apis.py
├── apps.py
├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
├── models.py
├── templates
│ ├── menu-settings.html
│ ├── menu-users.html
│ └── support.html
├── tests.py
├── urls.py
└── views.py
├── start-server.sh
├── static
├── css
│ ├── bootstrap-4.0.0.min.css
│ ├── bootstrap-tagsinput.css
│ ├── bootstrap.min.css
│ ├── cal-heatmap.css
│ ├── dataTables.bootstrap4.min.css
│ ├── family-Lato.css
│ ├── images
│ │ └── ui-bg_flat_75_ffffff_40x100.png
│ ├── jquery-ui.css
│ ├── jquery.dataTables.min.css
│ ├── patrowlmanager.css
│ ├── select.dataTables.min.css
│ └── selectize.bootstrap4.css
├── favicon.ico
├── fonts
│ ├── S6u8w4BMUTPHjxsAXC-v.ttf
│ ├── S6u9w4BMUTPHh6UVSwiPHA.ttf
│ ├── S6uyw4BMUTPHjx4wWw.ttf
│ ├── glyphicons-halflings-regular.eot
│ ├── glyphicons-halflings-regular.svg
│ ├── glyphicons-halflings-regular.ttf
│ ├── glyphicons-halflings-regular.woff
│ └── glyphicons-halflings-regular.woff2
├── images
│ ├── sort_asc.png
│ ├── sort_asc_disabled.png
│ ├── sort_both.png
│ └── sort_desc.png
├── js
│ ├── Chart.bundle.min.js
│ ├── bootstrap-tagsinput.min.js
│ ├── bootstrap-tagsinput.min.js.map
│ ├── bootstrap.min.js
│ ├── cal-heatmap.min.js
│ ├── cal-heatmap.source-map.js
│ ├── d3.min.js
│ ├── dataTables.buttons.min.js
│ ├── dataTables.select.min.js
│ ├── fontawesome-all.min.js
│ ├── jquery-3.2.0.min.js
│ ├── jquery-ui.min.js
│ ├── jquery.dataTables.min.js
│ ├── jquery.min.js
│ ├── selectize.js
│ ├── tether.min.js
│ └── typeahead
│ │ └── typeahead.bundle.min.js
└── tmpl
│ ├── assets_bulk_import_template.csv
│ └── users_bulk_import_template.csv
├── templates
├── base.html
├── errors
│ ├── 400.html
│ ├── 403.html
│ ├── 404.html
│ └── 500.html
├── home.html
├── login.html
└── signup.html
├── templatetags
├── __init__.py
└── common_tags.py
├── tests
└── test_sample.py
├── users
├── __init__.py
├── admin.py
├── apis.py
├── apps.py
├── forms.py
├── middleware.py
├── migrations
│ ├── 0001_initial.py
│ ├── 0002_auto_20200616_1704.py
│ ├── 0003_auto_20200624_0037.py
│ ├── 0004_remove_profile_role.py
│ ├── 0005_profile_is_delegated.py
│ └── __init__.py
├── models.py
├── permissions.py
├── serializers.py
├── templates
│ ├── add-user.html
│ ├── details-user.html
│ ├── edit-user-password.html
│ └── list-users.html
├── tests.py
├── urls.py
└── views.py
└── var
├── bin
├── create_default_admin.py
├── create_default_team.py
└── update_db_migrations.sh
├── data
├── assets.AssetCategory.json
├── engines.Engine.json
├── engines.EnginePolicy.json
└── engines.EnginePolicyScope.json
├── db
├── create_db.sql
├── create_user_and_db.sql
└── init_db.sql
├── etc
├── supervisord-celery-pro.conf
├── supervisord-celery.conf
├── supervisord-pro.conf
└── supervisord.conf
├── log
├── .dockerignore
└── .gitignore
├── migrations
└── assets
│ └── 0002_asset_exposure.py
└── tmp
└── .gitignore
/.dockerignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | app/settings.py
3 | app/assets_detection_rules.py
4 | var/log/*.log
5 | var/log/*.log.[0-9]*
6 | var/tmp/*.pid
7 | # **/migrations/[0-9]*.py
8 | media/*
9 | *.pyc
10 | __pycache__
11 | env
12 | env3
13 |
14 | pro/*
15 | env3pro
16 | .env
17 | .travis.yml
18 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | PATROWL_MANAGER_VERSION=latest
2 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [MaKyOtOx]
2 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | name: "CodeQL"
7 |
8 | on:
9 | push:
10 | branches: [master]
11 | pull_request:
12 | # The branches below must be a subset of the branches above
13 | branches: [master]
14 | schedule:
15 | - cron: '0 23 * * 3'
16 |
17 | jobs:
18 | analyze:
19 | name: Analyze
20 | runs-on: ubuntu-latest
21 |
22 | strategy:
23 | fail-fast: false
24 | matrix:
25 | # Override automatic language detection by changing the below list
26 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
27 | language: ['python']
28 | # Learn more...
29 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
30 |
31 | steps:
32 | - name: Checkout repository
33 | uses: actions/checkout@v2
34 | with:
35 | # We must fetch at least the immediate parents so that if this is
36 | # a pull request then we can checkout the head.
37 | fetch-depth: 2
38 |
39 | # If this run was triggered by a pull request event, then checkout
40 | # the head of the pull request instead of the merge commit.
41 | - run: git checkout HEAD^2
42 | if: ${{ github.event_name == 'pull_request' }}
43 |
44 | # Initializes the CodeQL tools for scanning.
45 | - name: Initialize CodeQL
46 | uses: github/codeql-action/init@v1
47 | with:
48 | languages: ${{ matrix.language }}
49 | # If you wish to specify custom queries, you can do so here or in a config file.
50 | # By default, queries listed here will override any specified in a config file.
51 | # Prefix the list here with "+" to use these queries and those in the config file.
52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
53 |
54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
55 | # If this step fails, then you should remove it and run the build manually (see below)
56 | - name: Autobuild
57 | uses: github/codeql-action/autobuild@v1
58 |
59 | # ℹ️ Command-line programs to run using the OS shell.
60 | # 📚 https://git.io/JvXDl
61 |
62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
63 | # and modify them (or add more) to build your code if your project
64 | # uses a compiled language
65 |
66 | #- run: |
67 | # make bootstrap
68 | # make release
69 |
70 | - name: Perform CodeQL Analysis
71 | uses: github/codeql-action/analyze@v1
72 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | # Gitlab CI automation
2 |
3 | name: CI mirroring
4 |
5 | # Controls when the action will run.
6 | on:
7 | push:
8 | branches:
9 | - '**'
10 | tags-ignore:
11 | - '*.*'
12 | pull_request:
13 | # schedule:
14 | # # * is a special character in YAML so you have to quote this string
15 | # - cron: '30 2 * * *'
16 |
17 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
18 | jobs:
19 | # This workflow contains a single job called "build"
20 | build:
21 | # The type of runner that the job will run on
22 | #runs-on: ubuntu-latest
23 | runs-on: ubuntu-latest
24 | timeout-minutes: 15
25 |
26 | # Steps represent a sequence of tasks that will be executed as part of the job
27 | steps:
28 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
29 | - uses: actions/checkout@v2
30 |
31 | - name: Import environment variables from a file
32 | id: import-env
33 | shell: bash
34 | run: |
35 | gpg --quiet --batch --yes --decrypt --passphrase="$PATROWL_SECRET_GITHUB_KEY" --output ./.github/workflows/secret.env ./.github/workflows/secret.env.pgp
36 | while read line; do
37 | echo "$line" >> $GITHUB_ENV
38 | done < ./.github/workflows/secret.env
39 | env:
40 | PATROWL_SECRET_GITHUB_KEY: ${{ secrets.PATROWL_SECRET_GITHUB_KEY }}
41 |
42 | - name: Get branch name (merge)
43 | if: github.event_name != 'pull_request'
44 | shell: bash
45 | run: echo "BRANCH=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_ENV
46 |
47 | - name: Get branch name (pull request)
48 | if: github.event_name == 'pull_request'
49 | shell: bash
50 | run: echo "BRANCH=$(echo ${GITHUB_HEAD_REF})" >> $GITHUB_ENV
51 |
52 | - name: github-sync and wait for gitlab pipeline
53 | shell: bash
54 | run: |
55 | chmod +x .github/workflows/github-sync.sh
56 | ./.github/workflows/github-sync.sh
57 | env:
58 | SRC_REPO: ${{ env.SRC_REPO }}
59 | DST_REPO: ${{ env.DST_REPO }}
60 | SRC_REPO_TOKEN_USER: ${{ env.SRC_REPO_TOKEN_USER }}
61 | DST_REPO_TOKEN_USER: ${{ env.DST_REPO_TOKEN_USER }}
62 | SRC_REPO_TOKEN: ${{ env.SRC_REPO_TOKEN }}
63 | DST_REPO_TOKEN: ${{ env.DST_REPO_TOKEN }}
64 |
--------------------------------------------------------------------------------
/.github/workflows/secret.env.pgp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/.github/workflows/secret.env.pgp
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | *.pyc
3 | __pycache__
4 | env
5 | env3
6 | db.sqlite3
7 | .DS_Store
8 | dump.rdb
9 | app/settings.py
10 | app/settings-*.py
11 | app/assets_detection_rules.py
12 | media/imports/*
13 | media/owners_docs/*
14 | media/policies/*
15 | media/reports/*
16 | var/tmp/db.json
17 | # **/migrations/[0-9]*.py
18 | .scannerwork
19 | .vscode
20 | staticfiles
21 | docker-compose.cloud.yml*
22 | docker-entrypoint.cloud.sh*
23 |
24 | # Exclude PRO files :)
25 | pro
26 | env3pro/
27 | .env-*
28 |
--------------------------------------------------------------------------------
/.gitlab-ci-old.yml:
--------------------------------------------------------------------------------
1 | ---
2 | stages:
3 | - build
4 | - test
5 | - clean
6 |
7 | build-job:
8 | stage: build
9 | before_script:
10 | - docker-compose --version
11 | script:
12 | - echo "Building Patrowl Manager on branch $CI_COMMIT_BRANCH"
13 | - docker-compose build --force-rm
14 |
15 | run-job:
16 | stage: test
17 | script:
18 | - echo "Running Patrowl Manager on branch $CI_COMMIT_BRANCH"
19 | - docker-compose up --detach
20 | needs: ["build-job"]
21 | after_script:
22 | - docker ps -a
23 |
24 | clean-job:
25 | stage: clean
26 | script:
27 | - echo "Stopping Patrowl Manager on branch $CI_COMMIT_BRANCH"
28 | - docker-compose stop
29 | needs: ["run-job"]
30 |
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | ---
2 | variables:
3 | CI_DEBUG_TRACE: "false"
4 | REGISTRY_URL: "registry.local:5000/patrowl/docker-images"
5 | STACK: "patrowl-manager"
6 | FOLDER_PATH: "."
7 | stages:
8 | - build
9 | - test
10 | - run
11 |
12 | build-push-manager:
13 | stage: build
14 | image:
15 | name: gcr.io/kaniko-project/executor:debug
16 | entrypoint: [""]
17 | script:
18 | - VERSION=$(cat $CI_PROJECT_DIR/$FOLDER_PATH/VERSION | cut -d " " -f1)
19 | - echo "Building Dockerfile $REGISTRY_URL/$STACK:$VERSION on branch $CI_COMMIT_BRANCH"
20 | - mkdir -p /kaniko/.docker
21 | - echo "{\"auths\":{$(echo \"${REGISTRY_URL}\" | cut -d '/' -f1)\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
22 | - cat /kaniko/.docker/config.json
23 | - /kaniko/executor --insecure --cleanup --context $CI_PROJECT_DIR/$FOLDER_PATH --dockerfile $CI_PROJECT_DIR/$FOLDER_PATH/Dockerfile --destination $REGISTRY_URL/$STACK:$VERSION
24 | rules:
25 | - if: $CI_COMMIT_BRANCH == "master"
26 | changes:
27 | - VERSION
28 |
29 | test-build-manager:
30 | stage: test
31 | image:
32 | name: gcr.io/kaniko-project/executor:debug
33 | entrypoint: [""]
34 | script:
35 | - echo "Building Dockerfile $REGISTRY_URL/$STACK:$VERSION on branch $CI_COMMIT_BRANCH"
36 | - /kaniko/executor --insecure --cleanup --context $CI_PROJECT_DIR/$FOLDER_PATH --dockerfile $CI_PROJECT_DIR/$FOLDER_PATH/Dockerfile --no-push
37 | rules:
38 | - if: $CI_COMMIT_BRANCH == "master"
39 | when: never
40 | - when: always
41 |
42 | # run-job-manager:
43 | # stage: run
44 | # image:
45 | # name: $REGISTRY_URL/$STACK:$VERSION
46 | # script:
47 | # - echo "Running $STACK on branch $CI_COMMIT_BRANCH"
48 | # rules:
49 | # - if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH
50 | # changes:
51 | # - $FOLDER_PATH/*
52 | # after_script:
53 | # - docker images | grep $STACK
54 | # - docker stop $(docker ps -a -q --filter ancestor=$REGISTRY_URL/$STACK:$VERSION)
55 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: xenial
2 | language: python
3 | python:
4 | - '3.7'
5 | install:
6 | - pip3 install -r requirements.txt
7 | script:
8 | - docker --version
9 | # - docker build --tag patrowl-manager .
10 | - docker-compose up -d
11 | # - docker-compose up -f docker-compose.with-engines.yml
12 | - pytest
13 | after_script:
14 | - docker images
15 |
--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
1 | Authors
2 | -------
3 | * Nicolas Mattiocco - Patrowl (@MaKyOtOx - nicolas@patrowl.io)
4 |
5 | Crontributors
6 | -------
7 | * CERT Banque de France (CERT-BDF)
8 | * Nicolas Béguier
9 |
10 | Copyright (C) 2018-2021 Nicolas MATTIOCCO
11 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.7-slim
2 | MAINTAINER Patrowl.io "getsupport@patrowl.io"
3 | LABEL Name="PatrowlManager" Version="1.8.4"
4 |
5 | ENV PYTHONUNBUFFERED 1
6 | ARG arg_http_proxy
7 | ENV http_proxy $arg_http_proxy
8 | ENV https_proxy $arg_http_proxy
9 |
10 | RUN mkdir -p /opt/patrowl-manager/
11 | WORKDIR /opt/patrowl-manager/
12 |
13 | RUN apt-get update -yq \
14 | && apt-get install -yq --no-install-recommends apt-utils python3 python3-pip libmagic-dev python3-dev gcc wget \
15 | && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \
16 | && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
17 |
18 | ADD ./requirements.txt /root/
19 |
20 | #RUN python --version \
21 | # && pip3 install virtualenv \
22 | # && virtualenv env3 \
23 | # && /opt/patrowl-manager/env3/bin/pip3 install --no-cache-dir -r /root/requirements.txt
24 |
25 | RUN python --version \
26 | && pip3 install --trusted-host pypi.org --trusted-host files.pythonhosted.org --trusted-host pypi.python.org --default-timeout=100 virtualenv \
27 | && virtualenv env3 \
28 | && /opt/patrowl-manager/env3/bin/pip3 install --trusted-host pypi.org --trusted-host files.pythonhosted.org --trusted-host pypi.python.org --default-timeout=100 --no-cache-dir -r /root/requirements.txt
29 |
30 | COPY . /opt/patrowl-manager/
31 | COPY app/settings.py.sample /opt/patrowl-manager/app/settings.py
32 | COPY app/assets_detection_rules.py.sample /opt/patrowl-manager/app/assets_detection_rules.py
33 |
34 | EXPOSE 8003
35 | ENTRYPOINT ["/opt/patrowl-manager/docker-entrypoint.sh"]
36 | CMD ["run"]
37 |
--------------------------------------------------------------------------------
/Dockerfile_nginx:
--------------------------------------------------------------------------------
1 | FROM nginx:latest
2 |
3 | MAINTAINER Patrowl.io "getsupport@patrowl.io"
4 | LABEL Name="Patrowl Manager - Nginx" Version="1.0.0"
5 |
6 | ADD /static /static
7 | ADD /media /media
8 |
9 | WORKDIR /
10 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | - [Security Policy](#security-policy)
2 | - [Supported Versions](#supported-versions)
3 | - [Reporting a Vulnerability](#reporting-a-vulnerability)
4 |
5 | # Security Policy
6 |
7 | ## Supported Versions
8 |
9 | We release patches for security vulnerabilities. Which versions are eligible
10 | receiving such patches depend on the CVSS v3.0 Rating:
11 |
12 | | CVSS v3.0 | Supported Versions |
13 | | --------- | ----------------------------------------- |
14 | | 9.0-10.0 | Releases within the previous three months |
15 | | 4.0-8.9 | Most recent release |
16 |
17 | ## Reporting a Vulnerability
18 |
19 | Please report (suspected) security vulnerabilities to
20 | **[security@patrowl.io](mailto:security@patrowl.io)**. You will receive a response from
21 | us within 48 hours. If the issue is confirmed, we will release a patch as soon
22 | as possible depending on complexity but historically within a few days.
23 |
--------------------------------------------------------------------------------
/VERSION:
--------------------------------------------------------------------------------
1 | 1.8.4 // Community Edition
2 |
--------------------------------------------------------------------------------
/app/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import absolute_import, unicode_literals
3 | from .celery import app as celery_app
4 |
5 | __all__ = ('celery_app',)
6 |
--------------------------------------------------------------------------------
/app/assets_detection_rules.py.sample:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | ASSET_DETECTION_RULES = [
3 | {
4 | "name": "New urls from NMAP report (HTTP)",
5 | "datatype": "url",
6 | "group_name": "New URLS",
7 | "filters": [
8 | {
9 | "asset_name__startswith": "http://",
10 | }, {
11 | "engine_type": "NMAP",
12 | "title__startswith": "Service 'http' is running"
13 | }, {
14 | "engine_type": "NMAP",
15 | "title__startswith": "Port 'tcp/80' is open"
16 | },
17 | ],
18 | "allowed_datatypes": ["ip", "domain", "fqdn"],
19 | "output_pattern": "http://__asset__"
20 | },
21 | {
22 | "name": "New urls from NMAP report (HTTPS)",
23 | "datatype": "url",
24 | "group_name": "New URLS",
25 | "filters": [
26 | {
27 | "asset_name__startswith": "https://",
28 | }, {
29 | "engine_type": "NMAP",
30 | "title__startswith": "Service 'https' is running"
31 | }, {
32 | "engine_type": "NMAP",
33 | "title__startswith": "Port 'tcp/443' is open"
34 | },
35 | ],
36 | "allowed_datatypes": ["ip", "domain", "fqdn"],
37 | "output_pattern": "https://__asset__"
38 | },
39 | ]
40 |
--------------------------------------------------------------------------------
/app/celery.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import os
4 | from celery import Celery
5 | from django.conf import settings
6 | from kombu import Exchange, Queue
7 |
8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings')
9 |
10 | # set the default Django settings module for the 'celery' program.
11 | app = Celery('app', broker=settings.BROKER_URL)
12 | app.config_from_object('django.conf:settings', namespace='CELERY')
13 | app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
14 |
15 | # app.conf.broker_transport_options = {
16 | # 'max_retries': 3,
17 | # 'interval_start': 0,
18 | # 'interval_step': 0.2,
19 | # 'interval_max': 0.2,
20 | # }
21 |
22 | #app.conf.timezone = os.environ.get('PATROWL_TZ', 'Europe/Paris')
23 |
24 | app.conf.task_queues = (
25 | # Default Queue / administrative purposes
26 | Queue('default', Exchange('default'), routing_key='default'),
27 | Queue('scan', Exchange('scan'), routing_key='scan'),
28 | Queue('scanmgt', Exchange('scanmgt'), routing_key='scanmgt'),
29 | )
30 | app.conf.task_default_queue = 'default'
31 | app.conf.task_default_exchange = 'default'
32 | app.conf.task_default_exchange_type = 'direct'
33 | app.conf.task_default_routing_key = 'default'
34 |
35 | app.conf.enable_utc = False
36 |
--------------------------------------------------------------------------------
/app/context_processors.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 |
3 |
4 | def selected_settings(request):
5 | r = {
6 | 'PATROWL_VERSION': settings.PATROWL_VERSION,
7 | 'PATROWL_REFRESH_ENGINE': settings.PATROWL_REFRESH_ENGINE,
8 | 'PRO_EDITION': settings.PRO_EDITION,
9 | 'LOGOUT_URL': settings.LOGOUT_URL
10 | }
11 |
12 | if hasattr(settings, 'LOGIN_SSO_URL'):
13 | r.update({'LOGIN_SSO_URL': settings.LOGIN_SSO_URL})
14 |
15 | return r
16 |
--------------------------------------------------------------------------------
/app/views.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import render
2 |
3 |
4 | # HTTP Error 400
5 | def custom_bad_request(request, exception=None):
6 | response = render(request, 'errors/400.html', {})
7 | response.status_code = 400
8 |
9 | return response
10 |
11 |
12 | # HTTP Error 403
13 | def custom_permission_denied(request, exception=None):
14 | response = render(request, 'errors/403.html', {})
15 | response.status_code = 403
16 |
17 | return response
18 |
19 |
20 | # HTTP Error 404
21 | def custom_page_not_found(request, exception):
22 | response = render(request, 'errors/404.html', {})
23 | response.status_code = 404
24 |
25 | return response
26 |
27 |
28 | # HTTP Error 500
29 | def custom_error(request, exception=None):
30 | response = render(request, 'errors/500.html', {})
31 | response.status_code = 500
32 |
33 | return response
34 |
--------------------------------------------------------------------------------
/app/wsgi.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import os
4 | from django.core.wsgi import get_wsgi_application
5 |
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings")
7 |
8 | application = get_wsgi_application()
9 |
--------------------------------------------------------------------------------
/assets/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/assets/__init__.py
--------------------------------------------------------------------------------
/assets/admin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.contrib import admin
4 |
5 | # Register your models here.
6 | from .models import (
7 | Asset, AssetGroup, DynamicAssetGroup,
8 | AssetOwner, AssetOwnerContact, AssetOwnerDocument, AssetCategory
9 | )
10 |
11 | admin.site.register(Asset)
12 | admin.site.register(AssetGroup)
13 | admin.site.register(DynamicAssetGroup)
14 | admin.site.register(AssetOwner)
15 | admin.site.register(AssetOwnerContact)
16 | admin.site.register(AssetOwnerDocument)
17 | admin.site.register(AssetCategory)
18 |
--------------------------------------------------------------------------------
/assets/apps.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | from django.apps import AppConfig
4 |
5 |
6 | class AssetsConfig(AppConfig):
7 | name = 'assets'
8 |
--------------------------------------------------------------------------------
/assets/migrations/0002_auto_20200616_1735.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.13 on 2020-06-16 15:35
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('users', '0002_auto_20200616_1704'),
11 | ('assets', '0001_initial'),
12 | ]
13 |
14 | operations = [
15 | migrations.RemoveField(
16 | model_name='asset',
17 | name='owner',
18 | ),
19 | migrations.AddField(
20 | model_name='asset',
21 | name='team',
22 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.Team'),
23 | ),
24 | ]
25 |
--------------------------------------------------------------------------------
/assets/migrations/0003_asset_owner.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.13 on 2020-06-16 15:38
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12 | ('assets', '0002_auto_20200616_1735'),
13 | ]
14 |
15 | operations = [
16 | migrations.AddField(
17 | model_name='asset',
18 | name='owner',
19 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
20 | ),
21 | ]
22 |
--------------------------------------------------------------------------------
/assets/migrations/0004_auto_20200617_0958.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.13 on 2020-06-17 07:58
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('users', '0002_auto_20200616_1704'),
10 | ('assets', '0003_asset_owner'),
11 | ]
12 |
13 | operations = [
14 | migrations.RemoveField(
15 | model_name='asset',
16 | name='team',
17 | ),
18 | migrations.AddField(
19 | model_name='asset',
20 | name='teams',
21 | field=models.ManyToManyField(to='users.Team'),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/assets/migrations/0005_auto_20200617_1011.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.13 on 2020-06-17 08:11
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('assets', '0004_auto_20200617_0958'),
12 | ]
13 |
14 | operations = [
15 | migrations.AlterField(
16 | model_name='asset',
17 | name='owner',
18 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
19 | ),
20 | migrations.AlterField(
21 | model_name='asset',
22 | name='teams',
23 | field=models.ManyToManyField(blank=True, to='users.Team'),
24 | ),
25 | ]
26 |
--------------------------------------------------------------------------------
/assets/migrations/0006_assetgroup_teams.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.13 on 2020-06-17 08:27
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('users', '0002_auto_20200616_1704'),
10 | ('assets', '0005_auto_20200617_1011'),
11 | ]
12 |
13 | operations = [
14 | migrations.AddField(
15 | model_name='assetgroup',
16 | name='teams',
17 | field=models.ManyToManyField(blank=True, to='users.Team'),
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/assets/migrations/0007_auto_20200709_0007.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.13 on 2020-07-08 22:07
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('assets', '0006_assetgroup_teams'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='asset',
15 | name='name',
16 | field=models.TextField(max_length=2048),
17 | ),
18 | migrations.AlterField(
19 | model_name='asset',
20 | name='value',
21 | field=models.TextField(max_length=2048, unique=True),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/assets/migrations/0008_asset_exposure.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.16 on 2020-09-21 09:40
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('assets', '0007_auto_20200709_0007'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='asset',
15 | name='exposure',
16 | field=models.CharField(choices=[('external', 'External'), ('internal', 'Internal'), ('restricted', 'Restricted')], default='external', max_length=16),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/assets/migrations/0009_auto_20200921_1151.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.16 on 2020-09-21 09:51
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('assets', '0008_asset_exposure'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='asset',
15 | name='exposure',
16 | field=models.CharField(choices=[('unknown', 'Unknown'), ('external', 'External'), ('internal', 'Internal'), ('restricted', 'Restricted')], default='unknown', max_length=16),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/assets/migrations/0010_auto_20210402_1439.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.18 on 2021-04-02 12:39
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('assets', '0009_auto_20200921_1151'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='assetgroup',
15 | name='categories',
16 | field=models.ManyToManyField(blank=True, to='assets.AssetCategory'),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/assets/migrations/0011_auto_20211123_1140.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.24 on 2021-11-23 10:40
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('assets', '0010_auto_20210402_1439'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='assetgroup',
15 | name='criticity',
16 | field=models.CharField(choices=[('low', 'low'), ('medium', 'medium'), ('high', 'high')], default='low', max_length=10),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/assets/migrations/0012_dynamicassetgroup.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.25 on 2021-12-21 15:00
2 |
3 | import assets.models
4 | from django.conf import settings
5 | import django.contrib.postgres.fields.jsonb
6 | from django.db import migrations, models
7 | import django.db.models.deletion
8 | import django.utils.timezone
9 |
10 |
11 | class Migration(migrations.Migration):
12 |
13 | dependencies = [
14 | ('users', '0005_profile_is_delegated'),
15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
16 | ('assets', '0011_auto_20211123_1140'),
17 | ]
18 |
19 | operations = [
20 | migrations.CreateModel(
21 | name='DynamicAssetGroup',
22 | fields=[
23 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
24 | ('name', models.CharField(max_length=256, unique=True)),
25 | ('criticity', models.CharField(choices=[('low', 'low'), ('medium', 'medium'), ('high', 'high')], default='low', max_length=10)),
26 | ('risk_level', django.contrib.postgres.fields.jsonb.JSONField(default=assets.models.get_default_risk_level)),
27 | ('description', models.CharField(blank=True, max_length=256, null=True)),
28 | ('created_at', models.DateTimeField(default=django.utils.timezone.now)),
29 | ('updated_at', models.DateTimeField(default=django.utils.timezone.now)),
30 | ('owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
31 | ('tags', models.ManyToManyField(blank=True, to='assets.AssetCategory')),
32 | ('teams', models.ManyToManyField(blank=True, to='users.Team')),
33 | ],
34 | options={
35 | 'db_table': 'asset_groups_dynamic',
36 | },
37 | ),
38 | ]
39 |
--------------------------------------------------------------------------------
/assets/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/assets/migrations/__init__.py
--------------------------------------------------------------------------------
/assets/serializers.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from rest_framework import serializers, generics
4 | from common.utils.pagination import StandardResultsSetPagination
5 | from django.utils.translation import gettext_lazy as _
6 | from django_filters import rest_framework as filters
7 | from django_filters import FilterSet, OrderingFilter
8 | from .models import Asset, AssetGroup
9 |
10 |
11 | class AssetSerializer(serializers.ModelSerializer):
12 | class Meta:
13 | model = Asset
14 | fields = ('id', 'value', 'name', 'type', 'owner', 'description',
15 | 'criticity', 'status', 'created_at', 'updated_at', 'teams', 'exposure')
16 |
17 |
18 | class AssetFilter(FilterSet):
19 | sorted_by = OrderingFilter(
20 | # tuple-mapping retains order
21 | choices=(
22 | ('value', _('Value')),
23 | ('-value', _('Value (desc)')),
24 | ('name', _('Name')),
25 | ('-name', _('Name (desc)')),
26 | ('criticity', _('Criticity')),
27 | ('-criticity', _('Criticity (desc)')),
28 | ('type', _('Type')),
29 | ('-type', _('Type (desc)')),
30 | ('exposure', _('Exposure')),
31 | ('-exposure', _('Exposure (desc)')),
32 | )
33 | )
34 |
35 | class Meta:
36 | model = Asset
37 | fields = {
38 | 'name': ['icontains'],
39 | 'value': ['icontains'],
40 | 'description': ['icontains'],
41 | }
42 |
43 |
44 | class AssetList(generics.ListAPIView):
45 | serializer_class = AssetSerializer
46 | filter_backends = (filters.DjangoFilterBackend,)
47 | filterset_class = AssetFilter
48 | filterset_fields = ('id', 'name', 'value', 'criticity', 'type')
49 | pagination_class = StandardResultsSetPagination
50 |
51 | def get_queryset(self):
52 | return Asset.objects.for_user(self.request.user).all().order_by('value')
53 |
54 |
55 | class AssetGroupSerializer(serializers.ModelSerializer):
56 | class Meta:
57 | model = AssetGroup
58 | fields = ('id', 'name', 'owner', 'description', 'assets',
59 | 'criticity', 'created_at', 'updated_at', 'teams')
60 |
61 |
62 | class AssetGroupFilter(FilterSet):
63 | sorted_by = OrderingFilter(
64 | choices=(
65 | ('name', _('Name')),
66 | ('-name', _('Name (desc)')),
67 | ('criticity', _('Criticity')),
68 | ('-criticity', _('Criticity (desc)')),
69 | )
70 | )
71 |
72 | class Meta:
73 | model = AssetGroup
74 | fields = {
75 | 'name': ['icontains'],
76 | 'description': ['icontains'],
77 | }
78 |
79 |
80 | class AssetGroupList(generics.ListAPIView):
81 | serializer_class = AssetGroupSerializer
82 | filter_backends = (filters.DjangoFilterBackend,)
83 | filterset_class = AssetGroupFilter
84 | filterset_fields = ('id', 'name', 'criticity')
85 | pagination_class = StandardResultsSetPagination
86 |
87 | def get_queryset(self):
88 | return AssetGroup.objects.for_user(self.request.user).all().order_by('name')
89 |
--------------------------------------------------------------------------------
/assets/templates/add-asset.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
8 |
9 |
45 |
46 |
47 | {% if messages %}
48 |
49 | {% for message in messages %}
50 | - {{ message }}
51 | {% endfor %}
52 |
53 | {% endif %}
54 |
55 |
56 | {% endblock %}
57 |
--------------------------------------------------------------------------------
/assets/templates/add-assets-bulk.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
8 |
9 |
42 |
43 | {% endblock %}
44 |
--------------------------------------------------------------------------------
/assets/templates/delete-asset-group.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
5 |
16 |
17 |
18 | {% if messages %}
19 |
20 | {% for message in messages %}
21 | - {{ message }}
22 | {% endfor %}
23 |
24 | {% endif %}
25 |
26 |
27 | {% endblock %}
28 |
--------------------------------------------------------------------------------
/assets/templates/delete-asset-owner.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
5 |
16 |
17 |
18 | {% if messages %}
19 |
20 | {% for message in messages %}
21 | - {{ message }}
22 | {% endfor %}
23 |
24 | {% endif %}
25 |
26 |
27 | {% endblock %}
28 |
--------------------------------------------------------------------------------
/assets/templates/delete-asset.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
5 |
16 |
17 |
18 | {% if messages %}
19 |
20 | {% for message in messages %}
21 | - {{ message }}
22 | {% endfor %}
23 |
24 | {% endif %}
25 |
26 |
27 | {% endblock %}
28 |
--------------------------------------------------------------------------------
/assets/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase, Client
2 |
3 |
4 | class AssetTestCase(TestCase):
5 |
6 | def add_asset_test(self):
7 | print("TEST CASE: add_asset_test")
8 | c = Client()
9 |
10 | print("TEST CASE: testing with GET method")
11 | r = c.get('http://127.0.0.1:8000/assets/add?value=testingasset')
12 | print(r.json())
13 |
14 | print("TEST CASE: testing with POST method")
15 | r = c.post('http://127.0.0.1:8000/assets/add', {
16 | "value": "8.8.8.8",
17 | "name": "DNS Google (A)",
18 | "type": "ipv4",
19 | "owner": "nicolas",
20 | "description": "Google DNS Server",
21 | "status": "up"
22 | })
23 | print(r.json())
24 |
--------------------------------------------------------------------------------
/assets/utils.py:
--------------------------------------------------------------------------------
1 | from users.models import Team
2 | from assets.models import Asset, AssetGroup
3 |
4 |
5 | def _get_allowed_team(team_name, user):
6 | team = None
7 | if user.is_superuser:
8 | team = Team.objects.filter(name__iexact=team_name).first()
9 | else:
10 | team = user.users_team.filter(name__iexact=team_name).first()
11 |
12 | return team
13 |
14 |
15 | def _add_new_asset(metadata):
16 | """
17 | Add new asset.
18 |
19 | {
20 | "datatype": rule['datatype'],
21 | "rule_name": rule['name'],
22 | "group_name": rule['group_name'],
23 | "asset_value": asset_value,
24 | "original_asset_value": self.asset.value,
25 | "asset_teams": self.asset.teams.all(),
26 | }
27 | """
28 |
29 | try:
30 | # Create the asset
31 | asset = Asset.objects.create(
32 | value=metadata["asset_value"],
33 | name=f"{metadata['asset_value']} (Auto-created)",
34 | type=metadata["datatype"],
35 | owner=metadata["owner"],
36 | description=f"Auto-created by finding evaluation rule ({metadata['rule_name']}) from asset {metadata['original_asset_value']}",
37 | )
38 |
39 | # Add related Teams
40 | for team in metadata["asset_teams"]:
41 | asset.teams.add(team)
42 |
43 | # Add it to a group
44 | asset_group = AssetGroup.objects.filter(name=metadata['group_name']).first()
45 | if asset_group is None:
46 | # Create it first
47 | asset_group = AssetGroup.objects.create(
48 | name=metadata['group_name'],
49 | owner=metadata["owner"],
50 | description=f"Auto-created by finding evaluation rule ({metadata['rule_name']})"
51 | )
52 |
53 | for team in metadata["asset_teams"]:
54 | asset_group.teams.add(team)
55 |
56 | # Add this asset to the group
57 | asset_group.assets.add(asset)
58 |
59 | # print(asset)
60 | except Exception:
61 | return None
62 |
63 | return asset.id
64 |
--------------------------------------------------------------------------------
/common/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/common/__init__.py
--------------------------------------------------------------------------------
/common/utils/__init__.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.decorators import user_passes_test
2 |
3 |
4 | def pro_permission_required(*args):
5 | """
6 | Check user has any of the given permissions.
7 |
8 | Permission required can not be used in its place as that takes only a
9 | single permission.
10 | """
11 | def test_func(user):
12 | from django.conf import settings
13 | if not settings.PRO_EDITION or user.is_superuser:
14 | return True
15 | for perm in args:
16 | if user.has_perm(perm):
17 | return True
18 | return False
19 | return user_passes_test(test_func)
20 |
21 |
22 | def pro_group_required(*group_names):
23 | """Require user membership in at least one of the groups passed in."""
24 | def in_groups(u):
25 | from django.conf import settings
26 | if not settings.PRO_EDITION or u.is_superuser:
27 | return True
28 | if u.is_authenticated:
29 | if bool(u.groups.filter(name__in=group_names)):
30 | return True
31 | return False
32 | return user_passes_test(in_groups)
33 |
34 |
35 | def get_api_default_permissions(self):
36 | from rest_framework.decorators import permission_classes
37 | from rest_framework.permissions import IsAdminUser, IsAuthenticated
38 | if self.action == 'list':
39 | permission_classes = [IsAuthenticated]
40 | else:
41 | permission_classes = [IsAdminUser]
42 | return [permission() for permission in permission_classes]
43 |
44 |
45 | def chunked_queryset(queryset, chunk_size):
46 | """ Slice a queryset into chunks. """
47 |
48 | start_pk = 0
49 | queryset = queryset.order_by('pk')
50 |
51 | while True:
52 | # No entry left
53 | if not queryset.filter(pk__gt=start_pk).exists():
54 | break
55 |
56 | try:
57 | # Fetch chunk_size entries if possible
58 | end_pk = queryset.filter(pk__gt=start_pk).values_list(
59 | 'pk', flat=True)[chunk_size - 1]
60 |
61 | # Fetch rest entries if less than chunk_size left
62 | except IndexError:
63 | end_pk = queryset.values_list('pk', flat=True).last()
64 |
65 | yield queryset.filter(pk__gt=start_pk).filter(pk__lte=end_pk)
66 |
67 | start_pk = end_pk
68 |
--------------------------------------------------------------------------------
/common/utils/cpe.py:
--------------------------------------------------------------------------------
1 | from cpe import CPE
2 |
3 |
4 | def extract_cpe(cpe_vector):
5 | """Extract vendor and product strings from CPE vector."""
6 | vendor = None
7 | product = None
8 |
9 | try:
10 | c = CPE(cpe_vector)
11 | vendor = c.get_vendor()[0]
12 | product = c.get_product()[0]
13 | # print("-->", c, vendor, product)
14 | except Exception as e:
15 | print(cpe_vector, e)
16 |
17 | return vendor, product
18 |
--------------------------------------------------------------------------------
/common/utils/date.py:
--------------------------------------------------------------------------------
1 | import datetime
2 |
3 |
4 | def validate_datetime(date_text):
5 | # print(date_text)
6 | try:
7 | return datetime.datetime.strptime(date_text, '%Y-%m-%d %H:%M:%S')
8 | except ValueError:
9 | return False
10 |
--------------------------------------------------------------------------------
/common/utils/encoding.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from datetime import date, datetime
4 | from uuid import UUID
5 | import html.entities
6 |
7 |
8 | def unicode_escape(unistr):
9 | """
10 | Tidys up unicode entities into HTML friendly entities.
11 | Takes a unicode string as an argument
12 | Returns a unicode string
13 | """
14 |
15 | escaped = ""
16 | for char in unistr:
17 | if ord(char) in html.entities.codepoint2name:
18 | name = html.entities.codepoint2name.get(ord(char))
19 | entity = html.entities.name2codepoint.get(name)
20 | escaped += "" + str(entity)
21 |
22 | else:
23 | escaped += char
24 |
25 | return escaped
26 |
27 |
28 | def json_serial(obj):
29 | """JSON serializer for objects not serializable by default json code."""
30 | if isinstance(obj, (datetime, date)):
31 | return obj.isoformat()
32 | if isinstance(obj, UUID):
33 | # if the obj is uuid, we simply return the value of uuid
34 | return obj.hex
35 | raise TypeError("Type %s not serializable" % type(obj))
36 |
--------------------------------------------------------------------------------
/common/utils/pagination.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from rest_framework.pagination import PageNumberPagination
3 |
4 |
5 | class StandardResultsSetPagination(PageNumberPagination):
6 | page_size_query_param = 'limit'
7 |
--------------------------------------------------------------------------------
/common/utils/password.py:
--------------------------------------------------------------------------------
1 | import random
2 | import string
3 |
4 |
5 | def get_random_alphanumeric_string(length):
6 | letters_and_digits = string.ascii_letters + string.digits
7 | return ''.join((random.choice(letters_and_digits) for i in range(length)))
8 |
--------------------------------------------------------------------------------
/common/utils/settings.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from app import settings
4 |
5 | #
6 | # def is_restricted():
7 | # """Check if the instance is usage restricted."""
8 | # if "RESTRICTED_USAGE" in dir(settings) and settings.RESTRICTED_USAGE is True:
9 | # return True
10 | # return False
11 |
12 |
13 | def is_restricted():
14 | """Check if the instance run PRO edition."""
15 | if "PRO_EDITION" in dir(settings) and settings.PRO_EDITION is True:
16 | return True
17 | return False
18 |
--------------------------------------------------------------------------------
/docker-compose.with-engines.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 |
4 | rabbitmq:
5 | image: rabbitmq:3-alpine
6 | container_name: patrowl-rabbitmq
7 | environment:
8 | - RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS=-rabbit log_levels [{connection,error}]
9 | expose:
10 | - '5672'
11 |
12 | db:
13 | image: postgres:11-alpine
14 | restart: always
15 | container_name: patrowl-postgres
16 | environment:
17 | - POSTGRES_DB=patrowl_db
18 | - POSTGRES_USER=PATROWL_DB_USER
19 | - POSTGRES_PASSWORD=PATROWL_DB_PASSWD_TO_CHANGE
20 | expose:
21 | - '5432'
22 | volumes:
23 | - ./var/db/init_db.sql:/docker-entrypoint-initdb.d/init_db.sql
24 | # - ./pg_data:/var/lib/postgresql/data/
25 |
26 | web:
27 | container_name: patrowl-django
28 | build: .
29 | image: patrowl/patrowl-manager-community-edition:${PATROWL_MANAGER_VERSION}
30 | environment:
31 | - POSTGRES_HOST=db
32 | - DEBUG=False
33 | - RABBITMQ_HOSTNAME=rabbitmq:5672
34 | depends_on:
35 | - db
36 | - rabbitmq
37 | expose:
38 | - "8003"
39 | volumes:
40 | - ./staticfiles:/opt/patrowl-manager/staticfiles
41 | - ./media:/opt/patrowl-manager/media
42 | links:
43 | - db
44 | - rabbitmq
45 | entrypoint: ./docker-entrypoint.with-engines.sh
46 |
47 | nginx:
48 | image: nginx:stable-alpine
49 | container_name: patrowl-nginx
50 | ports:
51 | - "8083:8083"
52 | volumes:
53 | - ./staticfiles:/opt/patrowl-manager/staticfiles
54 | # - ./media:/opt/patrowl-manager/media
55 | - ./var/log:/opt/patrowl-manager/var/log
56 | - ./nginx_docker.conf:/etc/nginx/conf.d/default.conf
57 | depends_on:
58 | - web
59 |
60 | engine-nmap:
61 | image: patrowl/engine-nmap
62 | restart: always
63 | container_name: patrowl-nmap
64 | expose:
65 | - '5001'
66 |
67 | engine-sslscan:
68 | image: patrowl/engine-sslscan
69 | restart: always
70 | container_name: patrowl-sslscan
71 | expose:
72 | - '5014'
73 |
74 | engine-owl_dns:
75 | image: patrowl/engine-owl_dns
76 | restart: always
77 | container_name: patrowl-owl_dns
78 | expose:
79 | - '5006'
80 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 |
4 | rabbitmq:
5 | image: rabbitmq:3-alpine
6 | container_name: patrowlmanager-rabbitmq
7 | environment:
8 | - RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS=-rabbit log_levels [{connection,error}]
9 | expose:
10 | - '5672'
11 |
12 | db:
13 | image: postgres:11-alpine
14 | restart: always
15 | container_name: patrowlmanager-postgres
16 | environment:
17 | - POSTGRES_DB=patrowl_db
18 | - POSTGRES_USER=PATROWL_DB_USER
19 | - POSTGRES_PASSWORD=PATROWL_DB_PASSWD_TO_CHANGE
20 | expose:
21 | - '5432'
22 | volumes:
23 | - ./var/db/init_db.sql:/docker-entrypoint-initdb.d/init_db.sql
24 | # - ./pg_data:/var/lib/postgresql/data/
25 |
26 | web:
27 | container_name: patrowlmanager-django
28 | build: .
29 | image: patrowl/patrowl-manager-community-edition:${PATROWL_MANAGER_VERSION}
30 | environment:
31 | - POSTGRES_HOST=db
32 | - DEBUG=True
33 | - RABBITMQ_HOSTNAME=rabbitmq:5672
34 | depends_on:
35 | - db
36 | - rabbitmq
37 | expose:
38 | - "8003"
39 | volumes:
40 | - ./staticfiles:/opt/patrowl-manager/staticfiles
41 | - ./media:/opt/patrowl-manager/media
42 | links:
43 | - db
44 | - rabbitmq
45 |
46 | nginx:
47 | image: nginx:stable-alpine
48 | container_name: patrowlmanager-nginx
49 | ports:
50 | - "8083:8083"
51 | volumes:
52 | - ./staticfiles:/opt/patrowl-manager/staticfiles
53 | # - ./media:/opt/patrowl-manager/media
54 | - ./var/log:/opt/patrowl-manager/var/log
55 | - ./nginx_docker.conf:/etc/nginx/conf.d/default.conf
56 | depends_on:
57 | - web
58 |
--------------------------------------------------------------------------------
/docker-entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | export POSTGRES_HOST=${POSTGRES_HOST:-db}
3 | export POSTGRES_PORT=${POSTGRES_PORT:-5432}
4 | export RABBITMQ_HOST=${RABBITMQ_HOST:-rabbitmq}
5 | export RABBITMQ_PORT=${RABBITMQ_PORT:-5672}
6 | # export UPDATE_DB_SCHEMA=${UPDATE_DB_SCHEMA:-0}
7 |
8 | echo "[+] Wait for DB availability"
9 | while !
5 | engines
6 | policies
7 | add new policy
8 |
9 |
45 |
46 | {% endblock %}
47 |
--------------------------------------------------------------------------------
/engines/templates/add-engine.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
9 |
10 |
46 |
47 | {% endblock %}
48 |
--------------------------------------------------------------------------------
/engines/templates/add-scan-engine.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
5 | - engines
6 | - add new scan engine
7 |
8 |
9 |
46 |
47 |
68 |
69 | {% endblock %}
70 |
--------------------------------------------------------------------------------
/engines/templates/delete-engine-policy.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
5 |
16 |
17 | {% if messages %}
18 |
19 | {% for message in messages %}
20 | - {{ message }}
21 | {% endfor %}
22 |
23 | {% endif %}
24 |
25 |
26 | {% endblock %}
27 |
--------------------------------------------------------------------------------
/engines/templates/delete-engine.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
5 |
16 |
17 | {% if messages %}
18 |
19 | {% for message in messages %}
20 | - {{ message }}
21 | {% endfor %}
22 |
23 | {% endif %}
24 |
25 |
26 | {% endblock %}
27 |
--------------------------------------------------------------------------------
/engines/templates/delete-scan-engine.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
5 |
17 |
18 | {% if messages %}
19 |
20 | {% for message in messages %}
21 | - {{ message }}
22 | {% endfor %}
23 |
24 | {% endif %}
25 |
26 |
27 | {% endblock %}
28 |
--------------------------------------------------------------------------------
/engines/templates/edit-engine-policy.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
9 |
10 |
11 |
45 | {% if messages %}
46 |
47 | {% for message in messages %}
48 | - {{ message }}
49 | {% endfor %}
50 |
51 | {% endif %}
52 |
53 |
54 | {% endblock %}
55 |
--------------------------------------------------------------------------------
/engines/templates/edit-engine.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
8 |
9 |
10 |
44 | {% if messages %}
45 |
46 | {% for message in messages %}
47 | - {{ message }}
48 | {% endfor %}
49 |
50 | {% endif %}
51 |
52 |
53 | {% endblock %}
54 |
--------------------------------------------------------------------------------
/engines/templates/import-engine-policies.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
8 |
9 |
42 |
43 | {% endblock %}
44 |
--------------------------------------------------------------------------------
/engines/templates/info-scan-engine.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
5 |
6 | {% csrf_token %}
7 |
8 | {% for key, value in engine_infos.items %}
9 |
10 | - {{ key }}: {{ value }}
11 |
12 | {% endfor %}
13 |
14 | - Nb scans performed: {{ nb_scans }}
15 |
16 |
17 | - Current scans: {{ current_scans }}
18 |
19 |
22 |
23 |
24 | {% if messages %}
25 |
26 | {% for message in messages %}
27 | - {{ message }}
28 | {% endfor %}
29 |
30 | {% endif %}
31 |
32 | {% endblock %}
33 |
--------------------------------------------------------------------------------
/engines/tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.test import TestCase, Client
4 |
--------------------------------------------------------------------------------
/events/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/events/__init__.py
--------------------------------------------------------------------------------
/events/admin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.contrib import admin
4 | from .models import Event, Alert, AlertOverride
5 |
6 |
7 | class EventAdmin(admin.ModelAdmin):
8 | raw_id_fields = ('finding', 'rawfinding', 'scan',)
9 |
10 |
11 | admin.site.register(Event, EventAdmin)
12 | admin.site.register(Alert)
13 | admin.site.register(AlertOverride)
14 |
--------------------------------------------------------------------------------
/events/apis.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """REST-API definitions for Events and Alerts."""
3 |
4 | from django.http import JsonResponse
5 | from django.shortcuts import get_object_or_404
6 | from django.forms.models import model_to_dict
7 | # from common.utils.pagination import StandardResultsSetPagination
8 | from rest_framework.decorators import api_view
9 | # from rest_framework import viewsets
10 | # from django_filters import rest_framework as filters
11 | from .models import Event, Alert
12 | # from .serializers import AlertSerializer
13 |
14 |
15 | @api_view(['GET'])
16 | def list_events_api(request):
17 | """List last 100 events."""
18 | events = []
19 | for e in Event.objects.all().order_by('-id')[:100]:
20 | events.append(model_to_dict(e))
21 |
22 | return JsonResponse(events, safe=False)
23 |
24 |
25 | @api_view(['DELETE'])
26 | def delete_event_api(request, event_id):
27 | """Delete an event."""
28 | event = get_object_or_404(Event, id=event_id)
29 | event.delete()
30 |
31 | return JsonResponse({
32 | "status": "deleted",
33 | "message": "event '{}' deleted.".format(event_id)
34 | })
35 |
36 |
37 | @api_view(['POST'])
38 | def ack_alerts_api(request):
39 | """Acknowledge an alert."""
40 | alerts = request.data
41 | for alert_id in alerts:
42 | a = Alert.objects.filter(id=alert_id).first()
43 | if a is not None:
44 | a.status = "read"
45 | a.save()
46 |
47 | return JsonResponse({'status': 'success'})
48 |
49 |
50 | @api_view(['POST'])
51 | def archive_alerts_api(request):
52 | """Archive an alert."""
53 | alerts = request.data
54 | for alert_id in alerts:
55 | a = Alert.objects.filter(id=alert_id).first()
56 | if a is not None:
57 | a.status = "archived"
58 | a.save()
59 |
60 | return JsonResponse({'status': 'success'})
61 |
--------------------------------------------------------------------------------
/events/apps.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 | from django.apps import AppConfig
4 |
5 |
6 | class EventsConfig(AppConfig):
7 | name = 'events'
8 |
--------------------------------------------------------------------------------
/events/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.5 on 2019-09-17 23:07
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 | import django.utils.timezone
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | initial = True
11 |
12 | dependencies = [
13 | ('findings', '0001_initial'),
14 | ('scans', '0001_initial'),
15 | ]
16 |
17 | operations = [
18 | migrations.CreateModel(
19 | name='Event',
20 | fields=[
21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
22 | ('message', models.CharField(max_length=250)),
23 | ('description', models.TextField(default='n/a')),
24 | ('type', models.CharField(choices=[('CREATE', 'CREATE'), ('UPDATE', 'UPDATE'), ('DELETE', 'DELETE'), ('ERROR', 'ERROR'), ('NOTIFICATION', 'NOTIFICATION'), ('ALERT', 'ALERT'), ('UNSPECIFIED', 'UNSPECIFIED')], default='UNSPECIFIED', max_length=15)),
25 | ('severity', models.CharField(choices=[('INFO', 'INFO'), ('WARNING', 'WARNING'), ('ERROR', 'ERROR'), ('DEBUG', 'DEBUG')], default='INFO', max_length=10)),
26 | ('code', models.CharField(blank=True, max_length=10, null=True)),
27 | ('created_at', models.DateTimeField(default=django.utils.timezone.now)),
28 | ('updated_at', models.DateTimeField(default=django.utils.timezone.now)),
29 | ('finding', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='findings.Finding')),
30 | ('rawfinding', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='findings.RawFinding')),
31 | ('scan', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='scans.Scan')),
32 | ],
33 | options={
34 | 'db_table': 'events',
35 | },
36 | ),
37 | ]
38 |
--------------------------------------------------------------------------------
/events/migrations/0002_alert.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.12 on 2020-05-29 14:43
2 |
3 | from django.db import migrations, models
4 | import django.utils.timezone
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('events', '0001_initial'),
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='Alert',
16 | fields=[
17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('message', models.CharField(max_length=250)),
19 | ('severity', models.CharField(choices=[('info', 'info'), ('low', 'low'), ('medium', 'medium'), ('high', 'high'), ('critical', 'critical')], default='info', max_length=10)),
20 | ('status', models.CharField(choices=[('new', 'New'), ('read', 'Read'), ('archived', 'Archived')], default='new', max_length=10)),
21 | ('created_at', models.DateTimeField(default=django.utils.timezone.now)),
22 | ('updated_at', models.DateTimeField(default=django.utils.timezone.now)),
23 | ],
24 | options={
25 | 'db_table': 'alerts',
26 | },
27 | ),
28 | ]
29 |
--------------------------------------------------------------------------------
/events/migrations/0003_alert_metadata.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.12 on 2020-06-02 09:29
2 |
3 | import django.contrib.postgres.fields.jsonb
4 | from django.db import migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('events', '0002_alert'),
11 | ]
12 |
13 | operations = [
14 | migrations.AddField(
15 | model_name='alert',
16 | name='metadata',
17 | field=django.contrib.postgres.fields.jsonb.JSONField(default=dict),
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/events/migrations/0004_auto_20200602_1813.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.12 on 2020-06-02 16:13
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('events', '0003_alert_metadata'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='event',
16 | name='finding',
17 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='event_finding', to='findings.Finding'),
18 | ),
19 | migrations.AlterField(
20 | model_name='event',
21 | name='rawfinding',
22 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='event_rawfinding', to='findings.RawFinding'),
23 | ),
24 | migrations.AlterField(
25 | model_name='event',
26 | name='scan',
27 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='event_scan', to='scans.Scan'),
28 | ),
29 | ]
30 |
--------------------------------------------------------------------------------
/events/migrations/0005_auto_20200616_1631.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.13 on 2020-06-16 14:31
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('events', '0004_auto_20200602_1813'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='event',
16 | name='finding',
17 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='findings.Finding'),
18 | ),
19 | migrations.AlterField(
20 | model_name='event',
21 | name='rawfinding',
22 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='findings.RawFinding'),
23 | ),
24 | migrations.AlterField(
25 | model_name='event',
26 | name='scan',
27 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='scans.Scan'),
28 | ),
29 | ]
30 |
--------------------------------------------------------------------------------
/events/migrations/0006_auto_20200624_0923.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.13 on 2020-06-24 07:23
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('users', '0004_remove_profile_role'),
12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
13 | ('events', '0005_auto_20200616_1631'),
14 | ]
15 |
16 | operations = [
17 | migrations.AddField(
18 | model_name='alert',
19 | name='owner',
20 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
21 | ),
22 | migrations.AddField(
23 | model_name='alert',
24 | name='teams',
25 | field=models.ManyToManyField(blank=True, to='users.Team'),
26 | ),
27 | ]
28 |
--------------------------------------------------------------------------------
/events/migrations/0007_auditlog.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.13 on 2020-07-20 16:29
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 | import django.utils.timezone
7 |
8 |
9 | class Migration(migrations.Migration):
10 |
11 | dependencies = [
12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
13 | ('events', '0006_auto_20200624_0923'),
14 | ]
15 |
16 | operations = [
17 | migrations.CreateModel(
18 | name='AuditLog',
19 | fields=[
20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21 | ('message', models.TextField(default='n/a')),
22 | ('scope', models.CharField(choices=[('asset', 'Asset'), ('scan', 'Scan'), ('finding', 'Finding'), ('user', 'User'), ('other', 'Other')], default='other', max_length=10)),
23 | ('created_at', models.DateTimeField(default=django.utils.timezone.now)),
24 | ('owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
25 | ],
26 | options={
27 | 'db_table': 'audit_logs',
28 | },
29 | ),
30 | ]
31 |
--------------------------------------------------------------------------------
/events/migrations/0008_auditlog_owner_username.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.13 on 2020-07-20 16:31
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('events', '0007_auditlog'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='auditlog',
15 | name='owner_username',
16 | field=models.TextField(default='n/a'),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/events/migrations/0009_auditlog_type.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.14 on 2020-07-21 09:32
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('events', '0008_auditlog_owner_username'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='auditlog',
15 | name='type',
16 | field=models.CharField(default='n-a', max_length=250),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/events/migrations/0010_auditlog_metadata.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.14 on 2020-07-21 15:42
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('events', '0009_auditlog_type'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='auditlog',
15 | name='metadata',
16 | field=models.TextField(default='n/a'),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/events/migrations/0011_auto_20200723_1528.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.14 on 2020-07-23 13:28
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('events', '0010_auditlog_metadata'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='auditlog',
15 | name='metadata',
16 | field=models.TextField(default=''),
17 | ),
18 | migrations.AlterField(
19 | model_name='auditlog',
20 | name='scope',
21 | field=models.CharField(choices=[('asset', 'Asset'), ('scan', 'Scan'), ('engine', 'Engine'), ('finding', 'Finding'), ('user', 'User'), ('rule', 'Rule'), ('setting', 'Setting'), ('other', 'Other')], default='other', max_length=10),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/events/migrations/0012_alert_type.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.24 on 2021-11-18 09:55
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('events', '0011_auto_20200723_1528'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='alert',
15 | name='type',
16 | field=models.CharField(choices=[('other', 'Other'), ('new_finding', 'New Finding'), ('missing_finding', 'Missing Finding'), ('reopened_finding', 'Reopened Finding')], default='other', max_length=20),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/events/migrations/0013_alertoverride.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.25 on 2021-12-17 08:54
2 |
3 | import django.contrib.postgres.fields.jsonb
4 | from django.db import migrations, models
5 | import django.utils.timezone
6 | import events.models
7 |
8 |
9 | class Migration(migrations.Migration):
10 |
11 | dependencies = [
12 | ('events', '0012_alert_type'),
13 | ]
14 |
15 | operations = [
16 | migrations.CreateModel(
17 | name='AlertOverride',
18 | fields=[
19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20 | ('name', models.CharField(max_length=256)),
21 | ('enabled', models.BooleanField(default=True)),
22 | ('engine_type', models.CharField(max_length=20)),
23 | ('action', models.CharField(choices=[('disable-alert', 'Disable alert'), ('set-severity', 'Set custom severity')], default='disable-alert', max_length=32)),
24 | ('params', django.contrib.postgres.fields.jsonb.JSONField(default=events.models.get_default_alert_override_params)),
25 | ('created_at', models.DateTimeField(default=django.utils.timezone.now)),
26 | ('updated_at', models.DateTimeField(default=django.utils.timezone.now)),
27 | ],
28 | options={
29 | 'db_table': 'alert_override',
30 | },
31 | ),
32 | ]
33 |
--------------------------------------------------------------------------------
/events/migrations/0014_alertoverride_teams.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.25 on 2021-12-17 08:55
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('users', '0005_profile_is_delegated'),
10 | ('events', '0013_alertoverride'),
11 | ]
12 |
13 | operations = [
14 | migrations.AddField(
15 | model_name='alertoverride',
16 | name='teams',
17 | field=models.ManyToManyField(blank=True, to='users.Team'),
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/events/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/events/migrations/__init__.py
--------------------------------------------------------------------------------
/events/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers, generics
2 | from .models import Event, Alert
3 |
4 |
5 | class EventSerializer(serializers.ModelSerializer):
6 | class Meta:
7 | model = Event
8 | # fields = '__all__'
9 | fields = [
10 | 'id',
11 | 'message',
12 | 'description',
13 | 'type',
14 | 'severity',
15 | 'code',
16 | 'created_at',
17 | 'updated_at',
18 | 'scan_id',
19 | 'finding_id',
20 | 'rawfinding_id'
21 | ]
22 |
23 |
24 | class EventListCreate(generics.ListCreateAPIView):
25 | queryset = Event.objects.all()
26 | serializer_class = EventSerializer
27 |
28 |
29 | class AlertSerializer(serializers.ModelSerializer):
30 | class Meta:
31 | model = Alert
32 | # fields = '__all__'
33 | fields = [
34 | 'id',
35 | 'message',
36 | 'severity',
37 | 'status',
38 | 'metadata',
39 | 'created_at',
40 | 'updated_at',
41 | ]
42 |
43 |
44 | class AlertListCreate(generics.ListCreateAPIView):
45 | # queryset = Alert.objects.all()
46 | serializer_class = AlertSerializer
47 |
48 | def get_queryset(self):
49 | return Alert.objects.for_user(self.request.user).all().order_by('-updated_at')
50 |
--------------------------------------------------------------------------------
/events/templates/delete-event.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
5 |
17 |
18 | {% if messages %}
19 |
20 | {% for message in messages %}
21 | - {{ message }}
22 | {% endfor %}
23 |
24 | {% endif %}
25 |
26 |
27 | {% endblock %}
28 |
--------------------------------------------------------------------------------
/events/tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.test import TestCase
4 |
5 | # Create your tests here.
6 |
--------------------------------------------------------------------------------
/events/urls.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.urls import path
4 | from django.conf.urls import url
5 | from . import views, apis, serializers
6 |
7 |
8 | urlpatterns = [
9 | # API Views
10 | # ex: /events/api/v1/list
11 | url(r'^api/v1/list$',
12 | apis.list_events_api, name='list_events_api'),
13 | # ex: /events/api/v1/delete/2
14 | url(r'^api/v1/delete/(?P[0-9]+)$',
15 | apis.delete_event_api, name='delete_event_api'),
16 | url(r'^api/v1/alerts/ack$', apis.ack_alerts_api, name='ack_alerts_api'),
17 | url(r'^api/v1/alerts/archive$', apis.archive_alerts_api, name='archive_alerts_api'),
18 |
19 | # WEB Views
20 | # ex: /events/delete [POST]
21 | url(r'^delete/(?P[0-9]+)$',
22 | views.delete_event_view, name='delete_event_view'),
23 | ]
24 |
25 | # Serialized data
26 | urlpatterns += [
27 | path('alerts/list', views.list_alerts_view, name='list_alerts_view'),
28 | path('api/list', serializers.EventListCreate.as_view()),
29 | path('api/alerts/list', serializers.AlertListCreate.as_view()),
30 | ]
31 |
--------------------------------------------------------------------------------
/findings/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/findings/__init__.py
--------------------------------------------------------------------------------
/findings/admin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.contrib import admin
4 | from .models import Finding, RawFinding, FindingOverride
5 |
6 |
7 | class FindingAdmin(admin.ModelAdmin):
8 | raw_id_fields = ('raw_finding', 'asset', 'scan',)
9 |
10 |
11 | class RawFindingAdmin(admin.ModelAdmin):
12 | raw_id_fields = ('asset', 'scan',)
13 |
14 |
15 | admin.site.register(Finding, FindingAdmin)
16 | admin.site.register(RawFinding, RawFindingAdmin)
17 | admin.site.register(FindingOverride)
18 |
--------------------------------------------------------------------------------
/findings/apps.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from __future__ import unicode_literals
4 |
5 | from django.apps import AppConfig
6 |
7 |
8 | class FindingsConfig(AppConfig):
9 | name = 'findings'
10 |
--------------------------------------------------------------------------------
/findings/forms.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import os
4 | from django import forms
5 | from django.core.exceptions import ValidationError
6 | from .models import Finding, FINDING_SEVERITIES
7 |
8 | ENGINE_TYPES = (
9 | ('json', 'json'),
10 | ('nessus', 'Nessus'),
11 | )
12 |
13 |
14 | def validate_file_extension(value):
15 | ext = os.path.splitext(value.name)[1]
16 | valid_extensions = ['.xml', '.nessus', '.json']
17 | if ext not in valid_extensions:
18 | raise ValidationError(u'File not supported!')
19 |
20 |
21 | class ImportFindingsForm(forms.Form):
22 | class Meta:
23 | fields = ['engine', 'min_level', 'file']
24 |
25 | engine = forms.CharField(widget=forms.Select(
26 | attrs={'class': 'form-control form-control-sm'},
27 | choices=ENGINE_TYPES))
28 | min_level = forms.CharField(widget=forms.Select(
29 | attrs={'class': 'form-control form-control-sm'},
30 | choices=FINDING_SEVERITIES),
31 | label='Minimum severity')
32 | file = forms.FileField(widget=forms.FileInput(
33 | attrs={'accept': '.json,.xml,.nessus'}),
34 | validators=[validate_file_extension]
35 | )
36 |
37 |
38 | class FindingForm(forms.ModelForm):
39 | class Meta:
40 | model = Finding
41 | fields = [
42 | 'title', 'type', 'severity', 'status', 'description', 'tags',
43 | 'solution', 'risk_info', 'vuln_refs', 'links', 'comments', 'asset'
44 | ]
45 | widgets = {
46 | 'description': forms.Textarea(
47 | attrs={'class': 'form-control form-control-sm'}),
48 | 'solution': forms.Textarea(
49 | attrs={'class': 'form-control form-control-sm'}),
50 | 'tags': forms.Textarea(
51 | attrs={'class': 'form-control form-control-sm'}),
52 | 'risk_info': forms.Textarea(
53 | attrs={'class': 'form-control form-control-sm'}),
54 | 'title': forms.TextInput(
55 | attrs={'class': 'form-control form-control-sm'}),
56 | 'vuln_refs': forms.Textarea(
57 | attrs={'class': 'form-control form-control-sm'}),
58 | 'links': forms.Textarea(
59 | attrs={'class': 'form-control form-control-sm'}),
60 | 'type': forms.TextInput(
61 | attrs={'class': 'form-control form-control-sm'}),
62 | 'severity': forms.Select(
63 | attrs={'class': 'form-control form-control-sm'}),
64 | 'comments': forms.Textarea(
65 | attrs={'class': 'form-control form-control-sm'}),
66 | 'status': forms.Select(
67 | attrs={'class': 'form-control form-control-sm'}),
68 | 'asset': forms.Select(
69 | attrs={'class': 'form-control form-control-sm'})
70 | }
71 |
72 | #tags = forms.CharField(widget=forms.TextInput(attrs={"data-role": "tagsinput"}))
73 |
--------------------------------------------------------------------------------
/findings/migrations/0002_auto_20200306_0822.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.11 on 2020-03-06 07:22
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('findings', '0001_initial'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='finding',
15 | name='scopes',
16 | field=models.ManyToManyField(blank=True, related_name='finding_scopes', to='engines.EnginePolicyScope'),
17 | ),
18 | migrations.AlterField(
19 | model_name='rawfinding',
20 | name='hash',
21 | field=models.CharField(default='', max_length=256),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/findings/migrations/0003_auto_20200602_2323.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.12 on 2020-06-02 21:23
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('findings', '0002_auto_20200306_0822'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='finding',
15 | name='score',
16 | field=models.IntegerField(blank=True, default=0, null=True),
17 | ),
18 | migrations.AddField(
19 | model_name='rawfinding',
20 | name='score',
21 | field=models.IntegerField(blank=True, default=0, null=True),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/findings/migrations/0004_auto_20200709_0024.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.13 on 2020-07-08 22:24
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('findings', '0003_auto_20200602_2323'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='finding',
15 | name='status',
16 | field=models.CharField(choices=[('new', 'New'), ('ack', 'Acknowledged'), ('mitigated', 'Mitigated'), ('confirmed', 'Confirmed'), ('patched', 'Patched'), ('closed', 'Closed'), ('false-positive', 'False-Positive'), ('undone', 'Undone')], default='new', max_length=16),
17 | ),
18 | migrations.AlterField(
19 | model_name='rawfinding',
20 | name='status',
21 | field=models.CharField(choices=[('new', 'New'), ('ack', 'Acknowledged'), ('mitigated', 'Mitigated'), ('confirmed', 'Confirmed'), ('patched', 'Patched'), ('closed', 'Closed'), ('false-positive', 'False-Positive'), ('undone', 'Undone')], max_length=16),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/findings/migrations/0005_auto_20200709_0910.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.13 on 2020-07-09 07:10
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('findings', '0004_auto_20200709_0024'),
12 | ]
13 |
14 | operations = [
15 | migrations.AlterField(
16 | model_name='finding',
17 | name='owner',
18 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL),
19 | ),
20 | migrations.AlterField(
21 | model_name='rawfinding',
22 | name='owner',
23 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL),
24 | ),
25 | ]
26 |
--------------------------------------------------------------------------------
/findings/migrations/0006_auto_20200720_1829.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.13 on 2020-07-20 16:29
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('findings', '0005_auto_20200709_0910'),
12 | ]
13 |
14 | operations = [
15 | migrations.AlterField(
16 | model_name='finding',
17 | name='owner',
18 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
19 | ),
20 | migrations.AlterField(
21 | model_name='rawfinding',
22 | name='owner',
23 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
24 | ),
25 | ]
26 |
--------------------------------------------------------------------------------
/findings/migrations/0007_auto_20210113_1644.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.16 on 2021-01-13 15:44
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('findings', '0006_auto_20200720_1829'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='finding',
15 | name='status',
16 | field=models.CharField(choices=[('new', 'New'), ('ack', 'Acknowledged'), ('mitigated', 'Mitigated'), ('confirmed', 'Confirmed'), ('patched', 'Patched'), ('closed', 'Closed'), ('false-positive', 'False-Positive'), ('undone', 'Undone'), ('duplicate', 'Duplicate')], default='new', max_length=16),
17 | ),
18 | migrations.AlterField(
19 | model_name='rawfinding',
20 | name='status',
21 | field=models.CharField(choices=[('new', 'New'), ('ack', 'Acknowledged'), ('mitigated', 'Mitigated'), ('confirmed', 'Confirmed'), ('patched', 'Patched'), ('closed', 'Closed'), ('false-positive', 'False-Positive'), ('undone', 'Undone'), ('duplicate', 'Duplicate')], max_length=16),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/findings/migrations/0008_findingoverride.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.18 on 2021-05-04 11:19
2 |
3 | import django.contrib.postgres.fields.jsonb
4 | from django.db import migrations, models
5 | import django.utils.timezone
6 | import findings.models
7 |
8 |
9 | class Migration(migrations.Migration):
10 |
11 | dependencies = [
12 | ('findings', '0007_auto_20210113_1644'),
13 | ]
14 |
15 | operations = [
16 | migrations.CreateModel(
17 | name='FindingOverride',
18 | fields=[
19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20 | ('name', models.CharField(max_length=256)),
21 | ('enabled', models.BooleanField(default=True)),
22 | ('engine_type', models.CharField(max_length=20)),
23 | ('action', models.CharField(choices=[('set-status', 'Set custom status'), ('set-severity', 'Set custom severity')], default='set-status', max_length=16)),
24 | ('params', django.contrib.postgres.fields.jsonb.JSONField(default=findings.models.get_default_override_params)),
25 | ('created_at', models.DateTimeField(default=django.utils.timezone.now)),
26 | ('updated_at', models.DateTimeField(default=django.utils.timezone.now)),
27 | ],
28 | options={
29 | 'db_table': 'finding_override',
30 | },
31 | ),
32 | ]
33 |
--------------------------------------------------------------------------------
/findings/migrations/0009_auto_20210504_1320.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.18 on 2021-05-04 11:20
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('findings', '0008_findingoverride'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='findingoverride',
15 | name='action',
16 | field=models.CharField(choices=[('set-status', 'Set custom status'), ('set-severity', 'Set custom severity')], default='set-status', max_length=32),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/findings/migrations/0010_auto_20211104_1152.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.24 on 2021-11-04 10:52
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('findings', '0009_auto_20210504_1320'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='finding',
15 | name='status',
16 | field=models.CharField(choices=[('new', 'New'), ('ack', 'Acknowledged'), ('confirmed', 'Confirmed'), ('mitigated', 'Mitigated'), ('patched', 'Patched'), ('closed', 'Closed'), ('false-positive', 'False-Positive'), ('undone', 'Undone'), ('duplicate', 'Duplicate'), ('reopened', 'Reopened')], default='new', max_length=16),
17 | ),
18 | migrations.AlterField(
19 | model_name='rawfinding',
20 | name='status',
21 | field=models.CharField(choices=[('new', 'New'), ('ack', 'Acknowledged'), ('confirmed', 'Confirmed'), ('mitigated', 'Mitigated'), ('patched', 'Patched'), ('closed', 'Closed'), ('false-positive', 'False-Positive'), ('undone', 'Undone'), ('duplicate', 'Duplicate'), ('reopened', 'Reopened')], max_length=16),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/findings/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/findings/migrations/__init__.py
--------------------------------------------------------------------------------
/findings/templates/add-finding.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
8 |
9 |
45 |
46 | {% endblock %}
47 |
--------------------------------------------------------------------------------
/findings/templates/delete-findings.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
5 |
17 |
18 | {% if messages %}
19 |
20 | {% for message in messages %}
21 | - {{ message }}
22 | {% endfor %}
23 |
24 | {% endif %}
25 |
26 |
27 | {% endblock %}
28 |
--------------------------------------------------------------------------------
/findings/templates/edit-finding.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
8 |
9 |
21 |
22 |
23 | {% if raw == True %}
24 |
62 |
63 |
64 | {% endblock %}
65 |
--------------------------------------------------------------------------------
/findings/templates/import-findings.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
8 |
9 |
46 |
47 | {% endblock %}
48 |
--------------------------------------------------------------------------------
/findings/tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.test import TestCase, Client
4 |
5 |
6 | class FindingTestCase(TestCase):
7 | # fixtures = ['tmp/db.json']
8 |
9 | def add_finding_test(self):
10 | print("TEST CASE: add_finding_test")
11 | c = Client()
12 |
13 | print("TEST CASE: testing with GET method")
14 | r = c.get('http://127.0.0.1:8000/findings/add?title=testing')
15 | print(r.json())
16 |
17 |
18 | print("TEST CASE: testing with POST method")
19 | r = c.post('http://127.0.0.1:8000/findings/add', {
20 | "asset_id": "1",
21 | "title": "Open port TCP/80",
22 | "type": "open_ports",
23 | "confidence": "certain",
24 | "severity": "info",
25 | "status": "new",
26 | "engine_type": "nmap"
27 | })
28 | print(r.json())
29 |
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env -S python3 -OO
2 | import os
3 | import sys
4 |
5 | if __name__ == "__main__":
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings")
7 | try:
8 | from django.core.management import execute_from_command_line
9 | except ImportError:
10 | # The above import may fail for some other reason. Ensure that the
11 | # issue is really that Django is missing to avoid masking other
12 | # exceptions on Python 2.
13 | try:
14 | import django
15 | except ImportError:
16 | raise ImportError(
17 | "Couldn't import Django. Are you sure it's installed and "
18 | "available on your PYTHONPATH environment variable? Did you "
19 | "forget to activate a virtual environment?"
20 | )
21 | raise
22 | execute_from_command_line(sys.argv)
23 |
--------------------------------------------------------------------------------
/media/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 |
--------------------------------------------------------------------------------
/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 8080;
3 | autoindex on;
4 | server_name my.patrowl.io localhost;
5 |
6 | root .;
7 |
8 | # access_log var/log/nginx.patrowlmanager-access.log;
9 | # error_log var/log/nginx.patrowlmanager-error.log;
10 |
11 | location / {
12 | proxy_pass http://127.0.0.1:8000;
13 |
14 | proxy_set_header X-Real-IP $remote_addr;
15 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
16 | proxy_set_header X-Forwarded-Host $host;
17 |
18 | proxy_set_header Proxy "";
19 | }
20 |
21 | location /static {
22 | alias ./staticfiles;
23 | }
24 | # location /media {
25 | # alias ./media;
26 | # }
27 | }
28 |
--------------------------------------------------------------------------------
/nginx_docker.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 8083;
3 | autoindex off;
4 | server_name localhost;
5 |
6 | access_log /var/log/nginx.patrowlmanager-access.log;
7 | error_log /var/log/nginx.patrowlmanager-error.log;
8 |
9 | # Protects against Clickjacking attacks.
10 | add_header X-Frame-Options "SAMEORIGIN";
11 |
12 | # Protects against Clickjacking attacks.
13 | add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
14 |
15 | # Protects against XSS injections.
16 | add_header X-XSS-Protection "1; mode=block";
17 |
18 | # Protects against MIME-type confusion attack.
19 | add_header X-Content-Type-Options "nosniff";
20 |
21 | # CSP modern XSS directive-based defence, used since 2014.
22 | add_header Content-Security-Policy "default-src 'self'; font-src *;img-src * data:; script-src *; style-src *;";
23 |
24 | # Prevents from leaking referrer data over insecure connections.
25 | add_header Referrer-Policy 'strict-origin';
26 |
27 | # Disable any limits to avoid HTTP 413 for large image uploads
28 | client_max_body_size 0;
29 |
30 | location / {
31 | proxy_pass http://web:8003;
32 |
33 | proxy_set_header X-Real-IP $remote_addr;
34 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
35 | proxy_set_header X-Forwarded-Host $host;
36 |
37 | proxy_set_header Proxy "";
38 |
39 | add_header 'Cache-Control' 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
40 | expires off;
41 |
42 | # Custom headers
43 | proxy_connect_timeout 75s;
44 | proxy_read_timeout 300s;
45 |
46 | proxy_redirect off;
47 | proxy_buffering off;
48 | proxy_buffer_size "4k";
49 | }
50 |
51 | location /static {
52 | alias /opt/patrowl-manager/staticfiles;
53 | }
54 | # location /media {
55 | # alias /opt/patrowl-manager/media;
56 | # }
57 | }
58 |
--------------------------------------------------------------------------------
/reportings/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/reportings/__init__.py
--------------------------------------------------------------------------------
/reportings/admin.py:
--------------------------------------------------------------------------------
1 | # from django.contrib import admin
2 |
3 | # Register your models here.
4 |
--------------------------------------------------------------------------------
/reportings/api.py:
--------------------------------------------------------------------------------
1 | from django.http import JsonResponse
2 |
3 |
4 | def global_stats_api(request):
5 | data = None
6 | return JsonResponse(data)
7 |
--------------------------------------------------------------------------------
/reportings/apps.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | from django.apps import AppConfig
4 |
5 |
6 | class ReportingsConfig(AppConfig):
7 | name = 'reportings'
8 |
--------------------------------------------------------------------------------
/reportings/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/reportings/migrations/__init__.py
--------------------------------------------------------------------------------
/reportings/models.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | FIELD_TYPES_CRITERIAS = {
4 | "TEXT": [
5 | {"criteria": "is", "criteria_kw": "exact"},
6 | {"criteria": "contains", "criteria_kw": "icontains"},
7 | {"criteria": "start with", "criteria_kw": "startswith"},
8 | {"criteria": "end with", "criteria_kw": "endswith"}],
9 | "NUMBER": [
10 | {"criteria": "is equal to", "criteria_kw": "exact"},
11 | {"criteria": "is greater than", "criteria_kw": "gt"},
12 | {"criteria": "is lower than", "criteria_kw": "lt"}],
13 | "DATETIME": [
14 | {"criteria": "is equal to", "criteria_kw": "exact"}],
15 | }
16 |
--------------------------------------------------------------------------------
/reportings/templates/patch-management-dashboard.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load patrowl_tags %}
3 | {% block content %}
4 |
5 |
9 |
10 | {{data}}
11 |
12 |
13 |
14 |
15 | {% if messages %}
16 |
17 | {% for message in messages %}
18 | - {{ message }}
19 | {% endfor %}
20 |
21 | {% endif %}
22 |
23 |
24 |
75 |
76 | {% endblock %}
77 |
--------------------------------------------------------------------------------
/reportings/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import url
2 | from . import views
3 | from . import api
4 |
5 |
6 | urlpatterns = [
7 | # VIEWS
8 | # ex: /reportings/patch_management
9 | url(r'^patch_management$', views.patch_management_view, name='patch_management_view'),
10 | # ex: /reportings/dashboard
11 | url(r'^dashboard$', views.homepage_dashboard_view, name='homepage_dashboard_view'),
12 |
13 | ## API
14 | url(r'^api/v1/global_stats$', api.global_stats_api, name='global_stats_api'),
15 | ]
16 |
--------------------------------------------------------------------------------
/requirements.macos.txt:
--------------------------------------------------------------------------------
1 | amqp==2.5.2
2 | Babel==2.9.1
3 | backports-abc==0.5
4 | billiard==3.6.1.0
5 | celery==4.4.0
6 | certifi==2017.4.17
7 | chardet==3.0.4
8 | coreapi==2.3.3
9 | coreschema==0.0.4
10 | Django==2.2.26
11 | #django-celery-beat==1.1.1
12 | django-celery-beat==2.0.0
13 | django-celery-results==2.0.1
14 | django-cursor-pagination==0.1.4
15 | django-datetime-widget==0.9.3
16 | django-debug-toolbar==3.2.2
17 | django-extensions==2.1.9
18 | django-rest-swagger==2.2.0
19 | django-timezone-field==4.0
20 | djangorestframework>=3.11.2
21 | djangorestframework-datatables==0.6.0
22 | flower==0.9.2
23 | future==0.16.0
24 | futures==3.1.1
25 | gunicorn==20.0.4
26 | idna==2.5
27 | importlib-metadata==0.23
28 | itypes==1.1.0
29 | Jinja2==2.11.3
30 | jsonfield==2.0.2
31 | kombu==4.6.7
32 | libmagic==1.0
33 | MarkupSafe==1.1.0
34 | meld3==1.0.2
35 | more-itertools==7.2.0
36 | netaddr==0.7.19
37 | openapi-codec==1.3.2
38 | #psycopg2-binary==2.8.6
39 | psycopg2-binary==2.7.5
40 | python-crontab==2.3.6
41 | python-dateutil==2.8.0
42 | python-magic==0.4.15
43 | python-magic-bin==0.4.14
44 | python-memcached==1.59
45 | pytz==2019.2
46 | requests==2.25.1
47 | simplejson==3.16.0
48 | singledispatch==3.4.0.3
49 | six==1.10.0
50 | sqlparse==0.4.2
51 | supervisor==4.2.1
52 | thehive4py==1.8.1
53 | tornado==5.1
54 | tzlocal==1.5.1
55 | uritemplate==3.0.0
56 | urllib3==1.26.5
57 | vine==1.3.0
58 | Werkzeug==2.0.1
59 | zipp==0.6.0
60 | django-filter==2.4.0
61 | django-cors-headers==3.2.0
62 | djangorestframework-simplejwt==4.4.0
63 | django-health-check==3.12.1
64 | psutil==5.7.0
65 | django-organizations==1.1.2
66 | django-annoying==0.10.6
67 | django-reset-migrations
68 | django-dbconn-retry
69 | cpe==1.2.1
70 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | amqp==2.5.2
2 | Babel==2.9.1
3 | backports-abc==0.5
4 | billiard==3.6.1.0
5 | celery==4.4.0
6 | certifi==2017.4.17
7 | chardet==3.0.4
8 | coreapi==2.3.3
9 | coreschema==0.0.4
10 | Django==2.2.26
11 | #django-celery-beat==1.1.1
12 | django-celery-beat==2.0.0
13 | django-celery-results==2.0.1
14 | django-cursor-pagination==0.1.4
15 | django-datetime-widget==0.9.3
16 | django-debug-toolbar==3.2.2
17 | django-extensions==2.1.9
18 | django-rest-swagger==2.2.0
19 | django-timezone-field==4.0
20 | djangorestframework>=3.11.2
21 | djangorestframework-datatables==0.6.0
22 | flower==0.9.2
23 | future==0.16.0
24 | futures==3.1.1
25 | gunicorn==20.0.4
26 | idna==2.5
27 | importlib-metadata==0.23
28 | itypes==1.1.0
29 | Jinja2==2.11.3
30 | jsonfield==2.0.2
31 | kombu==4.6.7
32 | libmagic==1.0
33 | MarkupSafe==1.1.0
34 | meld3==1.0.2
35 | more-itertools==7.2.0
36 | netaddr==0.7.19
37 | openapi-codec==1.3.2
38 | #psycopg2-binary==2.8.6
39 | psycopg2-binary==2.7.5
40 | python-crontab==2.3.6
41 | python-dateutil==2.8.0
42 | python-magic==0.4.15
43 | #python-magic-bin==0.4.14
44 | python-memcached==1.59
45 | pytz==2019.2
46 | requests==2.25.1
47 | simplejson==3.16.0
48 | singledispatch==3.4.0.3
49 | six==1.10.0
50 | sqlparse==0.4.2
51 | supervisor==4.2.1
52 | thehive4py==1.8.1
53 | tornado==5.1
54 | tzlocal==1.5.1
55 | uritemplate==3.0.0
56 | urllib3==1.26.5
57 | vine==1.3.0
58 | Werkzeug==2.0.1
59 | zipp==0.6.0
60 | django-filter==2.4.0
61 | django-cors-headers==3.2.0
62 | djangorestframework-simplejwt==4.4.0
63 | django-health-check==3.12.1
64 | psutil==5.7.0
65 | django-organizations==1.1.2
66 | django-annoying==0.10.6
67 | django-reset-migrations
68 | django-dbconn-retry
69 | cpe==1.2.1
70 |
--------------------------------------------------------------------------------
/rules/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
--------------------------------------------------------------------------------
/rules/admin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.contrib import admin
4 | from .models import Rule
5 |
6 | admin.site.register(Rule)
7 |
--------------------------------------------------------------------------------
/rules/apps.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 | from django.apps import AppConfig
4 |
5 |
6 | class RulesConfig(AppConfig):
7 | name = 'rules'
8 |
--------------------------------------------------------------------------------
/rules/forms.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
--------------------------------------------------------------------------------
/rules/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.5 on 2019-09-17 23:07
2 |
3 | from django.conf import settings
4 | import django.contrib.postgres.fields.jsonb
5 | from django.db import migrations, models
6 | import django.db.models.deletion
7 | import django.utils.timezone
8 |
9 |
10 | class Migration(migrations.Migration):
11 |
12 | initial = True
13 |
14 | dependencies = [
15 | ('django_celery_beat', '0001_initial'),
16 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
17 | ]
18 |
19 | operations = [
20 | migrations.CreateModel(
21 | name='Rule',
22 | fields=[
23 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
24 | ('title', models.CharField(max_length=256)),
25 | ('comments', models.CharField(default='n/a', max_length=256)),
26 | ('scope', models.CharField(choices=[('asset', 'Asset'), ('finding', 'Finding'), ('scan', 'Scan')], default='finding', max_length=10)),
27 | ('scope_attr', models.CharField(blank=True, max_length=20, null=True)),
28 | ('condition', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True)),
29 | ('target', models.CharField(choices=[('event', 'Patrowl event'), ('logfile', 'To logfile'), ('email', 'Send email'), ('thehive', 'To TheHive (event'), ('splunk', 'To Splunk'), ('slack', 'To Slack')], default='event', max_length=10)),
30 | ('severity', models.CharField(choices=[('Low', 'Low'), ('Medium', 'Medium'), ('High', 'High')], default='Low', max_length=10)),
31 | ('trigger', models.CharField(choices=[('ondemand', 'On-demand'), ('auto', 'Auto'), ('periodic', 'Periodic')], default='auto', max_length=10)),
32 | ('trigger_attr', models.CharField(blank=True, max_length=20, null=True)),
33 | ('summary', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True)),
34 | ('enabled', models.BooleanField(default=False)),
35 | ('nb_matches', models.IntegerField(default=0)),
36 | ('created_at', models.DateTimeField(default=django.utils.timezone.now)),
37 | ('updated_at', models.DateTimeField(default=django.utils.timezone.now)),
38 | ('owner', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL)),
39 | ('periodic_task', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='django_celery_beat.PeriodicTask')),
40 | ],
41 | options={
42 | 'db_table': 'rules',
43 | },
44 | ),
45 | ]
46 |
--------------------------------------------------------------------------------
/rules/migrations/0002_auto_20200720_1829.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.13 on 2020-07-20 16:29
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('rules', '0001_initial'),
12 | ]
13 |
14 | operations = [
15 | migrations.AlterField(
16 | model_name='rule',
17 | name='owner',
18 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
19 | ),
20 | migrations.AlterField(
21 | model_name='rule',
22 | name='target',
23 | field=models.CharField(choices=[('email', 'Send email'), ('thehive', 'TheHive Event'), ('slack', 'Slack')], default='event', max_length=10),
24 | ),
25 | ]
26 |
--------------------------------------------------------------------------------
/rules/migrations/0003_auto_20210113_1644.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.16 on 2021-01-13 15:44
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('rules', '0002_auto_20200720_1829'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='rule',
15 | name='scope',
16 | field=models.CharField(choices=[('asset', 'Asset'), ('finding', 'Finding'), ('scan', 'Scan'), ('alert', 'Alert')], default='finding', max_length=10),
17 | ),
18 | migrations.AlterField(
19 | model_name='rule',
20 | name='severity',
21 | field=models.CharField(choices=[('Info', 'Info'), ('Low', 'Low'), ('Medium', 'Medium'), ('High', 'High'), ('Critical', 'Critical')], default='Low', max_length=10),
22 | ),
23 | migrations.AlterField(
24 | model_name='rule',
25 | name='target',
26 | field=models.CharField(choices=[('email', 'Send email'), ('thehive', 'TheHive Event'), ('slack', 'Slack'), ('alert', 'Alert')], default='event', max_length=10),
27 | ),
28 | ]
29 |
--------------------------------------------------------------------------------
/rules/migrations/0004_auto_20211104_1152.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.24 on 2021-11-04 10:52
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('rules', '0003_auto_20210113_1644'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='rule',
15 | name='scope',
16 | field=models.CharField(choices=[('asset', 'Asset'), ('finding', 'Finding'), ('scan', 'Scan')], default='finding', max_length=10),
17 | ),
18 | migrations.AlterField(
19 | model_name='rule',
20 | name='target',
21 | field=models.CharField(choices=[('email', 'Send email'), ('thehive', 'TheHive event'), ('slack', 'Slack notification'), ('alert', 'Patrowl alert')], default='event', max_length=10),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/rules/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/rules/migrations/__init__.py
--------------------------------------------------------------------------------
/rules/tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.test import TestCase
4 |
5 | # Create your tests here.
6 |
--------------------------------------------------------------------------------
/rules/urls.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """URL routes for alerting rules."""
4 |
5 | from django.conf.urls import url
6 | from . import views, apis
7 |
8 |
9 | urlpatterns = [
10 | # API Views
11 | # ex: /rules/api/v1/alerting/list
12 | url(r'^api/v1/alerting/list$',
13 | apis.list_alerting_rules_api, name='list_alerting_rules_api'),
14 | # ex: /rules/api/v1/alerting/1
15 | url(r'^api/v1/alerting/by-id/(?P[0-9]+)$',
16 | apis.get_alerting_rule_api, name='get_alerting_rule_api'),
17 | # ex: /rules/api/v1/delete
18 | url(r'^api/v1/delete$',
19 | apis.delete_rules_api, name='delete_rules_api'),
20 | # ex: /rules/api/v1/delete/1
21 | url(r'^api/v1/delete/(?P[0-9]+)$',
22 | apis.delete_rule_api, name='delete_rule_api'),
23 | # ex: /rules/api/v1/add
24 | url(r'^api/v1/add$',
25 | apis.add_rule_api, name='add_rule_api'),
26 | # ex: /rules/api/v1/duplicate/3
27 | url(r'^api/v1/alerting/duplicate/(?P[0-9]+)$',
28 | apis.duplicate_rule_api, name='duplicate_rule_api'),
29 | # ex: /rules/api/v1/change_status/3
30 | url(r'^api/v1/change_status/(?P[0-9]+)$',
31 | apis.toggle_rule_status_api, name='toggle_rule_status_api'),
32 | # ex: /rules/api/v1/send/slack
33 | # url(r'^api/v1/send/slack$',
34 | # apis.send_slack_message_api, name='send_slack_message_api'),
35 |
36 | # WEB Views
37 | # ex: /rules/list
38 | url(r'^list$',
39 | views.list_rules_view, name='list_rules_view'),
40 |
41 | ]
42 |
--------------------------------------------------------------------------------
/scans/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/scans/__init__.py
--------------------------------------------------------------------------------
/scans/admin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.contrib import admin
4 | from .models import Scan, ScanCampaign, ScanDefinition
5 |
6 |
7 | class ScanAssetInline(admin.TabularInline):
8 | model = Scan.assets.through
9 |
10 |
11 | class ScanAdmin(admin.ModelAdmin):
12 | raw_id_fields = ('scan_definition',)
13 | exclude = ('assets',)
14 | inlines = [
15 | ScanAssetInline,
16 | ]
17 |
18 |
19 | class ScanDefAssetInline(admin.TabularInline):
20 | model = ScanDefinition.assets_list.through
21 |
22 |
23 | class ScanDefAssetGroupInline(admin.TabularInline):
24 | model = ScanDefinition.assetgroups_list.through
25 |
26 |
27 | class ScanDefDynAssetGroupInline(admin.TabularInline):
28 | model = ScanDefinition.dynassetgroups_list.through
29 |
30 |
31 | class ScanDefAdmin(admin.ModelAdmin):
32 | exclude = ('assets_list', 'assetgroups_list', 'dynassetgroups_list',)
33 | inlines = [
34 | ScanDefAssetInline, ScanDefAssetGroupInline, ScanDefDynAssetGroupInline
35 | ]
36 |
37 |
38 | admin.site.register(Scan, ScanAdmin)
39 | admin.site.register(ScanCampaign)
40 | admin.site.register(ScanDefinition, ScanDefAdmin)
41 |
--------------------------------------------------------------------------------
/scans/apps.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from __future__ import unicode_literals
4 |
5 | from django.apps import AppConfig
6 |
7 |
8 | class ScansConfig(AppConfig):
9 | name = 'scans'
10 |
--------------------------------------------------------------------------------
/scans/migrations/0002_scandefinition_teams.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.13 on 2020-06-23 14:54
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('users', '0002_auto_20200616_1704'),
10 | ('scans', '0001_initial'),
11 | ]
12 |
13 | operations = [
14 | migrations.AddField(
15 | model_name='scandefinition',
16 | name='teams',
17 | field=models.ManyToManyField(blank=True, to='users.Team'),
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/scans/migrations/0003_scanjob.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.16 on 2020-12-02 13:35
2 |
3 | import django.contrib.postgres.fields.jsonb
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 | import django.utils.timezone
7 | import uuid
8 |
9 |
10 | class Migration(migrations.Migration):
11 |
12 | dependencies = [
13 | ('assets', '0009_auto_20200921_1151'),
14 | ('engines', '0001_initial'),
15 | ('scans', '0002_scandefinition_teams'),
16 | ]
17 |
18 | operations = [
19 | migrations.CreateModel(
20 | name='ScanJob',
21 | fields=[
22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
23 | ('uuid', models.UUIDField(default=uuid.uuid4, editable=False)),
24 | ('status', models.CharField(choices=[('created', 'Created'), ('enqueued', 'Enqueued'), ('started', 'Started'), ('finished', 'Finished'), ('error', 'Error'), ('stopped', 'Stopped')], default='created', max_length=20)),
25 | ('summary', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True)),
26 | ('options', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True)),
27 | ('started_at', models.DateTimeField(blank=True, null=True)),
28 | ('finished_at', models.DateTimeField(blank=True, null=True)),
29 | ('created_at', models.DateTimeField(default=django.utils.timezone.now)),
30 | ('updated_at', models.DateTimeField(default=django.utils.timezone.now)),
31 | ('assets', models.ManyToManyField(to='assets.Asset')),
32 | ('engine', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='engines.EngineInstance')),
33 | ('engine_policy', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='engines.EnginePolicy')),
34 | ('engine_type', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='engines.Engine')),
35 | ('scan', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='scans.Scan')),
36 | ],
37 | options={
38 | 'db_table': 'scan_jobs',
39 | },
40 | ),
41 | ]
42 |
--------------------------------------------------------------------------------
/scans/migrations/0004_auto_20201202_1448.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.16 on 2020-12-02 13:48
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('scans', '0003_scanjob'),
10 | ]
11 |
12 | operations = [
13 | migrations.RemoveField(
14 | model_name='scanjob',
15 | name='uuid',
16 | ),
17 | migrations.AddField(
18 | model_name='scanjob',
19 | name='task_id',
20 | field=models.UUIDField(blank=True, null=True),
21 | ),
22 | ]
23 |
--------------------------------------------------------------------------------
/scans/migrations/0005_auto_20201202_1909.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.16 on 2020-12-02 18:09
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('scans', '0004_auto_20201202_1448'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='scanjob',
15 | name='position',
16 | field=models.IntegerField(blank=True, default=1, null=True),
17 | ),
18 | migrations.AlterField(
19 | model_name='scanjob',
20 | name='status',
21 | field=models.CharField(choices=[('started', 'Started'), ('finished', 'Finished'), ('error', 'Error'), ('stopped', 'Stopped')], default='started', max_length=20),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/scans/migrations/0006_scandefinition_taggroups_list.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.18 on 2021-04-02 12:39
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('assets', '0010_auto_20210402_1439'),
10 | ('scans', '0005_auto_20201202_1909'),
11 | ]
12 |
13 | operations = [
14 | migrations.AddField(
15 | model_name='scandefinition',
16 | name='taggroups_list',
17 | field=models.ManyToManyField(blank=True, to='assets.AssetCategory'),
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/scans/migrations/0007_auto_20220114_1547.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.25 on 2022-01-14 14:47
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('assets', '0012_dynamicassetgroup'),
10 | ('scans', '0006_scandefinition_taggroups_list'),
11 | ]
12 |
13 | operations = [
14 | migrations.RemoveField(
15 | model_name='scandefinition',
16 | name='taggroups_list',
17 | ),
18 | migrations.AddField(
19 | model_name='scandefinition',
20 | name='dynassetgroups_list',
21 | field=models.ManyToManyField(blank=True, to='assets.DynamicAssetGroup'),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/scans/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/scans/migrations/__init__.py
--------------------------------------------------------------------------------
/scans/templates/add-scan-campaign.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 | {{ form.media }}
5 |
6 |
11 |
12 |
46 |
47 | {% endblock %}
48 |
--------------------------------------------------------------------------------
/scans/templates/delete-scan-campaign.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
5 |
16 |
17 | {% if messages %}
18 |
19 | {% for message in messages %}
20 | - {{ message }}
21 | {% endfor %}
22 |
23 | {% endif %}
24 |
25 |
26 | {% endblock %}
27 |
--------------------------------------------------------------------------------
/scans/templates/delete-scan-definition.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
5 |
16 |
17 | {% if messages %}
18 |
19 | {% for message in messages %}
20 | - {{ message }}
21 | {% endfor %}
22 |
23 | {% endif %}
24 |
25 |
26 | {% endblock %}
27 |
--------------------------------------------------------------------------------
/scans/templates/edit-scan-campaign.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
5 |
36 | {% if messages %}
37 |
38 | {% for message in messages %}
39 | - {{ message }}
40 | {% endfor %}
41 |
42 | {% endif %}
43 |
44 |
45 | {% endblock %}
46 |
--------------------------------------------------------------------------------
/scans/templates/email_send_report.html:
--------------------------------------------------------------------------------
1 | Hello, A new scan report is available.
2 | Scan
3 |
4 | - Title: {{scan.id}}/{{scan.title}}
5 | - Status: {{scan.status|title}}
6 | - Started at: {{scan.started_at|date:"Y/m/d\-H:i:s"}}
7 | - Finished at: {{scan.finished_at|date:"Y/m/d\-H:i:s"}}
8 |
9 | Findings
10 |
11 | - Critical: {{findings_stats.critical}}
12 | - High: {{findings_stats.high}}
13 | - Medium: {{findings_stats.medium}}
14 | - Low: {{findings_stats.low}}
15 | - Info: {{findings_stats.info}}
16 |
17 |
18 | -- PatrowlManager Notification Center
19 | See https://patrowl.io
20 |
21 |
--------------------------------------------------------------------------------
/scans/templates/email_send_report.txt:
--------------------------------------------------------------------------------
1 | PatrowlManager - Send Email report
2 |
--------------------------------------------------------------------------------
/scans/tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.test import TestCase
4 |
5 | # Create your tests here.
6 |
--------------------------------------------------------------------------------
/search/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/search/__init__.py
--------------------------------------------------------------------------------
/search/admin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # from django.contrib import admin
4 |
5 | # Register your models here.
6 |
--------------------------------------------------------------------------------
/search/apis.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.http import JsonResponse
4 | from .views import _search
5 | from rest_framework.decorators import api_view
6 |
7 |
8 | @api_view(['GET'])
9 | def search_api(request):
10 | """REST-API: Search based on keywords."""
11 | kw = request.GET.get('srch-term', None)
12 |
13 | if not kw:
14 | return JsonResponse([])
15 |
16 | results = _search(kw)
17 | return JsonResponse({'results': results, 'search_term': kw})
18 |
--------------------------------------------------------------------------------
/search/apps.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from __future__ import unicode_literals
4 |
5 | from django.apps import AppConfig
6 |
7 |
8 | class SearchConfig(AppConfig):
9 | name = 'search'
10 |
--------------------------------------------------------------------------------
/search/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/search/migrations/__init__.py
--------------------------------------------------------------------------------
/search/models.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # from __future__ import unicode_literals
4 | #
5 | # from django.db import models
6 |
7 | # Create your models here.
8 |
--------------------------------------------------------------------------------
/search/templates/search-results.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
5 |
Search results for '{{ search_term }}':
6 |
7 |
8 | Type |
9 | Item |
10 |
11 | {% for result in results %}
12 |
13 | {{ result.type }} |
14 | {{ result.value }} |
15 |
16 | {% endfor %}
17 |
18 |
19 |
20 | {% if messages %}
21 |
22 | {% for message in messages %}
23 | - {{ message }}
24 | {% endfor %}
25 |
26 | {% endif %}
27 |
28 |
29 | {% endblock %}
30 |
--------------------------------------------------------------------------------
/search/tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.test import TestCase
4 |
5 | # Create your tests here.
6 |
--------------------------------------------------------------------------------
/search/urls.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.conf.urls import url
4 | from . import views, apis
5 |
6 |
7 | urlpatterns = [
8 | ## WEB Views
9 | # ex: /search
10 | url(r'^$', views.search_view, name='search_view'),
11 | # ex: /search
12 | url(r'/api/v1/$', apis.search_api, name='search_api'),
13 |
14 | ]
15 |
--------------------------------------------------------------------------------
/settings/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/settings/__init__.py
--------------------------------------------------------------------------------
/settings/admin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.contrib import admin
4 | from .models import Setting
5 |
6 | admin.site.register(Setting)
7 |
--------------------------------------------------------------------------------
/settings/apis.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """View and API definitions for Settings."""
3 |
4 | from django.shortcuts import get_object_or_404
5 | from django.contrib import messages
6 | from django.http import JsonResponse, HttpResponse
7 | from rest_framework.decorators import api_view
8 | from .models import Setting
9 | import csv
10 |
11 |
12 | @api_view(['POST'])
13 | def update_setting_api(request):
14 | """API: Update a setting value."""
15 | setting_id = request.data["setting_id"]
16 | setting = get_object_or_404(Setting, id=setting_id)
17 | setting.value = request.data["setting_value"]
18 | setting.save(force_update=True)
19 | messages.success(request, 'Setting successfully updated!')
20 |
21 | return JsonResponse({'status': 'success'})
22 |
23 |
24 | @api_view(['POST'])
25 | def add_setting_api(request):
26 | """API: Add a setting key/value."""
27 | setting_key = request.data["setting_key"]
28 | if Setting.objects.filter(key=setting_key).count() == 0:
29 | new_settings_args = {
30 | "key": request.data["setting_key"],
31 | "value": request.data["setting_value"],
32 | "comments": "n/a",
33 | }
34 | new_setting = Setting.objects.create(**new_settings_args)
35 | new_setting.save()
36 |
37 | messages.success(request, 'Setting successfully updated!')
38 | return JsonResponse({'status': 'success'})
39 |
40 | return JsonResponse(data={'status': 'error'}, status=403)
41 |
42 |
43 | @api_view(['GET'])
44 | def delete_setting_api(request, setting_id):
45 | """API: Delete a setting key/value."""
46 | setting = get_object_or_404(Setting, id=setting_id)
47 | setting.delete()
48 | messages.success(request, 'Setting successfully deleted!')
49 |
50 | return JsonResponse({'status': 'success'})
51 |
52 |
53 | @api_view(['GET'])
54 | def export_settings_api(request):
55 | """API: Export settings."""
56 | response = HttpResponse(content_type='text/csv')
57 | filename = "patrowl_settings.csv"
58 | response['Content-Disposition'] = 'attachment; filename=' + filename
59 | writer = csv.writer(response, delimiter=';')
60 |
61 | settings = Setting.objects.all()
62 |
63 | writer.writerow(['keys', 'values', 'comments']) # headers
64 | for setting in settings:
65 | writer.writerow([setting.key, setting.value, setting.comments])
66 |
67 | return response
68 |
69 |
70 | @api_view(['GET'])
71 | def import_settings_api(request):
72 | """API: Export settings."""
73 | # @Todo
74 | pass
75 |
--------------------------------------------------------------------------------
/settings/apps.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from __future__ import unicode_literals
4 | from django.apps import AppConfig
5 |
6 |
7 | class SettingsConfig(AppConfig):
8 | name = 'settings'
9 |
--------------------------------------------------------------------------------
/settings/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.5 on 2019-09-17 23:07
2 |
3 | from django.db import migrations, models
4 | import django.utils.timezone
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | initial = True
10 |
11 | dependencies = [
12 | ]
13 |
14 | operations = [
15 | migrations.CreateModel(
16 | name='Setting',
17 | fields=[
18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19 | ('key', models.CharField(max_length=256, unique=True)),
20 | ('value', models.CharField(default='n/a', max_length=256)),
21 | ('comments', models.CharField(default='n/a', max_length=256)),
22 | ('created_at', models.DateTimeField(default=django.utils.timezone.now)),
23 | ('updated_at', models.DateTimeField(default=django.utils.timezone.now)),
24 | ],
25 | options={
26 | 'db_table': 'settings',
27 | },
28 | ),
29 | ]
30 |
--------------------------------------------------------------------------------
/settings/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/settings/migrations/__init__.py
--------------------------------------------------------------------------------
/settings/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.utils import timezone
3 | from django.db.models.signals import post_save, post_delete
4 | from django.dispatch import receiver
5 | import inspect
6 |
7 |
8 | class Setting(models.Model):
9 | key = models.CharField(max_length=256, unique=True)
10 | value = models.CharField(max_length=256, default='n/a')
11 | comments = models.CharField(max_length=256, default='n/a')
12 | created_at = models.DateTimeField(default=timezone.now)
13 | updated_at = models.DateTimeField(default=timezone.now)
14 |
15 | class Meta:
16 | db_table = 'settings'
17 |
18 | def __str__(self):
19 | return "{}: {}".format(self.key, self.value)
20 |
21 | def save(self, *args, **kwargs):
22 | # update the 'updated_at' entry on each update except on creation
23 | if not self._state.adding:
24 | self.updated_at = timezone.now()
25 | return super(Setting, self).save(*args, **kwargs)
26 |
27 |
28 | @receiver(post_save, sender=Setting)
29 | def setting_create_update_log(sender, **kwargs):
30 | from events.models import Event, AuditLog
31 | message = ""
32 | if kwargs['created']:
33 | message = "[Setting] New setting created (id={}): {}".format(kwargs['instance'].id, kwargs['instance'])
34 | Event.objects.create(message=message, type="CREATE", severity="DEBUG")
35 | else:
36 | message = "[Setting] Setting '{}' modified (id={})".format(kwargs['instance'], kwargs['instance'].id)
37 | Event.objects.create(message=message, type="UPDATE", severity="DEBUG")
38 |
39 | AuditLog.objects.create(
40 | message=message,
41 | scope='setting', type='rsetting_create_update',
42 | request_context=inspect.stack())
43 |
44 |
45 | @receiver(post_delete, sender=Setting)
46 | def setting_delete_log(sender, **kwargs):
47 | from events.models import Event, AuditLog
48 | message = "[Setting] Setting '{}' deleted (id={})".format(kwargs['instance'], kwargs['instance'].id)
49 | Event.objects.create(message=message, type="DELETE", severity="DEBUG")
50 |
51 | AuditLog.objects.create(
52 | message=message,
53 | scope='setting', type='setting_delete',
54 | request_context=inspect.stack())
55 |
--------------------------------------------------------------------------------
/settings/tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.test import TestCase
4 |
5 | # Create your tests here.
6 |
--------------------------------------------------------------------------------
/settings/urls.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.conf.urls import url
4 | from . import views, apis
5 |
6 |
7 | urlpatterns = [
8 | ## WEB Views
9 | # ex: /settings/
10 | url(r'^$', views.show_settings_menu, name='show_settings_menu'),
11 | url(r'^support$', views.show_support_page, name='show_support_page'),
12 |
13 | ## API views
14 | # ex: /settings/api/v1/update
15 | url(r'^api/v1/update$', apis.update_setting_api, name='update_setting_api'),
16 | # ex: /settings/api/v1/add
17 | url(r'^api/v1/add$', apis.add_setting_api, name='add_setting_api'),
18 | # ex: /settings/api/v1/delete/3
19 | url(r'^api/v1/delete/(?P[0-9]+)$', apis.delete_setting_api, name='delete_setting_api'),
20 | # ex: /settings/api/v1/export
21 | url(r'^api/v1/export$', apis.export_settings_api, name='export_settings_api'),
22 |
23 | ]
24 |
--------------------------------------------------------------------------------
/settings/views.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """View and API definitions for Settings."""
3 |
4 | from django.shortcuts import render
5 | # from django.contrib.auth.models import User
6 | from django.contrib.auth import get_user_model
7 | from django.db.models import F
8 | from .models import Setting
9 | from events.models import Event
10 | from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
11 |
12 |
13 | def show_settings_menu(request):
14 | """View: List settings menus."""
15 | users = get_user_model().objects.all().annotate(apitoken=F('auth_token'))
16 | settings = Setting.objects.all().order_by("key")
17 | events_list = Event.objects.all().order_by("-id")
18 |
19 | nb_events_rows = int(request.GET.get('n_events', 25))
20 | # events_paginator = CursorPaginator(events_list, ordering=['-id'])
21 | page_events = request.GET.get('p_events', 1)
22 | paginator_events = Paginator(events_list, nb_events_rows)
23 | try:
24 | events = paginator_events.page(page_events)
25 | except PageNotAnInteger:
26 | events = paginator_events.page(1)
27 | except EmptyPage:
28 | events = paginator_events.page(paginator_events.num_pages)
29 |
30 | return render(request, 'menu-settings.html', {
31 | 'users': users,
32 | 'settings': settings,
33 | 'events': events
34 | })
35 |
36 |
37 | def show_support_page(request):
38 | """View: Support page."""
39 | return render(request, 'support.html')
40 |
--------------------------------------------------------------------------------
/start-server.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ## Starting Patrowl back-office server. MacOs compliant only.
4 | source env3/bin/activate
5 | if [ `ps waxu | grep -c postgres` -ne "1" ]; then
6 | echo "postgres db started. Nothing to do"
7 | else
8 | echo -e "postgres db stopped. Start DB First !\nExiting."
9 | exit 1
10 | fi
11 |
12 | if [ `ps waxu | grep -c supervisord` -ne "1" ]; then
13 | echo "supervisord started. Nothing to do"
14 | else
15 | echo -e "supervisord stopped. Starting supervisord."
16 | supervisord -c var/etc/supervisord.conf
17 | fi
18 | python manage.py makemigrations && \
19 | python manage.py migrate && \
20 | python manage.py collectstatic --noinput && \
21 | gunicorn app.wsgi:application -b :8000 --timeout 300 --access-logfile -
22 |
23 | deactivate
24 |
--------------------------------------------------------------------------------
/static/css/bootstrap-tagsinput.css:
--------------------------------------------------------------------------------
1 | .bootstrap-tagsinput {
2 | background-color: #fff;
3 | border: 1px solid #ccc;
4 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
5 | display: inline-block;
6 | padding: 4px 6px;
7 | color: #555;
8 | vertical-align: middle;
9 | border-radius: 4px;
10 | max-width: 100%;
11 | line-height: 22px;
12 | cursor: text;
13 | }
14 | .bootstrap-tagsinput input {
15 | border: none;
16 | box-shadow: none;
17 | outline: none;
18 | background-color: transparent;
19 | padding: 0 6px;
20 | margin: 0;
21 | width: auto;
22 | max-width: inherit;
23 | }
24 | .bootstrap-tagsinput.form-control input::-moz-placeholder {
25 | color: #777;
26 | opacity: 1;
27 | }
28 | .bootstrap-tagsinput.form-control input:-ms-input-placeholder {
29 | color: #777;
30 | }
31 | .bootstrap-tagsinput.form-control input::-webkit-input-placeholder {
32 | color: #777;
33 | }
34 | .bootstrap-tagsinput input:focus {
35 | border: none;
36 | box-shadow: none;
37 | }
38 | .bootstrap-tagsinput .tag {
39 | margin-right: 2px;
40 | color: white;
41 | }
42 | .bootstrap-tagsinput .tag [data-role="remove"] {
43 | margin-left: 8px;
44 | cursor: pointer;
45 | }
46 | .bootstrap-tagsinput .tag [data-role="remove"]:after {
47 | content: "x";
48 | padding: 0px 2px;
49 | }
50 | .bootstrap-tagsinput .tag [data-role="remove"]:hover {
51 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
52 | }
53 | .bootstrap-tagsinput .tag [data-role="remove"]:hover:active {
54 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
55 | }
56 |
--------------------------------------------------------------------------------
/static/css/cal-heatmap.css:
--------------------------------------------------------------------------------
1 | /* Cal-HeatMap CSS */
2 |
3 | .cal-heatmap-container {
4 | display: block;
5 | }
6 |
7 | .cal-heatmap-container .graph
8 | {
9 | font-family: "Lucida Grande", Lucida, Verdana, sans-serif;
10 | }
11 |
12 | .cal-heatmap-container .graph-label
13 | {
14 | fill: #999;
15 | font-size: 10px
16 | }
17 |
18 | .cal-heatmap-container .graph, .cal-heatmap-container .graph-legend rect {
19 | shape-rendering: crispedges
20 | }
21 |
22 | .cal-heatmap-container .graph-rect
23 | {
24 | fill: #ededed
25 | }
26 |
27 | .cal-heatmap-container .graph-subdomain-group rect:hover
28 | {
29 | stroke: #000;
30 | stroke-width: 1px
31 | }
32 |
33 | .cal-heatmap-container .subdomain-text {
34 | font-size: 8px;
35 | fill: #999;
36 | pointer-events: none
37 | }
38 |
39 | .cal-heatmap-container .hover_cursor:hover {
40 | cursor: pointer
41 | }
42 |
43 | .cal-heatmap-container .qi {
44 | background-color: #999;
45 | fill: #999
46 | }
47 |
48 | /*
49 | Remove comment to apply this style to date with value equal to 0
50 | .q0
51 | {
52 | background-color: #fff;
53 | fill: #fff;
54 | stroke: #ededed
55 | }
56 | */
57 |
58 | .cal-heatmap-container .q1
59 | {
60 | background-color: #dae289;
61 | fill: #dae289
62 | }
63 |
64 | .cal-heatmap-container .q2
65 | {
66 | background-color: #cedb9c;
67 | fill: #9cc069
68 | }
69 |
70 | .cal-heatmap-container .q3
71 | {
72 | background-color: #b5cf6b;
73 | fill: #669d45
74 | }
75 |
76 | .cal-heatmap-container .q4
77 | {
78 | background-color: #637939;
79 | fill: #637939
80 | }
81 |
82 | .cal-heatmap-container .q5
83 | {
84 | background-color: #3b6427;
85 | fill: #3b6427
86 | }
87 |
88 | .cal-heatmap-container rect.highlight
89 | {
90 | stroke:#444;
91 | stroke-width:1
92 | }
93 |
94 | .cal-heatmap-container text.highlight
95 | {
96 | fill: #444
97 | }
98 |
99 | .cal-heatmap-container rect.highlight-now
100 | {
101 | stroke: red
102 | }
103 |
104 | .cal-heatmap-container text.highlight-now
105 | {
106 | fill: red;
107 | font-weight: 800
108 | }
109 |
110 | .cal-heatmap-container .domain-background {
111 | fill: none;
112 | shape-rendering: crispedges
113 | }
114 |
115 | .ch-tooltip {
116 | padding: 10px;
117 | background: #222;
118 | color: #bbb;
119 | font-size: 12px;
120 | line-height: 1.4;
121 | width: 140px;
122 | position: absolute;
123 | z-index: 99999;
124 | text-align: center;
125 | border-radius: 2px;
126 | box-shadow: 2px 2px 2px rgba(0,0,0,0.2);
127 | display: none;
128 | box-sizing: border-box;
129 | }
130 |
131 | .ch-tooltip::after{
132 | position: absolute;
133 | width: 0;
134 | height: 0;
135 | border-color: transparent;
136 | border-style: solid;
137 | content: "";
138 | padding: 0;
139 | display: block;
140 | bottom: -6px;
141 | left: 50%;
142 | margin-left: -6px;
143 | border-width: 6px 6px 0;
144 | border-top-color: #222;
145 | }
146 |
--------------------------------------------------------------------------------
/static/css/family-Lato.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Lato';
3 | font-style: italic;
4 | font-weight: 400;
5 | src: local('Lato Italic'), local('Lato-Italic'), url(/static/fonts/S6u8w4BMUTPHjxsAXC-v.ttf) format('truetype');
6 | }
7 | @font-face {
8 | font-family: 'Lato';
9 | font-style: normal;
10 | font-weight: 400;
11 | src: local('Lato Regular'), local('Lato-Regular'), url(/static/fonts/S6uyw4BMUTPHjx4wWw.ttf) format('truetype');
12 | }
13 | @font-face {
14 | font-family: 'Lato';
15 | font-style: normal;
16 | font-weight: 700;
17 | src: local('Lato Bold'), local('Lato-Bold'), url(/static/fonts/S6u9w4BMUTPHh6UVSwiPHA.ttf) format('truetype');
18 | }
19 |
--------------------------------------------------------------------------------
/static/css/images/ui-bg_flat_75_ffffff_40x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/static/css/images/ui-bg_flat_75_ffffff_40x100.png
--------------------------------------------------------------------------------
/static/css/patrowlmanager.css:
--------------------------------------------------------------------------------
1 | /* Modal */
2 | .modal.right .modal-dialog, .modal.left .modal-dialog {
3 | position: fixed;
4 | margin: auto;
5 | width: 700px;
6 | height: 100%;
7 | -webkit-transform: translate3d(0%, 0, 0);
8 | -ms-transform: translate3d(0%, 0, 0);
9 | -o-transform: translate3d(0%, 0, 0);
10 | transform: translate3d(0%, 0, 0);
11 | }
12 |
13 | .modal.right .modal-content, .modal.left .modal-content {
14 | /*height: 100%;*/
15 | overflow-y: auto;
16 | }
17 |
18 | .modal.right .modal-body, .modal.left .modal-body {
19 | padding: 15px 15px 20px;
20 | }
21 |
22 | .modal.right.fade .modal-dialog {
23 | right: -320px;
24 | -webkit-transition: opacity 0.3s linear, right 0.3s ease-out;
25 | -moz-transition: opacity 0.3s linear, right 0.3s ease-out;
26 | -o-transition: opacity 0.3s linear, right 0.3s ease-out;
27 | transition: opacity 0.3s linear, right 0.3s ease-out;
28 | }
29 |
30 | .modal.right.fade.in .modal-dialog {
31 | right: 0;
32 | }
33 |
34 | .modal.left.fade .modal-dialog{
35 | left: -320px;
36 | -webkit-transition: opacity 0.3s linear, left 0.3s ease-out;
37 | -moz-transition: opacity 0.3s linear, left 0.3s ease-out;
38 | -o-transition: opacity 0.3s linear, left 0.3s ease-out;
39 | transition: opacity 0.3s linear, left 0.3s ease-out;
40 | }
41 |
42 | .modal.left.fade.in .modal-dialog {
43 | left: 0;
44 | }
45 |
46 | /* Twitter Typehead */
47 | .twitter-typeahead, .tt-hint, .tt-input, .tt-menu { width: 100%; }
48 |
49 | .tt-query {
50 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
51 | }
52 |
53 | .tt-hint {
54 | color: #999;
55 | }
56 |
57 | .tt-menu {
58 | /* width: 422px; */
59 | margin-top: 12px;
60 | padding: 8px 0;
61 | background-color: #fff;
62 | border: 1px solid #ccc;
63 | border: 1px solid rgba(0, 0, 0, 0.2);
64 | border-radius: 8px;
65 | box-shadow: 0 5px 10px rgba(0,0,0,.2);
66 | }
67 |
68 | .tt-suggestion {
69 | padding: 3px 20px;
70 | /* font-size: 18px; */
71 | line-height: 24px;
72 | }
73 |
74 | .tt-suggestion.tt-is-under-cursor {
75 | color: #fff;
76 | background-color: #0097cf;
77 |
78 | }
79 |
80 | .tt-suggestion p {
81 | margin: 0;
82 | }
83 |
84 | /* Other */
85 | /* .no_padding {
86 | padding-left: 0px;
87 | padding-right: 0px;
88 | } */
89 | input.search-input {
90 | float: left;
91 | width: auto;
92 | margin: 5px;
93 | border-width: 2px;
94 | border-style: outset;
95 | border-color: -internal-light-dark(rgb(118, 118, 118), rgb(133, 133, 133));
96 | border-image: initial;
97 | background-color: -internal-light-dark(rgb(239, 239, 239), rgb(59, 59, 59));
98 | padding: 1px 6px;
99 | height: 27px;
100 | }
101 | button.search-input-button{
102 | margin: 5px;
103 | }
104 | div.div-override{
105 | margin: 5px 0px;
106 | }
--------------------------------------------------------------------------------
/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/static/favicon.ico
--------------------------------------------------------------------------------
/static/fonts/S6u8w4BMUTPHjxsAXC-v.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/static/fonts/S6u8w4BMUTPHjxsAXC-v.ttf
--------------------------------------------------------------------------------
/static/fonts/S6u9w4BMUTPHh6UVSwiPHA.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/static/fonts/S6u9w4BMUTPHh6UVSwiPHA.ttf
--------------------------------------------------------------------------------
/static/fonts/S6uyw4BMUTPHjx4wWw.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/static/fonts/S6uyw4BMUTPHjx4wWw.ttf
--------------------------------------------------------------------------------
/static/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/static/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/static/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/static/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/static/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/static/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/static/fonts/glyphicons-halflings-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/static/fonts/glyphicons-halflings-regular.woff2
--------------------------------------------------------------------------------
/static/images/sort_asc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/static/images/sort_asc.png
--------------------------------------------------------------------------------
/static/images/sort_asc_disabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/static/images/sort_asc_disabled.png
--------------------------------------------------------------------------------
/static/images/sort_both.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/static/images/sort_both.png
--------------------------------------------------------------------------------
/static/images/sort_desc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/static/images/sort_desc.png
--------------------------------------------------------------------------------
/static/tmpl/assets_bulk_import_template.csv:
--------------------------------------------------------------------------------
1 | asset_value;asset_name;asset_type;asset_description;asset_criticality;asset_groupname;asset_tags;asset_teams;asset_exposure
2 | patrowl.io;Patrowl domain name;domain;Patrowl domain name;high;recently_imported;my_tag;default;external
3 | 8.8.8.8;Google DNS A IP addr;ip;Google DNS A IP addr;low;recently_imported;my_tag;default;external
4 | 8.8.4.4;Google DNS B IP addr;ip;Google DNS B IP addr;low;recently_imported;my_tag;default;external
5 |
--------------------------------------------------------------------------------
/static/tmpl/users_bulk_import_template.csv:
--------------------------------------------------------------------------------
1 | username;first_name;last_name;email;is_active;role;teams
2 | user01;fn user01;ln user01;user01@patrowl.io;true;Auditor;default
3 | user02;fn user02;ln user02;user02@patrowl.io;false;Manager;
4 | user03;fn user03;ln user03;user03@patrowl.io;true;BadRole;
5 |
--------------------------------------------------------------------------------
/templates/errors/400.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
5 |
6 |
7 |
8 | 400, Error page ...
9 |
10 |
11 |
12 |
13 |
14 |
15 | {% endblock %}
16 |
--------------------------------------------------------------------------------
/templates/errors/403.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
5 |
6 |
7 |
8 | 403, Forbbiden. Please go away ...
9 |
10 |
11 |
12 |
13 |
14 |
15 | {% endblock %}
16 |
--------------------------------------------------------------------------------
/templates/errors/404.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
5 |
6 |
7 |
8 | 404, Page not found ...
9 |
10 |
11 |
12 |
13 |
14 |
15 | {% endblock %}
16 |
--------------------------------------------------------------------------------
/templates/errors/500.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
5 |
6 |
7 |
8 | 500, Server Error ...
9 |
10 |
11 |
12 |
13 |
14 |
15 | {% endblock %}
16 |
--------------------------------------------------------------------------------
/templates/login.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 | {% if form.errors %}
5 | Your username and password didn't match. Please try again.
6 | {% endif %}
7 | {% if next %}
8 | {% if user.is_authenticated %}
9 |
10 | Your account doesn't have access to this page. To proceed, please login with an account that has access.
11 | {% else %}
12 |
13 | Please login to see this page.
14 | {% endif %}
15 | {% endif %}
16 |
17 |
18 |
19 |
20 |
21 |
22 |
Please Sign In
23 |
24 |
36 |
37 |
38 |
39 | {% if PRO_EDITION == True and LOGIN_SSO_URL != "" %}
40 |
41 |
42 |
43 |
44 |
Sign In with SSO
45 |
46 |
49 |
50 |
51 |
52 | {% endif %}
53 |
54 |
55 | {% endblock %}
56 | {% block javascript %}
57 |
58 |
59 |
64 |
65 | {% endblock %}
66 |
--------------------------------------------------------------------------------
/templates/signup.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 | Sign up
5 |
18 | {% endfor %}
19 |
20 |
21 | {% endblock %}
22 |
--------------------------------------------------------------------------------
/templatetags/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/templatetags/__init__.py
--------------------------------------------------------------------------------
/tests/test_sample.py:
--------------------------------------------------------------------------------
1 | def test_always_valid():
2 | assert True == True
3 |
--------------------------------------------------------------------------------
/users/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/users/__init__.py
--------------------------------------------------------------------------------
/users/admin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.contrib import admin
4 | from .models import Profile
5 |
6 | admin.site.register(Profile)
7 |
--------------------------------------------------------------------------------
/users/apps.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from __future__ import unicode_literals
4 |
5 | from django.apps import AppConfig
6 |
7 |
8 | class UsersConfig(AppConfig):
9 | name = 'users'
10 |
--------------------------------------------------------------------------------
/users/forms.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.forms import AuthenticationForm
2 | from django import forms
3 |
4 |
5 | class LoginForm(AuthenticationForm):
6 | username = forms.CharField(
7 | label="Username", max_length=250,
8 | widget=forms.TextInput(attrs={
9 | 'class': 'form-control', 'name': 'username', 'value': ''}))
10 | password = forms.CharField(
11 | label="Password", max_length=250,
12 | widget=forms.PasswordInput(attrs={
13 | 'class': 'form-control',
14 | 'name': 'password',
15 | 'value': '',
16 | 'autocomplete': 'off',
17 | }))
18 |
--------------------------------------------------------------------------------
/users/middleware.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import re
4 | from django.conf import settings
5 | from django.contrib.auth.decorators import login_required
6 |
7 |
8 | class RequireLoginMiddleware(object):
9 | """
10 | Middleware component that wraps the login_required decorator around
11 | matching URL patterns. To use, add the class to MIDDLEWARE_CLASSES and
12 | define LOGIN_REQUIRED_URLS and LOGIN_REQUIRED_URLS_EXCEPTIONS in your
13 | settings.py. For example:
14 | ------
15 | LOGIN_REQUIRED_URLS = (
16 | r'/topsecret/(.*)$',
17 | )
18 | LOGIN_REQUIRED_URLS_EXCEPTIONS = (
19 | r'/topsecret/login(.*)$',
20 | r'/topsecret/logout(.*)$',
21 | )
22 | ------
23 | LOGIN_REQUIRED_URLS is where you define URL patterns; each pattern must
24 | be a valid regex.
25 |
26 | LOGIN_REQUIRED_URLS_EXCEPTIONS is, conversely, where you explicitly
27 | define any exceptions (like login and logout URLs).
28 | """
29 |
30 | def __init__(self, get_response=None):
31 | self.required = tuple(
32 | re.compile(url) for url in settings.LOGIN_REQUIRED_URLS
33 | )
34 | self.exceptions = tuple(
35 | re.compile(url) for url in settings.LOGIN_REQUIRED_URLS_EXCEPTIONS
36 | )
37 | self.get_response = get_response
38 |
39 | def __call__(self, request):
40 | response = self.get_response(request)
41 | return response
42 |
43 | def process_view(self, request, view_func, view_args, view_kwargs):
44 | # No need to process URLs if user already logged in
45 | if request.user.is_authenticated:
46 | return None
47 |
48 | # An exception match should immediately return None
49 | for url in self.exceptions:
50 | if url.match(request.path):
51 | return None
52 |
53 | # Requests matching a restricted URL pattern are returned
54 | # wrapped with the login_required decorator
55 | for url in self.required:
56 | if url.match(request.path):
57 | return login_required(view_func)(request, *view_args, **view_kwargs)
58 |
59 | # Explicitly return None for all non-matching requests
60 | return None
61 |
--------------------------------------------------------------------------------
/users/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.5 on 2019-09-17 23:07
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 | import django.utils.timezone
7 |
8 |
9 | class Migration(migrations.Migration):
10 |
11 | initial = True
12 |
13 | dependencies = [
14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
15 | ]
16 |
17 | operations = [
18 | migrations.CreateModel(
19 | name='Profile',
20 | fields=[
21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
22 | ('status', models.CharField(choices=[('ACTIVE', 'ACTIVE'), ('DISABLED', 'DISABLED')], default='ACTIVE', max_length=10)),
23 | ('bio', models.TextField(blank=True, max_length=500)),
24 | ('department', models.CharField(blank=True, max_length=100)),
25 | ('created_at', models.DateTimeField(default=django.utils.timezone.now)),
26 | ('updated_at', models.DateTimeField(default=django.utils.timezone.now)),
27 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
28 | ],
29 | ),
30 | ]
31 |
--------------------------------------------------------------------------------
/users/migrations/0003_auto_20200624_0037.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.13 on 2020-06-23 22:37
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('users', '0002_auto_20200616_1704'),
10 | ]
11 |
12 | operations = [
13 | migrations.RemoveField(
14 | model_name='profile',
15 | name='bio',
16 | ),
17 | migrations.RemoveField(
18 | model_name='profile',
19 | name='department',
20 | ),
21 | migrations.AddField(
22 | model_name='profile',
23 | name='role',
24 | field=models.PositiveSmallIntegerField(blank=True, choices=[(1, 'Manager'), (2, 'Analyst'), (3, 'Auditor')], null=True),
25 | ),
26 | ]
27 |
--------------------------------------------------------------------------------
/users/migrations/0004_remove_profile_role.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.13 on 2020-06-23 22:43
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('users', '0003_auto_20200624_0037'),
10 | ]
11 |
12 | operations = [
13 | migrations.RemoveField(
14 | model_name='profile',
15 | name='role',
16 | ),
17 | ]
18 |
--------------------------------------------------------------------------------
/users/migrations/0005_profile_is_delegated.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.13 on 2020-07-08 22:56
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('users', '0004_remove_profile_role'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='profile',
15 | name='is_delegated',
16 | field=models.BooleanField(default=True),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/users/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Patrowl/PatrowlManager/d5f0a23cf8eebd7a393dfa2b73e4274f34891dce/users/migrations/__init__.py
--------------------------------------------------------------------------------
/users/permissions.py:
--------------------------------------------------------------------------------
1 | from rest_framework import permissions
2 |
3 |
4 | class IsOwnerOrReadOnly(permissions.BasePermission):
5 |
6 | def has_object_permission(self, request, view, obj):
7 | return obj == request.user
8 |
--------------------------------------------------------------------------------
/users/serializers.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from rest_framework import serializers, generics, views, response, permissions
4 | from django_filters import rest_framework as filters
5 | # from django.contrib.auth.models import User
6 | from django.contrib.auth import get_user_model
7 |
8 | # from rest_framework.authentication import SessionAuthentication, BasicAuthentication
9 | # from rest_framework.decorators import api_view
10 |
11 |
12 | class UserSerializer(serializers.ModelSerializer):
13 |
14 | class Meta:
15 | model = get_user_model()
16 | fields = ('id', 'username', 'last_login', 'is_superuser', 'is_staff')
17 |
18 |
19 | class UserList(generics.ListAPIView):
20 | queryset = get_user_model().objects.all()
21 | serializer_class = UserSerializer
22 | filter_backends = (filters.DjangoFilterBackend,)
23 | # filterset_fields = ('title', 'severity', 'engine_type')
24 |
25 |
26 | class CurrentUserView(views.APIView):
27 | # authentication_classes = [SessionAuthentication, BasicAuthentication, JWTAuthentication]
28 | # permission_classes = [IsAuthenticated]
29 | # permission_classes = [permissions.IsAdminUser]
30 |
31 | # @api_view(['GET', 'OPTIONS', 'POST'])
32 | def get(self, request):
33 | serializer = UserSerializer(request.user, context={'request': request})
34 | # print(serializer)
35 | # print(self.check_object_permissions(self.request, serializer))
36 | return response.Response(serializer.data)
37 |
--------------------------------------------------------------------------------
/users/templates/add-user.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
9 |
10 |
43 |
44 | {% endblock %}
45 |
--------------------------------------------------------------------------------
/users/templates/details-user.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load patrowl_tags %}
3 | {% block content %}
4 |
5 |
6 |
User details
7 |
8 |
9 | - User ID
- {{ user.id }}
10 | - Username
- {{ user.username }}
11 | - Role
-
12 | {% if user.is_superuser %}
13 | SuperAdmin
14 | {% else %}
15 | {{user.userrole.get_role_display}}
16 | {% endif %}
17 |
18 | - API Token
- {{ apitoken }}
19 | - First Name
- {{ user.first_name }}
20 | - Last Name
- {{ user.last_name }}
21 | - Email
- {{ user.email }}
22 | - Is Active?
- {{ user.is_active }}
23 | - Is Superuser?
- {{ user.is_superuser }}
24 | - Is Staff?
- {{ user.is_staff }}
25 | - Created at
- {{ user.date_joined|smartdate }}
26 | - Last Login
- {{ user.last_login|smartdate }}
27 |
28 |
29 |
30 |
31 |
32 | {% if messages %}
33 |
34 | {% for message in messages %}
35 | - {{ message }}
36 | {% endfor %}
37 |
38 | {% endif %}
39 |
40 |
41 |
42 | {% endblock %}
43 |
--------------------------------------------------------------------------------
/users/templates/edit-user-password.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
4 |
9 |
10 |
42 |
43 | {% endblock %}
44 |
--------------------------------------------------------------------------------
/users/tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.test import TestCase
4 |
5 | # Create your tests here.
6 |
--------------------------------------------------------------------------------
/users/urls.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.urls import path
4 | from django.conf.urls import url
5 | from . import views, apis, serializers
6 | from reportings import views as rep_views
7 |
8 |
9 | urlpatterns = [
10 |
11 | # Views
12 | url(r'^$', rep_views.homepage_dashboard_view,
13 | name='homepage_dashboard_view'),
14 | url(r'^list$', views.list_users_view,
15 | name='list_users_view'),
16 | url(r'^dashboard$', rep_views.homepage_dashboard_view,
17 | name='homepage_dashboard_view'),
18 | url(r'^details$', views.user_details_view,
19 | name='user_details_view'),
20 | url(r'^add$', views.add_user_view,
21 | name='add_user_view'),
22 | url(r'^editpw$', views.edit_user_password_view,
23 | name='edit_user_password_view'),
24 |
25 | # REST-API Endpoints
26 | url(r'^users/api/v1/details/(?P[0-9]+)$',
27 | apis.user_details_api, name='user_details_api'),
28 | url(r'^users/api/v1/delete/(?P[0-9]+)$',
29 | apis.delete_user_api, name='delete_user_api'),
30 | url(r'^users/api/v1/list$',
31 | apis.list_users_api, name='list_users_api'),
32 | url(r'^users/api/v1/authtoken/get$',
33 | apis.get_curruser_authtoken_api, name='get_curruser_authtoken_api'),
34 | url(r'^users/api/v1/authtoken/get/(?P[0-9]+)$',
35 | apis.get_user_authtoken_api, name='get_user_authtoken_api'),
36 | url(r'^users/api/v1/authtoken/renew$',
37 | apis.renew_curruser_authtoken_api, name='renew_curruser_authtoken_api'),
38 | url(r'^users/api/v1/authtoken/renew/(?P[0-9]+)$',
39 | apis.renew_user_authtoken_api, name='renew_user_authtoken_api'),
40 | url(r'^users/api/v1/authtoken/delete$',
41 | apis.delete_curruser_authtoken_api, name='delete_curruser_authtoken_api'),
42 | url(r'^users/api/v1/authtoken/delete/(?P[0-9]+)$',
43 | apis.delete_user_authtoken_api, name='delete_user_authtoken_api'),
44 | url(r'^users/api/v1/(?P[0-9]+)/renewpassword$',
45 | apis.renew_user_password_api, name='renew_user_password_api'),
46 | ]
47 |
48 | # Serialized data
49 | urlpatterns += [
50 | path('api/list', serializers.UserList.as_view()),
51 | path('api/current', serializers.CurrentUserView.as_view()),
52 | ]
53 |
--------------------------------------------------------------------------------
/var/bin/create_default_admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import get_user_model
2 | User = get_user_model()
3 | if not User.objects.filter(username='admin').exists():
4 | User.objects.create_superuser('admin', 'admin@dev.patrowl.io', 'Bonjour1!')
5 |
--------------------------------------------------------------------------------
/var/bin/create_default_team.py:
--------------------------------------------------------------------------------
1 | from users.models import Team, TeamUser, TeamOwner
2 | from django.contrib.auth import get_user_model
3 | admin_user = get_user_model().objects.get(username='admin')
4 | if admin_user.users_team.count() == 0:
5 | admin_org = Team.objects.create(name='default', is_active=True)
6 | admin_org.save()
7 | org_user = TeamUser.objects.create(user=admin_user, organization=admin_org, is_admin=True)
8 | org_user.save()
9 | org_owner = TeamOwner.objects.create(organization=admin_org, organization_user=org_user)
10 | org_owner.save()
11 |
--------------------------------------------------------------------------------
/var/bin/update_db_migrations.sh:
--------------------------------------------------------------------------------
1 | # verlte() {
2 | # [ "$1" = "`echo -e "$1\n$2" | sort -V | head -n1`" ]
3 | # }
4 | #
5 | # # Check version for compatibility, otherwise exit
6 | # verlte `cat VERSION | cut -f1 -d" "` 1.5.4 && echo "Let's go" || { echo "Use current migration files. Exit now" ; exit; }
7 |
8 | echo "-- Migrate django_celery_beat"
9 | python manage.py migrate django_celery_beat
10 |
11 | echo "-- Installed tables:"
12 | echo "from django.db import connection ; print(connection.introspection.table_names()) " | python manage.py shell
13 |
14 | if echo "from django.db import connection ; print('assets' in connection.introspection.table_names()) " | python manage.py shell | grep -q 'True'; then
15 |
16 | echo "-- Clean the django_migrations table"
17 | echo "from django.db import connection; cursor = connection.cursor(); cursor.execute('delete from django_migrations')" | python manage.py shell
18 |
19 | echo "-- Remove 'migrations' folders"
20 | rm -rf events/migrations
21 | rm -rf users/migrations
22 | rm -rf scans/migrations
23 | rm -rf assets/migrations
24 | rm -rf findings/migrations
25 | rm -rf rules/migrations
26 | rm -rf settings/migrations
27 |
28 | echo "-- Apply fake migrations for built-in apps"
29 | python manage.py migrate --fake
30 |
31 | echo "-- Run syncdb on every apps"
32 | python manage.py migrate events --run-syncdb
33 | python manage.py migrate users --run-syncdb
34 | python manage.py migrate scans --run-syncdb
35 | python manage.py migrate assets --run-syncdb
36 | python manage.py migrate findings --run-syncdb
37 | python manage.py migrate rules --run-syncdb
38 | python manage.py migrate settings --run-syncdb
39 |
40 | echo "-- Make migrations on every apps"
41 | python manage.py makemigrations events
42 | python manage.py makemigrations users
43 | python manage.py makemigrations scans
44 | python manage.py makemigrations assets
45 | python manage.py makemigrations findings
46 | python manage.py makemigrations rules
47 | python manage.py makemigrations settings
48 |
49 | echo "-- Apply migration (fake initial)"
50 | python manage.py migrate --fake-initial
51 |
52 | echo "-- Apply assets migrations (from 1.6.0)"
53 | cp var/migrations/assets/0002_asset_exposure.py assets/migrations/0002_asset_exposure.py
54 | python manage.py migrate assets 0002
55 | fi
56 |
--------------------------------------------------------------------------------
/var/data/engines.EnginePolicyScope.json:
--------------------------------------------------------------------------------
1 | [{
2 | "model": "engines.enginepolicyscope",
3 | "pk": 1,
4 | "fields": {
5 | "name": "Network Infrastructure",
6 | "priority": null,
7 | "created_at": "2017-07-09T16:42:12Z",
8 | "updated_at": "2017-07-09T16:42:12Z"
9 | }
10 | }, {
11 | "model": "engines.enginepolicyscope",
12 | "pk": 2,
13 | "fields": {
14 | "name": "System infrastructure",
15 | "priority": null,
16 | "created_at": "2017-07-16T08:51:44Z",
17 | "updated_at": "2017-07-27T10:34:54.561Z"
18 | }
19 | }, {
20 | "model": "engines.enginepolicyscope",
21 | "pk": 3,
22 | "fields": {
23 | "name": "Domain",
24 | "priority": null,
25 | "created_at": "2017-07-27T10:34:56Z",
26 | "updated_at": "2017-07-27T10:34:56Z"
27 | }
28 | }, {
29 | "model": "engines.enginepolicyscope",
30 | "pk": 4,
31 | "fields": {
32 | "name": "Web App",
33 | "priority": null,
34 | "created_at": "2017-07-27T10:35:09Z",
35 | "updated_at": "2017-07-27T10:35:09Z"
36 | }
37 | }, {
38 | "model": "engines.enginepolicyscope",
39 | "pk": 5,
40 | "fields": {
41 | "name": "HTTPS & Certificates",
42 | "priority": null,
43 | "created_at": "2017-07-27T10:35:20Z",
44 | "updated_at": "2017-07-27T10:35:20Z"
45 | }
46 | }, {
47 | "model": "engines.enginepolicyscope",
48 | "pk": 6,
49 | "fields": {
50 | "name": "E-Reputation",
51 | "priority": null,
52 | "created_at": "2017-07-27T10:35:32Z",
53 | "updated_at": "2017-07-27T10:35:32Z"
54 | }
55 | }, {
56 | "model": "engines.enginepolicyscope",
57 | "pk": 7,
58 | "fields": {
59 | "name": "Malware",
60 | "priority": null,
61 | "created_at": "2017-07-27T10:35:41Z",
62 | "updated_at": "2017-07-27T10:35:41Z"
63 | }
64 | }, {
65 | "model": "engines.enginepolicyscope",
66 | "pk": 8,
67 | "fields": {
68 | "name": "Availability",
69 | "priority": null,
70 | "created_at": "2017-07-27T10:35:47Z",
71 | "updated_at": "2017-07-27T10:35:47Z"
72 | }
73 | }]
74 |
--------------------------------------------------------------------------------
/var/db/create_db.sql:
--------------------------------------------------------------------------------
1 | -- Create user if not exists
2 | DO $$
3 | BEGIN
4 | IF NOT EXISTS (
5 | SELECT FROM pg_catalog.pg_roles -- SELECT list can be empty for this
6 | WHERE rolname = 'PATROWL_DB_USER') THEN
7 |
8 | CREATE USER 'PATROWL_DB_USER' WITH PASSWORD 'PATROWL_DB_PASSWD_TO_CHANGE';
9 | END IF;
10 | END
11 | $$;
12 |
13 |
14 | SELECT 'CREATE DATABASE patrowl_db WITH OWNER "PATROWL_DB_USER"'
15 | WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'patrowl_db')\gexec
16 |
17 | ALTER ROLE "PATROWL_DB_USER" SET client_encoding TO 'utf8';
18 | ALTER ROLE "PATROWL_DB_USER" SET default_transaction_isolation TO 'read committed';
19 | GRANT ALL PRIVILEGES ON DATABASE "patrowl_db" TO "PATROWL_DB_USER";
20 |
--------------------------------------------------------------------------------
/var/db/create_user_and_db.sql:
--------------------------------------------------------------------------------
1 | CREATE USER "PATROWL_DB_USER" WITH PASSWORD 'PATROWL_DB_PASSWD_TO_CHANGE';
2 | CREATE DATABASE "patrowl_db" WITH OWNER "PATROWL_DB_USER";
3 | ALTER ROLE "PATROWL_DB_USER" SET client_encoding TO 'utf8';
4 | ALTER ROLE "PATROWL_DB_USER" SET default_transaction_isolation TO 'read committed';
5 | -- ALTER ROLE "PATROWL_DB_USER" SET timezone TO 'UTC';
6 | GRANT ALL PRIVILEGES ON DATABASE "patrowl_db" TO "PATROWL_DB_USER";
7 |
--------------------------------------------------------------------------------
/var/db/init_db.sql:
--------------------------------------------------------------------------------
1 | ALTER ROLE "PATROWL_DB_USER" SET client_encoding TO 'utf8';
2 | ALTER ROLE "PATROWL_DB_USER" SET default_transaction_isolation TO 'read committed';
3 | ALTER ROLE "PATROWL_DB_USER" SET timezone TO 'UTC';
4 | GRANT ALL PRIVILEGES ON DATABASE "patrowl_db" TO "PATROWL_DB_USER";
5 |
--------------------------------------------------------------------------------
/var/etc/supervisord-celery-pro.conf:
--------------------------------------------------------------------------------
1 | ;[program:celery-flow]
2 | ;command=env3/bin/celery --app=app flower --port=5555
3 | ;stdout_logfile=var/log/celeryd.flower.log
4 | ;stderr_logfile=var/log/celeryderr.flower.log
5 | ;autostart=true
6 | ;autorestart=true
7 | ;startsecs=5
8 | ;stopwaitsecs=60
9 | ;stopasgroup=true
10 | ;killasgroup=true
11 | ;priority=998
12 |
13 | [program:celery-beat]
14 | command=env3/bin/celery beat --app=app -l info -S django --pidfile="var/tmp/celerybeat.pid"
15 | environment=PRO_EDITION='True'
16 | stdout_logfile=var/log/celeryd.beat.log
17 | stderr_logfile=var/log/celeryderr.beat.log
18 | autostart=true
19 | autorestart=true
20 | startsecs=5
21 | stopwaitsecs=60
22 | stopasgroup=true
23 | killasgroup=true
24 | priority=999
25 | pidfile=var/tmp/celerybeat.pid
26 |
27 | [program:celery-default]
28 | command=env3/bin/celery worker --hostname=default-node@%%n --app=app -l info -Q default --purge --without-mingle --without-gossip --without-heartbeat -Ofair
29 | environment=PRO_EDITION='True'
30 | stdout_logfile=var/log/celeryd.default.log
31 | stderr_logfile=var/log/celeryderr.default.log
32 | autostart=true
33 | autorestart=true
34 | startsecs=5
35 | stopwaitsecs=10
36 | stopasgroup=true
37 | killasgroup=true
38 | priority=990
39 |
40 | [program:celery-scan]
41 | command=env3/bin/celery worker --hostname=scan-node@%%n --app=app -l info -Q scan --purge --without-mingle --without-gossip --without-heartbeat -Ofair
42 | environment=PRO_EDITION='True'
43 | stdout_logfile=var/log/celeryd.scan.log
44 | stderr_logfile=var/log/celeryderr.scan.log
45 | autostart=true
46 | autorestart=true
47 | startsecs=5
48 | stopwaitsecs=10
49 | stopasgroup=true
50 | killasgroup=true
51 |
52 | [program:celery-scanmgt]
53 | command=env3/bin/celery worker --hostname=scanmgt-node@%%n --app=app -l info -Q scanmgt --purge --without-mingle --without-gossip --without-heartbeat -Ofair
54 | environment=PRO_EDITION='True'
55 | stdout_logfile=var/log/celeryd.scanmgt.log
56 | stderr_logfile=var/log/celeryderr.scanmgt.log
57 | autostart=true
58 | autorestart=true
59 | startsecs=5
60 | stopwaitsecs=10
61 | stopasgroup=true
62 | killasgroup=true
63 | priority=990
64 |
65 | ;[group:celery-workers]
66 | ;programs=celery-flow,celery-beat,celery-default,celery-scan,celery-scanmgt
67 | ;priority=990
68 |
--------------------------------------------------------------------------------
/var/etc/supervisord-celery.conf:
--------------------------------------------------------------------------------
1 | ;[program:celery-flow]
2 | ;command=env3/bin/celery --app=app flower --port=5555
3 | ;stdout_logfile=var/log/celeryd.flower.log
4 | ;stderr_logfile=var/log/celeryderr.flower.log
5 | ;autostart=true
6 | ;autorestart=true
7 | ;startsecs=5
8 | ;stopwaitsecs=60
9 | ;stopasgroup=true
10 | ;killasgroup=true
11 | ;priority=998
12 |
13 | [program:celery-beat]
14 | command=env3/bin/celery beat --app=app -l info -S django --pidfile="var/tmp/celerybeat.pid"
15 | stdout_logfile=var/log/celeryd.beat.log
16 | stderr_logfile=var/log/celeryderr.beat.log
17 | autostart=true
18 | autorestart=true
19 | startsecs=5
20 | stopwaitsecs=60
21 | stopasgroup=true
22 | killasgroup=true
23 | priority=999
24 | pidfile=var/tmp/celerybeat.pid
25 |
26 | [program:celery-default]
27 | command=env3/bin/celery worker --hostname=default-node@%%n --app=app -l info -Q default --purge --without-mingle --without-gossip --without-heartbeat -Ofair
28 | stdout_logfile=var/log/celeryd.default.log
29 | stderr_logfile=var/log/celeryderr.default.log
30 | autostart=true
31 | autorestart=true
32 | startsecs=5
33 | stopwaitsecs=10
34 | stopasgroup=true
35 | killasgroup=true
36 | priority=990
37 |
38 | [program:celery-scan]
39 | command=env3/bin/celery worker --hostname=scan-node@%%n --app=app -l info -Q scan --purge --without-mingle --without-gossip --without-heartbeat -Ofair
40 | stdout_logfile=var/log/celeryd.scan.log
41 | stderr_logfile=var/log/celeryderr.scan.log
42 | autostart=true
43 | autorestart=true
44 | startsecs=5
45 | stopwaitsecs=10
46 | stopasgroup=true
47 | killasgroup=true
48 | priority=990
49 |
50 | [program:celery-scanmgt]
51 | command=env3/bin/celery worker --hostname=scanmgt-node@%%n --app=app -l info -Q scanmgt --purge --without-mingle --without-gossip --without-heartbeat -Ofair
52 | stdout_logfile=var/log/celeryd.scanmgt.log
53 | stderr_logfile=var/log/celeryderr.scanmgt.log
54 | autostart=true
55 | autorestart=true
56 | startsecs=5
57 | stopwaitsecs=10
58 | stopasgroup=true
59 | killasgroup=true
60 | priority=990
61 |
62 | ;[group:celery-workers]
63 | ;programs=celery-flow,celery-beat,celery-default,celery-scan,celery-scanmgt
64 | ;priority=990
65 |
--------------------------------------------------------------------------------
/var/etc/supervisord-pro.conf:
--------------------------------------------------------------------------------
1 | [inet_http_server]
2 | port=127.0.0.1:9001
3 |
4 | [supervisord]
5 | logfile=var/log/supervisord.log
6 | logfile_maxbytes=10MB
7 | pidfile=var/tmp/supervisord.pid
8 |
9 | [supervisorctl]
10 | serverurl=http://localhost:9001
11 | logfile_backups=10
12 | loglevel=info
13 |
14 | [rpcinterface:supervisor]
15 | supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
16 |
17 | [include]
18 | files=supervisord-celery-pro.conf
19 |
--------------------------------------------------------------------------------
/var/etc/supervisord.conf:
--------------------------------------------------------------------------------
1 | [inet_http_server]
2 | port=127.0.0.1:9001
3 |
4 | [supervisord]
5 | logfile=var/log/supervisord.log
6 | logfile_maxbytes=10MB
7 | pidfile=var/tmp/supervisord.pid
8 |
9 | [supervisorctl]
10 | serverurl=http://localhost:9001
11 | logfile_backups=10
12 | loglevel=info
13 |
14 | [rpcinterface:supervisor]
15 | supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
16 |
17 | [include]
18 | files=supervisord-celery.conf
19 |
--------------------------------------------------------------------------------
/var/log/.dockerignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.log
3 | *.log.[0-9]*
4 |
--------------------------------------------------------------------------------
/var/log/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.log
3 | *.log.[0-9]*
4 |
--------------------------------------------------------------------------------
/var/migrations/assets/0002_asset_exposure.py:
--------------------------------------------------------------------------------
1 | from django.db import migrations, models
2 |
3 |
4 | class Migration(migrations.Migration):
5 |
6 | dependencies = [
7 | ('assets', '0001_initial'),
8 | ]
9 |
10 | operations = [
11 | migrations.AddField(
12 | model_name='asset',
13 | name='exposure',
14 | field=models.CharField(choices=[('external', 'External'), ('internal', 'Internal'), ('restricted', 'Restricted')], default='external', max_length=16),
15 | ),
16 | migrations.AlterField(
17 | model_name='asset',
18 | name='exposure',
19 | field=models.CharField(choices=[('unknown', 'Unknown'), ('external', 'External'), ('internal', 'Internal'), ('restricted', 'Restricted')], default='unknown', max_length=16),
20 | ),
21 | ]
22 |
--------------------------------------------------------------------------------
/var/tmp/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.pid
3 |
--------------------------------------------------------------------------------