Skip to content

Commit 29d09c7

Browse files
pUrGe12arkid15r
andauthored
Port sqlite operations to APSW (#1076)
* porting sqlite operations to APSW and making necessary changes with unittests for db.py * ruff fixes * ruff fixes 2.0 * improved test_db.py * ruff fixes * fix for subdomains * ruff * updated poetry lock and merged master * fixing test_db.py, the other one is still failing because of some mocking issues * fixing tests * poetry lock updates * bot suggestions * update documentation for databases * spelling fix * docstring update * added logic to toggle between APSW and SQLAlchemy for sqlite databases for backward compatibility * updated docs * bug fixing * coderabbit suggested changes and test fixes * coderabbit suggested changes * closing connections if no targets are present * added new parameter for retires in config and ensured closure of all connections * triple quotes preserve whitespaces which causes indentation issues in tests and make them more fragile * some bug fixes * removed unncessary file * error handling * Update code * some formatting changes * removing report * used context managers for file opens in db.py and updated test to handle __enter__ (as earlier we didn't have to) * fix: removing duplicate python instance * fixed lock file --------- Signed-off-by: Achintya Jai <153343775+pUrGe12@users.noreply.github.com> Co-authored-by: Arkadii Yakovets <arkadii.yakovets@owasp.org>
1 parent 915f760 commit 29d09c7

29 files changed

+2370
-398
lines changed

docs/Usage.md

Lines changed: 55 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -646,58 +646,71 @@ python nettacker.py --start-api --api-access-log --api-port 8080 --api-debug-mod
646646
![](https://github.com/aman566/DiceGameJS/blob/master/Screencast-from-Tuesday-09-June-2020-02-32-32-IST-_online-video-cutter.com_.gif)
647647

648648
# Database
649-
OWASP Nettacker, currently supports two databases:
650-
- SQLite
649+
650+
OWASP Nettacker, currently supports three databases:
651+
651652
- MySQL
653+
- PostgreSQL
654+
- SQLite
655+
652656
The default database is SQLite. You can, however, configure the db to your liking.
657+
653658
## SQLite configuration
654-
The SQLite database can be configured in `core/config.py` file under the `_database_config()` function. Here is a sample configuration:
655-
```
656-
return {
657-
"DB": "sqlite",
658-
"DATABASE": _paths()["home_path"] + "/nettacker.db", # This is the location of your db
659-
"USERNAME": "",
660-
"PASSWORD": "",
661-
"HOST": "",
662-
"PORT": ""
663-
}
659+
660+
The configurations below are for a SQLite wrapper called **APSW** (Another Python SQLite Wrapper). The configurations can be found inside `nettacker/config.py` file under the `DBConfig` class.
661+
662+
```python
663+
engine = "sqlite"
664+
name = str(CWD / ".nettacker/data/nettacker.db")
665+
host = ""
666+
port = ""
667+
username = ""
668+
password = ""
669+
ssl_mode = "disable"
670+
journal_mode = "WAL"
671+
synchronous_mode = "NORMAL"
664672
```
673+
674+
These are the default and recommended settings. Feel free to play around and change them according to need. To use SQLite database, ensure that the `engine` value is set to `sqlite` and the `name` is the path to your database. The `journal_mode` and `synchronous_mode` are chosen to be optimal for multithreaded I/O operations.
675+
676+
> Note: You can choose to use a lite wrapper for Sqlite called APSW by setting the `use_apsw_for_sqlite` parameter inside config to True for performance enhancements.
677+
665678
## MySQL configuration:
666-
The MySQL database can be configured in `core/config.py` file under the `_database_config()` function. Here is a sample configuration:
667-
```
668-
return {
669-
"DB": "mysql",
670-
"DATABASE": "nettacker", # This is the name of your db
671-
"USERNAME": "username",
672-
"PASSWORD": "password",
673-
"HOST": "localhost or some other host",
674-
"PORT": "3306 or some other custom port"
675-
}
676-
```
677-
After this configuration:
678-
1. Open the configuration file of mysql(`/etc/mysql/my.cnf` in case of linux) as a sudo user
679-
2. Add this to the end of the file :
680-
```
681-
[mysqld]
682-
sql_mode = "STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"
679+
680+
The MySQL database can be configured in `nettacker/config.py` file under the `DBConfig` class. Here is a sample configuration:
681+
682+
```python
683+
engine = "mysql"
684+
name = "nettacker"
685+
host = "localhost"
686+
port = 3306
687+
username = "root"
688+
password = "some-password"
689+
ssl_mode = "disable"
690+
journal_mode = "WAL"
691+
synchronous_mode = "NORMAL"
683692
```
684-
3. Restart MySQL
693+
694+
Only the relevant fields will be considered and you don't need to update/change/remove the irrelevant ones (`ssl_mode`, `journal_mode` and `synchronous_mode` aren't relevant in this case).
685695

686696
## Postgres Configuration
687697

688-
The Postgres database can be configured in core/config.py file under the _database_config() function. Here is a sample configuration:
689-
`
690-
return {
691-
"DB": "postgreas",
692-
"DATABASE": "nettacker" # Name of db
693-
"USERNAME": "username",
694-
"PASSWORD": "password",
695-
"HOST": "localhost or some other host",
696-
"PORT": "5432 or some other custom port"
697-
}
698-
`
699-
After this configuration please comment out the following line in database/db.py `connect_args={'check_same_thread': False}`
698+
The Postgres database can be configured in `nettacker/config.py` file under the `DBConfig` class. Here is a sample configuration:
699+
700+
```python
701+
engine = "postgres"
702+
name = "nettacker"
703+
host = "localhost"
704+
port = 5432
705+
username = "root"
706+
password = "some-password"
707+
ssl_mode = "disable"
708+
journal_mode = "WAL"
709+
synchronous_mode = "NORMAL"
710+
```
700711

712+
In this case the irrelevant fields are `journal_mode` and `synchronous_mode`. You don't have to update/change/remove them.
701713

714+
**Note**: If you want encryption, then set `ssl_mode` to `require`.
702715

703716
Let me know if you have any more questions.

nettacker/api/core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
from nettacker.config import Config
66
from nettacker.core.app import Nettacker
7-
from nettacker.core.messages import messages as _
87
from nettacker.core.messages import get_languages
8+
from nettacker.core.messages import messages as _
99

1010

1111
def get_value(flask_request, key):

nettacker/api/engine.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,39 +8,38 @@
88
from threading import Thread
99
from types import SimpleNamespace
1010

11-
from flask import Flask, jsonify
11+
from flask import Flask, Response, abort, jsonify, make_response, render_template
1212
from flask import request as flask_request
13-
from flask import render_template, abort, Response, make_response
1413
from werkzeug.serving import WSGIRequestHandler
1514
from werkzeug.utils import secure_filename
1615

1716
from nettacker import logger
1817
from nettacker.api.core import (
19-
get_value,
18+
api_key_is_valid,
2019
get_file,
21-
mime_types,
22-
scan_methods,
23-
profiles,
20+
get_value,
2421
graphs,
2522
languages_to_country,
26-
api_key_is_valid,
23+
mime_types,
24+
profiles,
25+
scan_methods,
2726
)
2827
from nettacker.api.helpers import structure
2928
from nettacker.config import Config
3029
from nettacker.core.app import Nettacker
3130
from nettacker.core.die import die_failure
3231
from nettacker.core.graph import create_compare_report
3332
from nettacker.core.messages import messages as _
34-
from nettacker.core.utils.common import now, generate_compare_filepath
33+
from nettacker.core.utils.common import generate_compare_filepath, now
3534
from nettacker.database.db import (
3635
create_connection,
3736
get_logs_by_scan_id,
38-
select_reports,
3937
get_scan_result,
4038
last_host_logs,
39+
logs_to_report_html,
4140
logs_to_report_json,
4241
search_logs,
43-
logs_to_report_html,
42+
select_reports,
4443
)
4544
from nettacker.database.models import Report
4645

nettacker/config.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from pathlib import Path
44

55
from nettacker import version
6-
from nettacker.core.utils.common import now, generate_random_token
6+
from nettacker.core.utils.common import generate_random_token, now
77

88
CWD = Path.cwd()
99
PACKAGE_PATH = Path(__file__).parent
@@ -82,7 +82,11 @@ class DbConfig(ConfigBase):
8282
For sqlite database:
8383
fill the name of the DB as sqlite,
8484
DATABASE as the name of the db user wants
85-
other details can be left empty
85+
Set the journal_mode (default="WAL") and
86+
synchronous_mode (default="NORMAL"). Rest
87+
of the fields can be left empty
88+
This is the default database:
89+
str(CWD / ".nettacker/data/nettacker.db")
8690
For mysql users:
8791
fill the ENGINE name of the DB as mysql
8892
NAME as the name of the database you want to create
@@ -104,6 +108,8 @@ class DbConfig(ConfigBase):
104108
username = ""
105109
password = ""
106110
ssl_mode = "disable"
111+
journal_mode = "WAL"
112+
synchronous_mode = "NORMAL"
107113

108114

109115
class PathConfig:
@@ -142,6 +148,9 @@ class DefaultSettings(ConfigBase):
142148
parallel_module_scan = 1
143149
passwords = None
144150
passwords_list = None
151+
# Setting to toggle between APSW and SQLAlchemy for sqlite databases.
152+
use_apsw_for_sqlite = False
153+
145154
ping_before_scan = False
146155
ports = None
147156
profiles = None
@@ -151,6 +160,9 @@ class DefaultSettings(ConfigBase):
151160
random_chars=generate_random_token(10),
152161
)
153162
retries = 1
163+
max_retries = 3
164+
max_submit_query_retry = 100
165+
retry_delay = 0.1
154166
scan_ip_range = False
155167
scan_subdomains = False
156168
selected_modules = None

nettacker/core/app.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@
1212
from nettacker.config import Config, version_info
1313
from nettacker.core.arg_parser import ArgParser
1414
from nettacker.core.die import die_failure
15-
from nettacker.core.graph import create_report, create_compare_report
15+
from nettacker.core.graph import create_compare_report, create_report
1616
from nettacker.core.ip import (
17-
get_ip_range,
1817
generate_ip_range,
19-
is_single_ipv4,
20-
is_ipv4_range,
18+
get_ip_range,
2119
is_ipv4_cidr,
22-
is_single_ipv6,
23-
is_ipv6_range,
20+
is_ipv4_range,
2421
is_ipv6_cidr,
22+
is_ipv6_range,
23+
is_single_ipv4,
24+
is_single_ipv6,
2525
)
2626
from nettacker.core.messages import messages as _
2727
from nettacker.core.module import Module
@@ -158,9 +158,7 @@ def expand_targets(self, scan_id):
158158

159159
for target in copy.deepcopy(self.arguments.targets):
160160
for row in find_events(target, "subdomain_scan", scan_id):
161-
for sub_domain in json.loads(row.json_event)["response"]["conditions_results"][
162-
"content"
163-
]:
161+
for sub_domain in json.loads(row)["response"]["conditions_results"]["content"]:
164162
if sub_domain not in self.arguments.targets:
165163
self.arguments.targets.append(sub_domain)
166164
# icmp_scan

nettacker/core/arg_parser.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@
55
import yaml
66

77
from nettacker import all_module_severity_and_desc
8-
from nettacker.config import version_info, Config
8+
from nettacker.config import Config, version_info
99
from nettacker.core.die import die_failure, die_success
1010
from nettacker.core.ip import (
11-
is_single_ipv4,
12-
is_single_ipv6,
11+
generate_ip_range,
1312
is_ipv4_cidr,
14-
is_ipv6_range,
15-
is_ipv6_cidr,
1613
is_ipv4_range,
17-
generate_ip_range,
14+
is_ipv6_cidr,
15+
is_ipv6_range,
16+
is_single_ipv4,
17+
is_single_ipv6,
1818
)
1919
from nettacker.core.messages import messages as _
2020
from nettacker.core.template import TemplateLoader

nettacker/core/graph.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@
99

1010
import texttable
1111

12-
from nettacker import logger, all_module_severity_and_desc
12+
from nettacker import all_module_severity_and_desc, logger
1313
from nettacker.config import Config, version_info
1414
from nettacker.core.die import die_failure
1515
from nettacker.core.messages import messages as _
1616
from nettacker.core.utils.common import (
17+
generate_compare_filepath,
1718
merge_logs_to_list,
1819
now,
1920
sanitize_path,
20-
generate_compare_filepath,
2121
)
22-
from nettacker.database.db import get_logs_by_scan_id, submit_report_to_db, get_options_by_scan_id
22+
from nettacker.database.db import get_logs_by_scan_id, get_options_by_scan_id, submit_report_to_db
2323

2424
log = logger.get_logger()
2525
nettacker_path_config = Config.path
@@ -86,7 +86,7 @@ def build_text_table(events):
8686
table_headers = ["date", "target", "module_name", "port", "logs"]
8787
_table.add_rows([table_headers])
8888
for event in events:
89-
log = merge_logs_to_list(json.loads(event["json_event"]), [])
89+
log = merge_logs_to_list(event, [])
9090
_table.add_rows(
9191
[
9292
table_headers,
@@ -252,15 +252,15 @@ def create_report(options, scan_id):
252252
)
253253
index = 1
254254
for event in all_scan_logs:
255-
log_list = merge_logs_to_list(json.loads(event["json_event"]), [])
255+
log_list = merge_logs_to_list(event, [])
256256
html_table_content += log_data.table_items.format(
257257
event["date"],
258258
event["target"],
259259
event["module_name"],
260260
event["port"],
261261
"<br>".join(log_list) if log_list else "Detected", # event["event"], #log
262262
index,
263-
html.escape(event["json_event"]),
263+
html.escape(json.dumps(event)),
264264
)
265265
index += 1
266266
html_table_content += (

nettacker/core/lib/base.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
from nettacker.config import Config
1111
from nettacker.core.messages import messages as _
1212
from nettacker.core.utils.common import merge_logs_to_list, remove_sensitive_header_keys
13-
from nettacker.database.db import find_temp_events, submit_temp_logs_to_db, submit_logs_to_db
14-
from nettacker.logger import get_logger, TerminalCodes
13+
from nettacker.database.db import find_temp_events, submit_logs_to_db, submit_temp_logs_to_db
14+
from nettacker.logger import TerminalCodes, get_logger
1515

1616
log = get_logger()
1717

@@ -52,7 +52,7 @@ def get_dependent_results_from_database(self, target, module_name, scan_id, even
5252
while True:
5353
event = find_temp_events(target, module_name, scan_id, event_name)
5454
if event:
55-
events.append(json.loads(event.event)["response"]["conditions_results"])
55+
events.append(json.loads(event)["response"]["conditions_results"])
5656
break
5757
time.sleep(0.1)
5858
return events

nettacker/core/lib/http.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111

1212
from nettacker.core.lib.base import BaseEngine
1313
from nettacker.core.utils.common import (
14-
replace_dependent_response,
15-
reverse_and_regex_condition,
1614
get_http_header_key,
1715
get_http_header_value,
16+
replace_dependent_response,
17+
reverse_and_regex_condition,
1818
)
1919

2020
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

nettacker/core/lib/socket.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import time
1212

1313
from nettacker.core.lib.base import BaseEngine, BaseLibrary
14-
from nettacker.core.utils.common import reverse_and_regex_condition, replace_dependent_response
14+
from nettacker.core.utils.common import replace_dependent_response, reverse_and_regex_condition
1515

1616
log = logging.getLogger(__name__)
1717

0 commit comments

Comments
 (0)