Skip to content

Commit

Permalink
ns-api(threathshield): added additional options to api (#793)
Browse files Browse the repository at this point in the history
Signed-off-by: Tommaso Bailetti <[email protected]>
Signed-off-by: Andrea Leardini <[email protected]>
Co-authored-by: Andrea Leardini <[email protected]>
  • Loading branch information
Tbaile and andre8244 authored Oct 2, 2024
1 parent 0d17f3d commit d63f624
Show file tree
Hide file tree
Showing 4 changed files with 271 additions and 6 deletions.
84 changes: 82 additions & 2 deletions packages/ns-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5584,8 +5584,23 @@ Response example:
### edit-settings
Configure banip settings:
```
api-cli ns.threatshield edit-settings --data '{"enabled": true}'
- `enabled`: disable or enable banip (true or false).
- `ban_logprerouting`: Log suspicious packets in the prerouting chain (true or false).
- `ban_loginput`: Log suspicious packets in the WAN-input chain (true or false).
- `ban_logforwardwan`: Log suspicious packets in the WAN-forward chain (true or false).
- `ban_logforwardlan`: Log suspicious packets in the LAN-forward chain (true or false).
- `ban_loglimit`: Enable or disable scanning of logfiles (true or false).
- `ban_logcount`: Specify how many times an IP must appear in the log to be considered suspicious (integer).
- `ban_logterm`: List of regex entries for logfile parsing (list of strings).
- `ban_icmplimit`: Enable or disable icmp DoS detection (true or false).
- `ban_synlimit`: Enable or disable syn DoS detection (true or false).
- `ban_udplimit`: Enable or disable udp DoS detection (true or false).
- `ban_nftexpiry`: Set the ban expiry, format is `1d` for 1 day, `2h` for 2 hours, `1m` for 1 minute. (string)
```bash
api-cli ns.threatshield edit-settings --data '{"enabled": true, "ban_logprerouting": true, "ban_loginput": true, "ban_logforwardwan": true, "ban_logforwardlan": true, "ban_loglimit": false, "ban_logcount": 5, "ban_logterm": ["regex1", "regex2"], "ban_icmplimit": true, "ban_synlimit": true, "ban_udplimit": true, "ban_nftexpiry": "1d"}'
```
Response example:
Expand Down Expand Up @@ -5670,6 +5685,71 @@ Response example:
It can raise the following validation errors:
- `address_not_found` if the address is not inside the allow list
### list-blocked
List blocked addresses from the local blocklist:
```
api-cli ns.threatshield list-blocked
```
Response example:
```json
{
"data": [
{
"address": "10.10.0.221/24",
"description": "WAN"
},
{
"address": "52:54:00:6A:50:BF",
"description": "my MAC address"
}
]
}
```
### add-blocked
Add an address to the block list:
```
api-cli ns.threatshield add-blocked --data '{"address": "1.2.3.4", "description": "my block1"}'
```
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 block list
### edit-blocked
Change the description of an address already inside the block list:
```
api-cli ns.threatshield edit-blocked --data '{"address": "1.2.3.4", "description": "my new desc"}'
```
It can raise the following validation errors:
- `address_not_found` if the address is not inside the allow list
### delete-blocked
Delete an address from the block list:
```
api-cli ns.threatshield delete-blocked --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 block list
### dns-list-blocklist
List current dns blocklist:
Expand Down
169 changes: 165 additions & 4 deletions packages/ns-api/files/ns.threatshield
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ def list_feeds():
with open('/etc/banip/banip.feeds') as f:
return json.loads(f.read())


def get_block_list():
return get_allow_list('/etc/banip/banip.blocklist')


def get_allow_list(file='/etc/banip/banip.allowlist'):
ret = []
try:
Expand All @@ -88,6 +93,11 @@ def write_allow_list(allow_list, file='/etc/banip/banip.allowlist'):
f.write('\n')
subprocess.run(["/etc/init.d/banip", "reload"], capture_output=True)


def write_block_list(block_list):
write_allow_list(block_list, '/etc/banip/banip.blocklist')


def list_dns_feeds():
# Decompress and read the JSON file /etc/adblock/combined.sources.gz
sources = '/etc/adblock/combined.sources.gz'
Expand Down Expand Up @@ -137,7 +147,22 @@ def list_blocklist(e_uci):
return { "data": ret }

def list_settings(e_uci):
return { 'data': {'enabled': e_uci.get('banip', 'global', 'ban_enabled') == '1' } }
return {
'data': {
'enabled': e_uci.get('banip', 'global', 'ban_enabled') == '1',
'ban_logprerouting': e_uci.get('banip', 'global', 'ban_logprerouting', default=False) == '1',
'ban_loginput': e_uci.get('banip', 'global', 'ban_loginput', default=False) == '1',
'ban_logforwardwan': e_uci.get('banip', 'global', 'ban_logforwardwan', default=False) == '1',
'ban_logforwardlan': e_uci.get('banip', 'global', 'ban_logforwardlan', default=False) == '1',
'ban_loglimit': True if int(e_uci.get('banip', 'global', 'ban_loglimit', default=100)) > 0 else False,
'ban_logcount': e_uci.get('banip', 'global', 'ban_logcount', default=1),
'ban_logterm': e_uci.get('banip', 'global', 'ban_logterm', list=True, default=[]),
'ban_icmplimit': True if int(e_uci.get('banip', 'global', 'ban_icmplimit', default=10)) > 0 else False,
'ban_synlimit': True if int(e_uci.get('banip', 'global', 'ban_synlimit', default=10)) > 0 else False,
'ban_udplimit': True if int(e_uci.get('banip', 'global', 'ban_udplimit', default=100)) > 0 else False,
'ban_nftexpiry': e_uci.get('banip', 'global', 'ban_nftexpiry', default='30m')
}
}

def edit_blocklist(e_uci, payload):
feeds = list_feeds()
Expand All @@ -160,21 +185,93 @@ def set_default(e_uci, option, value):
e_uci.set('banip', 'global', option, value)

def edit_settings(e_uci, payload):
if 'enabled' not in payload:
raise ValidationError('enabled', 'required')
if not isinstance(payload['enabled'], bool):
raise ValidationError('enabled', 'invalid', payload['enabled'])

if payload['enabled']:
# validate all other payload options
if 'ban_logprerouting' not in payload:
raise ValidationError('ban_logprerouting', 'required')
if not isinstance(payload['ban_logprerouting'], bool):
raise ValidationError('ban_logprerouting', 'invalid', payload['ban_logprerouting'])
if 'ban_loginput' not in payload:
raise ValidationError('ban_loginput', 'required')
if not isinstance(payload['ban_loginput'], bool):
raise ValidationError('ban_loginput', 'invalid', payload['ban_loginput'])
if 'ban_logforwardwan' not in payload:
raise ValidationError('ban_logforwardwan', 'required')
if not isinstance(payload['ban_logforwardwan'], bool):
raise ValidationError('ban_logforwardwan', 'invalid', payload['ban_logforwardwan'])
if 'ban_logforwardlan' not in payload:
raise ValidationError('ban_logforwardlan', 'required')
if not isinstance(payload['ban_logforwardlan'], bool):
raise ValidationError('ban_logforwardlan', 'invalid', payload['ban_logforwardlan'])
if 'ban_icmplimit' not in payload:
raise ValidationError('ban_icmplimit', 'required')
if not isinstance(payload['ban_icmplimit'], bool):
raise ValidationError('ban_icmplimit', 'invalid', payload['ban_icmplimit'])
if 'ban_synlimit' not in payload:
raise ValidationError('ban_synlimit', 'required')
if not isinstance(payload['ban_synlimit'], bool):
raise ValidationError('ban_synlimit', 'invalid', payload['ban_synlimit'])
if 'ban_udplimit' not in payload:
raise ValidationError('ban_udplimit', 'required')
if not isinstance(payload['ban_udplimit'], bool):
raise ValidationError('ban_udplimit', 'invalid', payload['ban_udplimit'])
if 'ban_loglimit' not in payload:
raise ValidationError('ban_loglimit', 'required')
if not isinstance(payload['ban_loglimit'], bool):
raise ValidationError('ban_loglimit', 'invalid', payload['ban_loglimit'])

if payload['ban_loglimit']:
if 'ban_logcount' not in payload:
raise ValidationError('ban_logcount', 'required')
if not isinstance(payload['ban_logcount'], int):
raise ValidationError('ban_logcount', 'invalid', payload['ban_logcount'])
if 'ban_logterm' not in payload:
raise ValidationError('ban_logterm', 'required')
if not isinstance(payload['ban_logterm'], list):
raise ValidationError('ban_logterm', 'invalid', payload['ban_logterm'])
if 'ban_nftexpiry' not in payload:
raise ValidationError('ban_nftexpiry', 'required')
if not isinstance(payload['ban_nftexpiry'], str):
raise ValidationError('ban_nftexpiry', 'invalid', payload['ban_nftexpiry'])

# Validation completed, set the values
e_uci.set('banip', 'global', 'ban_enabled', '1')
set_default(e_uci, 'ban_fetchcmd', 'curl')
set_default(e_uci, 'ban_protov4', '1')
set_default(e_uci, 'ban_protov6', '1')
set_default(e_uci, 'ban_nftexpiry', '30m')
set_default(e_uci, 'ban_logcount', '3')

e_uci.set('banip', 'global', 'ban_logprerouting', payload['ban_logprerouting'])
e_uci.set('banip', 'global', 'ban_loginput', payload['ban_loginput'])
e_uci.set('banip', 'global', 'ban_logforwardwan', payload['ban_logforwardwan'])
e_uci.set('banip', 'global', 'ban_logforwardlan', payload['ban_logforwardlan'])
e_uci.set('banip', 'global', 'ban_loglimit', 100 if payload['ban_loglimit'] else 0)

e_uci.set('banip', 'global', 'ban_icmplimit', 10 if payload['ban_icmplimit'] else 0)
e_uci.set('banip', 'global', 'ban_synlimit', 10 if payload['ban_synlimit'] else 0)
e_uci.set('banip', 'global', 'ban_udplimit', 100 if payload['ban_udplimit'] else 0)

if payload['ban_loglimit']:
e_uci.set('banip', 'global', 'ban_logcount', payload['ban_logcount'])
e_uci.set('banip', 'global', 'ban_logterm', payload['ban_logterm'])
e_uci.set('banip', 'global', 'ban_nftexpiry', payload['ban_nftexpiry'])
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 list_blocked():
return {"data": get_block_list()}

def add_allowed(payload):
cur = get_allow_list()
# extract address from cur list
Expand All @@ -184,6 +281,16 @@ def add_allowed(payload):
write_allow_list(cur)
return {'message': 'success'}


def add_blocked(payload):
cur = get_block_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_block_list(cur)
return {'message': 'success'}


def delete_allowed(payload):
cur = get_allow_list()
if payload['address'] not in [x['address'] for x in cur]:
Expand All @@ -196,6 +303,20 @@ def delete_allowed(payload):
write_allow_list(cur)
return {'message': 'success'}


def delete_blocked(payload):
cur = get_block_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_block_list(cur)
return {'message': 'success'}


def edit_allowed(payload):
cur = get_allow_list()
if payload['address'] not in [x['address'] for x in cur]:
Expand All @@ -207,6 +328,19 @@ def edit_allowed(payload):
write_allow_list(cur)
return {'message': 'success'}


def edit_blocked(payload):
cur = get_block_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_block_list(cur)
return {'message': 'success'}


def dns_list_blocklist(e_uci):
ret = []
feeds = list_dns_feeds()
Expand Down Expand Up @@ -345,11 +479,27 @@ if cmd == 'list':
'list-blocklist': {},
'edit-blocklist': { "blocklist": "blocklist_name", "enabled": True },
'list-settings': {},
'edit-settings': { 'enabled': True },
'edit-settings': {
'enabled': True,
'ban_logprerouting': True,
'ban_loginput': True,
'ban_logforwardwan': True,
'ban_logforwardlan': True,
'ban_loglimit': True,
'ban_logcount': 3,
'ban_logterm': ['string'],
'ban_icmplimit': True,
'ban_synlimit': True,
'ban_udplimit': 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' },
'list-blocked': {},
'add-blocked': {'address': '1.2.3.4', 'description': 'optional'},
'edit-blocked': {'address': '1.2.3.4', 'description': 'optional'},
'delete-blocked': {'address': '1.2.3.4'},
'dns-list-blocklist': {},
'dns-edit-blocklist': { "blocklist": "blocklist_name", "enabled": True },
'dns-list-settings': {},
Expand Down Expand Up @@ -387,6 +537,17 @@ elif cmd == 'call':
elif action == 'delete-allowed':
payload = json.loads(sys.stdin.read())
ret = delete_allowed(payload)
elif action == 'list-blocked':
ret = list_blocked()
elif action == 'add-blocked':
payload = json.loads(sys.stdin.read())
ret = add_blocked(payload)
elif action == 'edit-blocked':
payload = json.loads(sys.stdin.read())
ret = edit_blocked(payload)
elif action == 'delete-blocked':
payload = json.loads(sys.stdin.read())
ret = delete_blocked(payload)

if action == 'dns-list-blocklist':
ret = dns_list_blocklist(e_uci)
Expand Down
2 changes: 2 additions & 0 deletions packages/ns-threat_shield/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ define Package/ns-threat_shield/install
$(INSTALL_DATA) ./files/banip.nethesis.feeds $(1)/etc/banip
$(INSTALL_BIN) ./files/adjust-banip.py $(1)/usr/libexec/ns-api/post-commit/
$(INSTALL_BIN) ./files/configure-banip-wans.py $(1)/usr/libexec/ns-api/pre-commit/
$(INSTALL_DIR) $(1)/etc/uci-defaults
$(INSTALL_BIN) ./files/banip-defaults $(1)/etc/uci-defaults/99-nethsec-banip
gzip -9n $(1)/usr/share/threat_shield/nethesis-dns.sources
gzip -9n $(1)/usr/share/threat_shield/community-dns.sources
endef
Expand Down
22 changes: 22 additions & 0 deletions packages/ns-threat_shield/files/banip-defaults
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[ "$(uci -q get banip.global.ban_logforwardwan)" != "" ] && exit 0

uci -q batch << EOI
set banip.global.ban_logforwardwan="1"
set banip.global.ban_logforwardlan="1"
set banip.global.ban_logprerouting="0"
set banip.global.ban_loginput="0"

set banip.global.ban_loglimit="100"
set banip.global.ban_logcount="3"
set banip.global.ban_nftexpiry="30m"

delete banip.global.ban_logterm
add_list banip.global.ban_logterm="Exit before auth from"
add_list banip.global.ban_logterm="authentication failed for user"

set banip.global.ban_icmplimit="10"
set banip.global.ban_synlimit="10"
set banip.global.ban_udplimit="100"

commit banip
EOI

0 comments on commit d63f624

Please sign in to comment.