From c1aaed52df7b220db5a08149d7f22eca8e18a174 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Mon, 12 Feb 2024 12:28:04 +0100 Subject: [PATCH 1/4] ns-api: added threatshield scaffolding Signed-off-by: Tommaso Bailetti --- packages/ns-api/Makefile | 4 +++- packages/ns-api/files/ns.threatshield | 8 ++++++++ packages/ns-api/files/ns.threatshield.json | 13 +++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 packages/ns-api/files/ns.threatshield create mode 100644 packages/ns-api/files/ns.threatshield.json diff --git a/packages/ns-api/Makefile b/packages/ns-api/Makefile index 1fec4d480..eb4ca4fa9 100644 --- a/packages/ns-api/Makefile +++ b/packages/ns-api/Makefile @@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-api -PKG_VERSION:=0.0.35 +PKG_VERSION:=0.0.36 PKG_RELEASE:=1 PKG_BUILD_DIR:=$(BUILD_DIR)/ns-api-$(PKG_VERSION) @@ -132,6 +132,8 @@ define Package/ns-api/install $(INSTALL_DATA) ./files/ns.users.json $(1)/usr/share/rpcd/acl.d/ $(INSTALL_BIN) ./files/ns.commit $(1)/usr/libexec/rpcd/ $(INSTALL_DATA) ./files/ns.commit.json $(1)/usr/share/rpcd/acl.d/ + $(INSTALL_BIN) ./files/ns.threatshield $(1)/usr/libexec/rpcd/ + $(INSTALL_DATA) ./files/ns.threatshield.json $(1)/usr/share/rpcd/acl.d/ $(INSTALL_DIR) $(1)/lib/upgrade/keep.d $(INSTALL_CONF) files/msmtp.keep $(1)/lib/upgrade/keep.d/msmtp $(LN) /usr/bin/msmtp $(1)/usr/sbin/sendmail diff --git a/packages/ns-api/files/ns.threatshield b/packages/ns-api/files/ns.threatshield new file mode 100644 index 000000000..d61766972 --- /dev/null +++ b/packages/ns-api/files/ns.threatshield @@ -0,0 +1,8 @@ +#!/usr/bin/python3 + +# +# Copyright (C) 2024 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-2.0-only +# + +raise NotImplemented diff --git a/packages/ns-api/files/ns.threatshield.json b/packages/ns-api/files/ns.threatshield.json new file mode 100644 index 000000000..ebefe4cdd --- /dev/null +++ b/packages/ns-api/files/ns.threatshield.json @@ -0,0 +1,13 @@ +{ + "threat-shield": { + "description": "Manage adblock and banip to mitigate threats", + "write": {}, + "read": { + "ubus": { + "ns.threatshield": [ + "*" + ] + } + } + } +} From a43f0967529f0c64cafb9228411e7e016b72bbe0 Mon Sep 17 00:00:00 2001 From: Giacomo Sanchietti Date: Wed, 14 Feb 2024 08:54:00 +0100 Subject: [PATCH 2/4] ns-api: threatshield, implement basic APIs --- packages/ns-api/README.md | 139 ++++++++++++++++++++ packages/ns-api/files/ns.threatshield | 180 +++++++++++++++++++++++++- 2 files changed, 318 insertions(+), 1 deletion(-) diff --git a/packages/ns-api/README.md b/packages/ns-api/README.md index f29dd15d1..3df9606da 100644 --- a/packages/ns-api/README.md +++ b/packages/ns-api/README.md @@ -5343,6 +5343,145 @@ Response example: {"result": "success"} ``` +## ns.threatshield + +Manage banip configuration. + +### list-blocklist + +List current blocklist: +``` +api-cli ns.threatshield list-blocklist +``` + +Response example: +```json +{ + "data": [ + { + "name": "yoroimallvl1", + "type": "enterprise", + "enabled": false, + "confidence": 10, + "description": "Yoroi malware - Level 1" + }, + { + "name": "yoroimallvl2", + "type": "enterprise", + "enabled": false, + "confidence": 8, + "description": "Yoroi malware - Level 2" + } + ] +} +``` + +Fields: +- type can be `enterprise`, `community` or `unknown`, the warning type is when the list is present in UCI configuration but it's not a supported feed +- confidence can be `-1` if the value is not available + + +### list-settings + +Show current banip settings: +``` +api-cli ns.threatshield list-settings +``` + +Response example: +```json +{"data": {"enabled": true}} +``` + +### edit-settings + +Configure banip settings: +``` +api-cli ns.threatshield edit-settings --data '{"enabled": true}' +``` + +Response example: +```json +{"message": "success"} +``` + +### edit-blocklist + +Enable or disable a blocklist: +``` +api-cli ns.threatshield edit-blocklist --data '{ "blocklist": "blocklist_name", "enabled": True }' +``` + +### list-allowed + +List addresses always allowed: +``` +api-cli ns.threatshield list-allowed +``` + +Response example: +```json +{ + "data": [ + { + "address": "10.10.0.221/24", + "description": "WAN" + }, + { + "address": "52:54:00:6A:50:BF", + "description": "my MAC address" + } + ] +} +``` + +### add-allowed + +Add an address which is always allowed: +``` +api-cli ns.threatshield add-allowed --data '{"address": "1.2.3.4", "description": "my allow1"}' +``` + +The `address` field can be an IPv4/IPv6, a CIDR, a MAC or host name + +Response example: +```json +{"message": "success"} +``` + +It can raise the following validation errors: +- `address_already_present` if the address is already inside the allow list + +### edit-allowed + +Change the description of an address already insie the allow list: +``` +api-cli ns.threatshield edit-allowed --data '{"address": "1.2.3.4", "description": "my new desc"}' +``` + +Response example: +```json +{"message": "success"} +``` + +It can raise the following validation errors: +- `address_not_found` if the address is not inside the allow list + +### delete-allowed + +Delete an address from the allow list: +``` +api-cli ns.threatshield delete-allowed --data '{"address": "1.2.3.4"}' +``` + +Response example: +```json +{"message": "success"} +``` + +It can raise the following validation errors: +- `address_not_found` if the address is not inside the allow list + ## ns.qos Allows to configure QoS for each network interface available. diff --git a/packages/ns-api/files/ns.threatshield b/packages/ns-api/files/ns.threatshield index d61766972..bff9a8e06 100644 --- a/packages/ns-api/files/ns.threatshield +++ b/packages/ns-api/files/ns.threatshield @@ -5,4 +5,182 @@ # SPDX-License-Identifier: GPL-2.0-only # -raise NotImplemented +import os +import sys +import json +from euci import EUci +from nethsec.utils import ValidationError +from nethsec import utils + +## Utilities + +def list_feeds(): + if os.path.exists('/etc/banip/banip.custom.feeds') and os.path.getsize('/etc/banip/banip.custom.feeds') > 0: + with open('/etc/banip/banip.custom.feeds') as f: + return json.loads(f.read()) + else: + with open('/etc/banip/banip.feeds') as f: + return json.loads(f.read()) + +def get_allow_list(): + ret = [] + try: + with open('/etc/banip/banip.allowlist') as f: + for line in f: + line = line.strip() + if not line: + continue + parts = line.split('#') + if len(parts) > 1: + ret.append({ "address": parts[0].strip(), "description": parts[1].strip() }) + else: + ret.append({ "address": parts[0].strip(), "description": '' }) + except: + return [] + return ret + +def write_allow_list(allow_list): + with open('/etc/banip/banip.allowlist', 'w') as f: + for x in allow_list: + f.write(x['address']) + if x['description']: + f.write(' #' + x['description']) + f.write('\n') + +## APIs + +def list_blocklist(e_uci): + ret = [] + feeds = list_feeds() + try: + enabled_feeds = list(e_uci.get_all('banip', 'global', 'ban_feed')) + except: + enabled_feeds = [] + enterprise_subscription = e_uci.get('ns-plug', 'config', 'system_id', default='') != '' and 'my.nethesis.it' in e_uci.get('ns-plug', 'config', 'inventory_url', default='') + for f in feeds: + feed = feeds[f] + if f.endswith('lvl1'): + confidence = 10 + elif f.endswith('lvl2'): + confidence = 8 + elif f.endswith('lvl3'): + confidence = 6 + else: + confidence = -1 + + if 'bl.nethesis.it' in feed['url_4']: + type = 'enterprise' + else: + type = 'community' + enabled = f in enabled_feeds + # show only enterprise lists if the subscription is available + if enterprise_subscription and type == 'community': + if not enabled: + continue + else: + type = 'unknown' + ret.append({ 'name': f, 'type': type, 'enabled': enabled, 'confidence': confidence, 'description': feed.get('descr')}) + return { "data": ret } + +def list_settings(e_uci): + return { 'data': {'enabled': e_uci.get('banip', 'global', 'ban_enabled') == '1' } } + +def edit_blocklist(e_uci, payload): + feeds = list_feeds() + try: + enabled = list(e_uci.get_all('banip', 'global', 'ban_feed')) + except: + enabled = [] + if payload['enabled'] and payload['blocklist'] not in enabled: + enabled.append(payload['blocklist']) + if not payload['enabled'] and payload['blocklist'] in enabled: + enabled.remove(payload['blocklist']) + e_uci.set('banip', 'global', 'ban_feed', enabled) + e_uci.save('banip') + return {'message': 'success'} + +def edit_settings(e_uci, payload): + if payload['enabled']: + e_uci.set('banip', 'global', 'ban_enabled', '1') + else: + e_uci.set('banip', 'global', 'ban_enabled', '0') + e_uci.save('banip') + return {'message': 'success'} + +def list_allowed(): + return { "data": get_allow_list() } + +def add_allowed(payload): + cur = get_allow_list() + # extract address from cur list + if payload['address'] in [x['address'] for x in cur]: + raise ValidationError('address', 'address_already_present', payload['address']) + cur.append({ "address": payload['address'], "description": payload['description'] }) + write_allow_list(cur) + return {'message': 'success'} + +def delete_allowed(payload): + cur = get_allow_list() + if payload['address'] not in [x['address'] for x in cur]: + raise ValidationError('address', 'address_not_found', payload['address']) + # remove address from cur list + for i in range(len(cur)): + if cur[i]['address'] == payload['address']: + del cur[i] + break + write_allow_list(cur) + return {'message': 'success'} + +def edit_allowed(payload): + cur = get_allow_list() + if payload['address'] not in [x['address'] for x in cur]: + raise ValidationError('address', 'address_not_found', payload['address']) + for i in range(len(cur)): + if cur[i]['address'] == payload['address']: + cur[i]['description'] = payload['description'] + break + write_allow_list(cur) + return {'message': 'success'} + +cmd = sys.argv[1] + +if cmd == 'list': + print(json.dumps({ + 'list-blocklist': {}, + 'edit-blocklist': { "blocklist": "blocklist_name", "enabled": True }, + 'list-settings': { 'data': { 'enabled': True } }, + 'edit-settings': { 'enabled': True }, + 'list-allowed': {}, + 'add-allowed': { 'address': '1.2.3.4', 'description': 'optional' }, + 'edit-allowed': { 'address': '1.2.3.4', 'description': 'optional' }, + 'delete-allowed': { 'address': '1.2.3.4' } + })) +elif cmd == 'call': + action = sys.argv[2] + e_uci = EUci() + try: + if action == 'list-blocklist': + ret = list_blocklist(e_uci) + elif action == 'edit-blocklist': + payload = json.loads(sys.stdin.read()) + ret = edit_blocklist(e_uci, payload) + elif action == 'list-settings': + ret = list_settings(e_uci) + elif action == 'edit-settings': + payload = json.loads(sys.stdin.read()) + ret = edit_settings(e_uci, payload) + elif action == 'list-allowed': + ret = list_allowed() + elif action == 'add-allowed': + payload = json.loads(sys.stdin.read()) + ret = add_allowed(payload) + elif action == 'edit-allowed': + payload = json.loads(sys.stdin.read()) + ret = edit_allowed(payload) + elif action == 'delete-allowed': + payload = json.loads(sys.stdin.read()) + ret = delete_allowed(payload) + + print(json.dumps(ret)) + except ValidationError as ex: + print(json.dumps(utils.validation_error(ex.parameter, ex.message, ex.value))) From 24215bec4772634354aa130b37b08d2ab6646443 Mon Sep 17 00:00:00 2001 From: Giacomo Sanchietti Date: Wed, 14 Feb 2024 15:39:34 +0100 Subject: [PATCH 3/4] ns-api: bump release to 0.0.37 --- packages/ns-api/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ns-api/Makefile b/packages/ns-api/Makefile index eb4ca4fa9..a7ea45807 100644 --- a/packages/ns-api/Makefile +++ b/packages/ns-api/Makefile @@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-api -PKG_VERSION:=0.0.36 +PKG_VERSION:=0.0.37 PKG_RELEASE:=1 PKG_BUILD_DIR:=$(BUILD_DIR)/ns-api-$(PKG_VERSION) From 2f2c058001d9db4caf0eafb62970361875c81350 Mon Sep 17 00:00:00 2001 From: Giacomo Sanchietti Date: Wed, 14 Feb 2024 15:40:48 +0100 Subject: [PATCH 4/4] ns-threat_shield: fix Nethesis list name --- packages/ns-threat_shield/Makefile | 2 +- packages/ns-threat_shield/files/banip.nethesis.feeds | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ns-threat_shield/Makefile b/packages/ns-threat_shield/Makefile index 322cb7386..11e97185c 100644 --- a/packages/ns-threat_shield/Makefile +++ b/packages/ns-threat_shield/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-threat_shield -PKG_VERSION:=0.0.1 +PKG_VERSION:=0.0.2 PKG_RELEASE:=1 PKG_BUILD_DIR:=$(BUILD_DIR)/ns-threat_shield-$(PKG_VERSION) diff --git a/packages/ns-threat_shield/files/banip.nethesis.feeds b/packages/ns-threat_shield/files/banip.nethesis.feeds index 33cee3812..471e7b9bf 100644 --- a/packages/ns-threat_shield/files/banip.nethesis.feeds +++ b/packages/ns-threat_shield/files/banip.nethesis.feeds @@ -22,6 +22,6 @@ "nethesislvl3": { "url_4": "https://__USER__:__PASSWORD__@bl.nethesis.it/plain/nethesis-blacklists/nethesis_level3.netset", "rule_4": "/^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}/{printf \"%s,\\n\",$1}", - "descr": "Yoroi suspicious - Level 3" + "descr": "Nethesis suspicious - Level 3" } }