diff --git a/README.md b/README.md index 418c3fc..2eefd15 100644 --- a/README.md +++ b/README.md @@ -53,10 +53,12 @@ DB_NAME=study-project STORE_DB_NAME=store-db STORE_DB_PSWD=store-pswd STORE_DB_USER=store-user +STORE_SEC_KEY=store-secret-key BANK_DB_NAME=bank-db BANK_DB_PSWD=bank-store BANK_DB_USER=bank-user +BANK_SEC_KEY=bank-secret-key DB_PORT=51488 ``` @@ -66,25 +68,25 @@ DB_PORT=51488 ### How to get user balance ( example ) ``` -curl --insecure -X GET -H 'Content-Type: application/json' -u "seregga:seregga" "https://127.0.0.1:5001/api/balance" +curl --insecure -X GET -H 'Content-Type: application/json' -u "seregga:seregga" "https://127.0.0.1/api/bank/api/balance" ``` ### How to add account in our great bank ( example ) ``` -curl --insecure -X POST -H 'Content-Type: application/json' -d '{"uuid" : "test", "password" : "test"}' "https://127.0.0.1:5001/api/add-account" +curl --insecure -X POST -H 'Content-Type: application/json' -d '{"uuid" : "test", "password" : "test"}' "https://127.0.0.1/api/bank/api/add-account" ``` ### How to delete bank account ( example ) ``` -curl --insecure -X POST -H 'Content-Type: application/json' -u "test:test" "https://127.0.0.1:5001/api/delete-account" +curl --insecure -X POST -H 'Content-Type: application/json' -u "test:test" "https://127.0.0.1/api/bank/api/delete-account" ``` ### How to transfer money from one account to another ( example ) ``` -curl --insecure -X POST -H 'Content-Type: application/json' -u "test:test" -d '{"uuid_to" : "seregga", "amount" : 500 }' "https://127.0.0.1:5001/api/transfer" +curl --insecure -X POST -H 'Content-Type: application/json' -u "test:test" -d '{"uuid_to" : "seregga", "amount" : 500 }' "https://127.0.0.1/api/bank/api/transfer" ``` ### Return codes for bank api service @@ -112,15 +114,11 @@ curl --insecure -X POST -H 'Content-Type: application/json' -u "test:test" -d '{ # create test data (optional) python tools/deGenerator.py -# store.crt and store.key files for store service -cd store-service/ -openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout store.key -out store.crt -cd .. - -# bank.crt and bank.key files for bank service -cd bank-service/ -openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout bank.key -out bank.crt -cd .. +# create cerificates for reverse-proxy +cd nginx/certs/ +openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout gamehub.local.key -out gamehub.local.crt +openssl dhparam -out dhparam.pem 2048 +cd ../.. # run containers in daemon mode docker compose up --build -d diff --git a/bank-service/bank/__init__.py b/bank-service/bank/__init__.py index b5eb831..9f14287 100644 --- a/bank-service/bank/__init__.py +++ b/bank-service/bank/__init__.py @@ -11,5 +11,4 @@ # run flask application if __name__ == '__main__': - ssl_context = ('bank.crt', 'bank.key') - app.run('0.0.0.0', port=5001, debug=True, ssl_context=ssl_context) + app.run('0.0.0.0', port=5001, debug=True) diff --git a/bank-service/bank/database/db.py b/bank-service/bank/database/db.py index 9933d23..1f3b486 100644 --- a/bank-service/bank/database/db.py +++ b/bank-service/bank/database/db.py @@ -54,7 +54,7 @@ def add_account(uuid:str, password:str): phash = bcrypt.hashpw( password.encode('utf-8'), bcrypt.gensalt(rounds = 12) - ).hexdigest() + ).decode('utf-8') conn = get_db() try: cursor = conn.cursor() @@ -99,28 +99,32 @@ def transfer(id_from:int, uuid_to:str, amount:int): id_to = cursor.fetchone() if not id_to: raise ValueError(f'No recver uuid={uuid_to} in database') - id_to = id_to['id'] + id_to = id_to['id'] cursor.execute( - 'SELECT balance FROM bank.accounts WHERE id = %s;', + 'SELECT id, uuid, balance FROM bank.accounts WHERE id = %s;', (id_from,) ) - sender_balance = cursor.fetchone() - if not sender_balance or sender_balance['balance'] < amount: + + from_data = cursor.fetchone() + if not from_data: + raise ValueError(f'No sender with id={id_from} in database') + uuid_from = from_data['uuid'] + + if id_from == id_to: + raise ValueError('Why you need to send money for yourself???') + + if from_data['balance'] < amount: raise ValueError('Not enough money :(') cursor.execute( - 'UPDATE bank.accounts SET balance = balance - %s WHERE id = %s;', - (amount, id_from,) - ) - cursor.execute( - 'UPDATE bank.accounts SET balance = balance + %s WHERE id = %s;', - (amount, id_to,) + 'INSERT INTO bank.transactions (user_id, amount, account_uuid_snapshot) VALUES (%s, %s, %s);', + (id_from, -amount, uuid_from) ) cursor.execute( - 'INSERT INTO bank.transactions (user_id, amount) VALUES (%s, %s), (%s, %s);', - (id_from, -amount, id_to, amount) + 'INSERT INTO bank.transactions (user_id, amount, account_uuid_snapshot) VALUES (%s, %s, %s);', + (id_to, amount, uuid_to) ) conn.commit() diff --git a/bank-service/uwsgi.ini b/bank-service/uwsgi.ini index 9d31f03..ff2e7ac 100644 --- a/bank-service/uwsgi.ini +++ b/bank-service/uwsgi.ini @@ -7,7 +7,7 @@ processes = 4 threads = 2 enable-threads = true -http = 0.0.0.0:5000 +http = 0.0.0.0:5001 vacuum = true buffer-size = 32768 diff --git a/database/init-scripts/10-bank-schema.sql b/database/init-scripts/10-bank-schema.sql index 61a883f..1b29a4f 100644 --- a/database/init-scripts/10-bank-schema.sql +++ b/database/init-scripts/10-bank-schema.sql @@ -11,7 +11,28 @@ CREATE TABLE IF NOT EXISTS bank.transactions ( id SERIAL PRIMARY KEY, ts TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, amount INTEGER NOT NULL, - user_id INTEGER NOT NULL REFERENCES bank.accounts (id), + user_id INTEGER REFERENCES bank.accounts (id) ON DELETE SET NULL, + account_uuid_snapshot TEXT NOT NULL, FOREIGN KEY (user_id) REFERENCES bank.accounts ); + +CREATE OR REPLACE VIEW bank.current_balance AS +SELECT + a.id, + a.uuid, + COALESCE(SUM(t.amount), 0) AS balance +FROM bank.accounts AS a +LEFT JOIN bank.transactions AS t ON a.id = t.user_id +GROUP BY a.id, a.uuid; + +CREATE OR REPLACE VIEW bank.all_transactions_view AS +SELECT + t.id, + t.ts, + t.amount, + t.user_id, + COALESCE(a.uuid, t.account_uuid_snapshot) AS related_account_uuid, + (a.id IS NULL) AS is_account_deleted +FROM bank.transactions AS t +LEFT JOIN bank.accounts AS a ON t.user_id = a.id; diff --git a/docker-compose.yaml b/docker-compose.yaml index 1ef1280..7df5f40 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -38,6 +38,7 @@ services: DB_NAME: ${DB_NAME} DB_USER: ${STORE_DB_USER} DB_PSWD: ${STORE_DB_PSWD} + SECRET_KEY: ${STORE_SEC_KEY} volumes: - ./database/store-init-csvs:/tmp/init-csvs/ networks: @@ -47,28 +48,36 @@ services: build: bank-service depends_on: - db + ports: + - "5001:5001" environment: DB_HOST: db DB_PORT: ${DB_PORT} DB_NAME: ${DB_NAME} DB_USER: ${BANK_DB_USER} DB_PSWD: ${BANK_DB_PSWD} - ports: - - "5001:5001" + SECRET_KEY: ${BANK_SEC_KEY} networks: - gamehub-net reverse-proxy: - image: nginx:1.25-alpine + image: owasp/modsecurity-crs:nginx-alpine container_name: gamehub-rp ports: - "80:80" - "443:443" volumes: - - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro + - ./nginx/conf.d/app.conf:/etc/nginx/conf.d/app.conf:ro - ./nginx/certs:/etc/nginx/certs:ro - - ./nginx/conf.d:/etc/nginx/conf.d:ro - ./nginx/snippets:/etc/nginx/snippets:ro + - nginx_logs:/var/log/nginx + environment: + # On - блокировать, DetectionOnly - только обнаруживать, Off - выключить. + MODSEC_RULE_ENGINE: "Off" + # Уровень паранойи от 1 до 4. + PARANOIA_LEVEL: "1" + # Уровень логирования (error, warn, notice, info, debug) + LOG_LEVEL: "warn" depends_on: - store-service - bank-service @@ -76,8 +85,27 @@ services: - gamehub-net restart: always + ips: + image: jasonish/suricata:latest + container_name: gamehub-suricata + cap_add: + - NET_ADMIN + - NET_RAW + - SYS_NICE + command: -i eth0 + volumes: + - ./suricata/conf/suricata.yaml:/etc/suricata/suricata.yaml + - ./suricata/suricata_rules:/var/lib/suricata/rules + - suricata_logs:/var/log/suricata + networks: + - gamehub-net + networks: gamehub-net: volumes: postgres_data: + suricata_rules: + suricata_logs: + nginx_logs: + diff --git a/nginx/conf.d/app.conf b/nginx/conf.d/app.conf index d3b0034..1e81133 100644 --- a/nginx/conf.d/app.conf +++ b/nginx/conf.d/app.conf @@ -20,6 +20,8 @@ server { server_name gamehub.local www.gamehub.local; + modsecurity on; + ssl_certificate /etc/nginx/certs/gamehub.local.crt; ssl_certificate_key /etc/nginx/certs/gamehub.local.key; @@ -44,6 +46,7 @@ server { location / { proxy_pass http://store_service_upstream/; + proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/suricata/conf/suricata.yaml b/suricata/conf/suricata.yaml new file mode 100644 index 0000000..0c65734 --- /dev/null +++ b/suricata/conf/suricata.yaml @@ -0,0 +1,41 @@ +%YAML 1.1 +--- + +# Include the default configuration file. +#include: /etc/suricata/suricata.yaml-default + +# Overrides for this Docker container. +outputs: + - eve-log: + enabled: yes + filetype: regular + filename: eve.json + types: + - alert: + payload: yes + packet: yes + http: yes + - http: + extended: yes + - dns + - tls: + extended: yes + - files: + force-magic: yes + force-md5: yes + - ssh + - flow + - netflow + - stats: + enabled: yes + filename: stats.log + interval: 8 + +af-packet: + # Just define the default as we don't know what interface we will be + # run on. + - interface: default + threads: auto + use-mmap: yes + cluster-id: 99 + cluster-type: cluster_flow diff --git a/suricata/rules/another.rules b/suricata/rules/another.rules new file mode 100644 index 0000000..957c9a0 --- /dev/null +++ b/suricata/rules/another.rules @@ -0,0 +1,37 @@ +# Custom Suricata rules for GameHub + +# Web application security rules +alert http $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (msg:"GameHub: SQL Injection attempt detected"; flow:established,to_server; content:"SELECT"; nocase; content:"FROM"; nocase; distance:0; within:100; classtype:web-application-attack; sid:1000001; rev:1;) + +alert http $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (msg:"GameHub: XSS attempt detected"; flow:established,to_server; content:" $HTTP_SERVERS $HTTP_PORTS (msg:"GameHub: Path traversal attempt"; flow:established,to_server; content:"../"; classtype:web-application-attack; sid:1000003; rev:1;) + +alert http $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (msg:"GameHub: Command injection attempt"; flow:established,to_server; content:"|3B|"; content:"rm"; nocase; distance:0; within:50; classtype:web-application-attack; sid:1000004; rev:1;) + +# Banking service specific rules +alert http $EXTERNAL_NET any -> $HOME_NET 5001 (msg:"GameHub Bank: Unauthorized API access attempt"; flow:established,to_server; content:"POST"; http_method; content:"/api/transfer"; http_uri; content:!"Authorization:"; http_header; classtype:policy-violation; sid:1000010; rev:1;) + +alert http $EXTERNAL_NET any -> $HOME_NET 5001 (msg:"GameHub Bank: Large transaction attempt"; flow:established,to_server; content:"amount"; http_client_body; content:"|22|1000000"; http_client_body; distance:0; within:20; classtype:policy-violation; sid:1000011; rev:1;) + +# Store service specific rules +alert http $EXTERNAL_NET any -> $HOME_NET 5000 (msg:"GameHub Store: Price manipulation attempt"; flow:established,to_server; content:"price"; http_client_body; content:"|22|0"; http_client_body; distance:0; within:20; classtype:policy-violation; sid:1000020; rev:1;) + +alert http $EXTERNAL_NET any -> $HOME_NET 5000 (msg:"GameHub Store: Inventory manipulation"; flow:established,to_server; content:"quantity"; http_client_body; content:"|22|-"; http_client_body; distance:0; within:20; classtype:policy-violation; sid:1000021; rev:1;) + +# Database protection rules +alert tcp $EXTERNAL_NET any -> $HOME_NET $DB_PORT (msg:"GameHub DB: Direct database access attempt"; flow:established,to_server; classtype:policy-violation; sid:1000030; rev:1;) + +# Brute force protection +alert http $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (msg:"GameHub: Brute force login attempt"; flow:established,to_server; content:"POST"; http_method; content:"/login"; http_uri; detection_filter:track by_src, count 5, seconds 60; classtype:attempted-recon; sid:1000040; rev:1;) + +# DDoS protection +alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (msg:"GameHub: Potential DDoS attack"; flow:established,to_server; detection_filter:track by_src, count 100, seconds 10; classtype:attempted-dos; sid:1000050; rev:1;) + +# Suspicious user agents +alert http $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (msg:"GameHub: Suspicious bot activity"; flow:established,to_server; content:"User-Agent|3A 20|"; http_header; content:"bot"; nocase; http_header; classtype:policy-violation; sid:1000060; rev:1;) + +# File upload security +alert http $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (msg:"GameHub: Malicious file upload attempt"; flow:established,to_server; content:"Content-Type|3A 20|application/x-executable"; http_header; classtype:trojan-activity; sid:1000070; rev:1;) + +alert http $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (msg:"GameHub: PHP file upload attempt"; flow:established,to_server; content:"filename|3D 22|"; http_header; content:".php"; nocase; http_header; distance:0; within:100; classtype:web-application-attack; sid:1000071; rev:1;) diff --git a/suricata/rules/my.rules b/suricata/rules/my.rules new file mode 100644 index 0000000..b2d922a --- /dev/null +++ b/suricata/rules/my.rules @@ -0,0 +1 @@ +alert icmp $HOME_NET any -> 8.8.8.8 any (msg:"TEST ping google"; classtype:not-suspicious; sid:1; rev:1;)