Skip to content

Commit

Permalink
Merge pull request #329 from NethServer/threatshield-apis
Browse files Browse the repository at this point in the history
ThreatShield APIs
  • Loading branch information
gsanchietti authored Feb 15, 2024
2 parents ed0e27a + 2f2c058 commit 66262c7
Show file tree
Hide file tree
Showing 6 changed files with 343 additions and 3 deletions.
4 changes: 3 additions & 1 deletion packages/ns-api/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
include $(TOPDIR)/rules.mk

PKG_NAME:=ns-api
PKG_VERSION:=0.0.35
PKG_VERSION:=0.0.37
PKG_RELEASE:=1

PKG_BUILD_DIR:=$(BUILD_DIR)/ns-api-$(PKG_VERSION)
Expand Down Expand Up @@ -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
Expand Down
139 changes: 139 additions & 0 deletions packages/ns-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
186 changes: 186 additions & 0 deletions packages/ns-api/files/ns.threatshield
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
#!/usr/bin/python3

#
# Copyright (C) 2024 Nethesis S.r.l.
# SPDX-License-Identifier: GPL-2.0-only
#

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)))
13 changes: 13 additions & 0 deletions packages/ns-api/files/ns.threatshield.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"threat-shield": {
"description": "Manage adblock and banip to mitigate threats",
"write": {},
"read": {
"ubus": {
"ns.threatshield": [
"*"
]
}
}
}
}
2 changes: 1 addition & 1 deletion packages/ns-threat_shield/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion packages/ns-threat_shield/files/banip.nethesis.feeds
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@
"nethesislvl3": {
"url_4": "https://__USER__:[email protected]/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"
}
}

0 comments on commit 66262c7

Please sign in to comment.