diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f14285f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.pyc +__pycache__ +*.DS_Store diff --git a/src/a10_plugin/.gitignore b/src/a10_plugin/.gitignore new file mode 100644 index 0000000..d9e24df --- /dev/null +++ b/src/a10_plugin/.gitignore @@ -0,0 +1,5 @@ +*.py +*.python +*.egg +*.egg-info/ + diff --git a/src/a10_plugin/README.md b/src/a10_plugin/README.md new file mode 100644 index 0000000..d4b8edd --- /dev/null +++ b/src/a10_plugin/README.md @@ -0,0 +1,68 @@ +Fastnetmon Plugin: A10 Networks TPS AXAPIv3 integration for FastNetMon + +This script connect to A10 TPS device to create Protected Object and announce BGP route toward upstream router upon FastNetMon ban detection. + +1. Place both Python files at a directory that is reachable by FastNetMon +2. Make sure both scripts are executable, i.e. "chmod +x a10.py fastnetmon_a10_v0.2.py" +3. Modify fastnetmon.conf for notification, i.e. notify_script_path = /fastnetmon_a10_v0.2.py + +Please modify the following: + +1. A10 mitigator IP +2. BGP Autonomous System Number +3. Username and Password for your A10 Device. Note that you can use your own password vault or protection schema + +For more information about A10 Networks AXAPIv3: +https://www.a10networks.com/resources/glossary/axapi-custom-management + + +v0.2 - Jul 7th, 2016 - initial commit + +Author: Eric Chou ericc@a10networks.com + +Feedback and Feature Requests are Welcomed. + +Example Usage: + +- Ban action: + +``` +a10-ubuntu3:~/fastnetmon/src/a10_plugin$ sudo python fastnetmon_a10_v0.2.py "10.10.10.10" "outgoing" "111111" "ban" + +TH4435-1#show ddos dst zone all-entries +Legend (Rate/Limit): 'U'nlimited, 'E'xceeded, '-' Not applicable +Legend (State) : 'W'hitelisted, 'B'lacklisted, 'P'ermitted, black'H'oled, 'I'dle, 'L'earning, 'M'onitoring, '-' Regular mode +Zone Name / Zone Service Info | [State]| Curr Conn| Conn Rate| Pkt Rate | kBit Rate|Frag Pkt R|Sources # |Age |LockU + | | Limit| Limit| Limit| Limit| Limit| Limit|#min| Time +----------------------------------------------------------------------------------------------------------------------------------- +10.10.10.10_zone [M] U U U U U 1S 0 + - U U U U U +Displayed Entries: 1 +Displayed Services: 0 + +TH4435-1#sh run router bgp +!Section configuration: 221 bytes +! +router bgp 64513 + + network 10.10.10.10/32 + +! +TH4435-1# +TH4435-1#sh run router bgp | i 10.10.10.10 + network 10.10.10.10/32 +TH4435-1# +``` + +- Unban action: + +a10-ubuntu3:~/fastnetmon/src/a10_plugin$ sudo python fastnetmon_a10_v0.2.py "10.10.10.10" "outgoing" "111111" "unban" + +``` +TH4435-1#sh run router bgp | i 10.10.10.10 +TH4435-1# +``` + + + + diff --git a/src/a10_plugin/a10.py b/src/a10_plugin/a10.py new file mode 100755 index 0000000..a391b61 --- /dev/null +++ b/src/a10_plugin/a10.py @@ -0,0 +1,43 @@ + +# +# v0.1 +# ericc@a10networks.com +# + +import json, urllib2 + +def axapi_auth(host, username, password): + base_uri = 'https://'+host + auth_payload = {"credentials": {"username": username, "password": password}} + r = axapi_action(base_uri + '/axapi/v3/auth', payload=auth_payload) + signature = json.loads(r)['authresponse']['signature'] + return base_uri, signature + + +def axapi_action(uri, payload='', signature='', method='POST'): + try: + if method == 'POST': + req = urllib2.Request(uri) + req.add_header('content-type', 'application/json') + if signature: + req.add_header('Authorization', 'A10 {0}'.format(signature)) + response = urllib2.urlopen(req, json.dumps(payload)) + elif method == 'GET': + req = urllib2.Request(uri) + req.add_header('content-type', 'application/json') + if signature: + req.add_header('Authorization', 'A10 {0}'.format(signature)) + response = urllib2.urlopen(req) + elif method == 'DELETE': + req = urllib2.Request(uri) + req.add_header('content-type', 'application/json') + req.get_method = lambda: 'DELETE' + if signature: + req.add_header('Authorization', 'A10 {0}'.format(signature)) + response = urllib2.urlopen(req) + return response.read() + except Exception as e: + raise + + + diff --git a/src/a10_plugin/configs/dns_test_server.txt b/src/a10_plugin/configs/dns_test_server.txt new file mode 100644 index 0000000..963cfa6 --- /dev/null +++ b/src/a10_plugin/configs/dns_test_server.txt @@ -0,0 +1,14 @@ +! +ddos dst zone Test-Server + ip 210.0.0.10 + operational-mode monitor + port 53 udp + level 0 + zone-escalation-score 1 + indicator pkt-rate + score 50 + zone-threshold 1 + zone-violation-actions bmf_a10_script + level 1 +! + diff --git a/src/a10_plugin/fastnetmon_a10_v0.2.py b/src/a10_plugin/fastnetmon_a10_v0.2.py new file mode 100755 index 0000000..c2ba4a1 --- /dev/null +++ b/src/a10_plugin/fastnetmon_a10_v0.2.py @@ -0,0 +1,94 @@ +#!/usr/bin/python + +# +# v0.2 created [ban | unban] [on ramp | off ramp action] for A10 TPS +# v0.3 offload URI path and json_body into separate json_config files +# Eric Chou (ericc@a10networks.com) +# + +import sys +from sys import stdin +import optparse +import logging, json +from a10 import axapi_auth, axapi_action +from json_config.logoff import logoff_path +from json_config.write_memory import write_mem_path +from json_config.ddos_dst_zone import ddos_dst_zone_path, ddos_dst_zone +from json_config.bgp import bgp_advertisement_path, bgp_advertisement + +LOG_FILE = "/var/log/fastnetmon-notify.log" + + +logger = logging.getLogger("DaemonLog") +logger.setLevel(logging.INFO) +formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") +handler = logging.FileHandler(LOG_FILE) +handler.setFormatter(formatter) +logger.addHandler(handler) + + + +client_ip_as_string=sys.argv[1] +data_direction=sys.argv[2] +pps_as_string=int(sys.argv[3]) +action=sys.argv[4] + +logger.info(" - " . join(sys.argv)) + + +# A10 Mitigator Information +mitigator_ip = "192.168.199.152" +zone_name = client_ip_as_string + "_zone" +ip_addr = client_ip_as_string +asn="65003" +mitigator_base_url, signature = axapi_auth(mitigator_ip, "admin", "a10") + + +if action == "unban": + try: + r = axapi_action(mitigator_base_url+'/axapi/v3/router/bgp/'+asn+'/network/ip-cidr/172.31.201.2%2F32', method="DELETE", signature=signature) + except Exception as e: + logger.info("route not removed in unban, returned: " + str(e)) + + # Commit config + axapi_action(mitigator_base_url+write_mem_path, signature=signature) + # Logoff + axapi_action(mitigator_base_url+logoff_path, signature=signature) + + sys.exit(0) + +elif action == "ban" or action == "attack_details": + + r = axapi_action(mitigator_base_url+ddos_dst_zone_path, method='GET', signature=signature) + if zone_name in [i['zone-name'] for i in json.loads(r)['zone-list']]: + r = axapi_action(mitigator_base_url+ddos_dst_zone_path+zone_name, method="DELETE", signature=signature) + logger.info(str(r)) + + # A10 Mitigation On Ramp + zone_name = client_ip_as_string + "_zone" + ip_addr = client_ip_as_string + returned_body = ddos_dst_zone(zone_name, ip_addr) + try: + r = axapi_action(mitigator_base_url+ddos_dst_zone_path, signature=signature, payload=returned_body) + except Exception as e: + logger("zone not created: " + str(e)) + + route_advertisement = bgp_advertisement(ip_addr) + try: + r = axapi_action(mitigator_base_url+bgp_advertisement_path+asn, payload=route_advertisement, signature=signature) + except Exception as e: + logger("route not added: " + str(e)) + + # Commit changes + axapi_action(mitigator_base_url+write_mem_path, signature=signature) + # Log off + axapi_action(mitigator_base_url+logoff_path, signature=signature) + + sys.exit(0) + +else: + sys.exit(0) + + + + diff --git a/src/a10_plugin/json_config/__init__.py b/src/a10_plugin/json_config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/a10_plugin/json_config/bgp.py b/src/a10_plugin/json_config/bgp.py new file mode 100644 index 0000000..63a7ca3 --- /dev/null +++ b/src/a10_plugin/json_config/bgp.py @@ -0,0 +1,16 @@ +bgp_advertisement_path = '/axapi/v3/router/bgp/' + +def bgp_advertisement(ip_addr): + route_advertisement = { + "bgp": + { + "network": { + "ip-cidr-list": [ + { + "network-ipv4-cidr":ip_addr+"/32", + } + ] + }, + } + } + return route_advertisement diff --git a/src/a10_plugin/json_config/ddos_dst_zone.py b/src/a10_plugin/json_config/ddos_dst_zone.py new file mode 100644 index 0000000..666efab --- /dev/null +++ b/src/a10_plugin/json_config/ddos_dst_zone.py @@ -0,0 +1,44 @@ + +ddos_dst_zone_path = '/axapi/v3/ddos/dst/zone/' + +def ddos_dst_zone(zone_name, ip_addr): + port_num = 53 + port_protocol = 'udp' + ddos_dst_zone_payload = { + "zone-list": [ + { + "zone-name":zone_name, + "ip": [ + { + "ip-addr":ip_addr + } + ], + "operational-mode":"monitor", + "port": { + "zone-service-list": [ + { + "port-num":port_num, + "protocol":port_protocol, + "level-list": [ + { + "level-num":"0", + "zone-escalation-score":1, + "indicator-list": [ + { + "type":"pkt-rate", + "score":50, + "zone-threshold-num":1, + } + ], + }, + { + "level-num":"1", + } + ], + } + ], + }, + } + ] + } + return ddos_dst_zone_payload diff --git a/src/a10_plugin/json_config/logoff.py b/src/a10_plugin/json_config/logoff.py new file mode 100644 index 0000000..35c7f27 --- /dev/null +++ b/src/a10_plugin/json_config/logoff.py @@ -0,0 +1,3 @@ + +logoff_path = '/axapi/v3/logoff' + diff --git a/src/a10_plugin/json_config/write_memory.py b/src/a10_plugin/json_config/write_memory.py new file mode 100644 index 0000000..4634b16 --- /dev/null +++ b/src/a10_plugin/json_config/write_memory.py @@ -0,0 +1 @@ +write_mem_path = '/axapi/v3/write/memory' diff --git a/src/a10_plugin/tests/__init__.py b/src/a10_plugin/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/a10_plugin/tests/helperTests.py b/src/a10_plugin/tests/helperTests.py new file mode 100644 index 0000000..7153faf --- /dev/null +++ b/src/a10_plugin/tests/helperTests.py @@ -0,0 +1,43 @@ +import unittest,sys +sys.path.append('../') +from a10 import axapi_auth, axapi_action + +a10_tps = "192.168.199.152" +username = "admin" +password = "a10" +hostname = "TH4435" + +class Test_Auth(unittest.TestCase): + + def testAssertTrue(self): + print("Testing axapi_auth") + try: + mitigator_base_url, signature = axapi_auth(a10_tps, username, password) + print("base url: ", mitigator_base_url, "Signature: ", signature) + axapi_action(mitigator_base_url+"/axapi/v3/logoff") + + except Exception as e: + self.fail("Not authenticated") + + +class Test_API_Actions(unittest.TestCase): + + def testAssertTrue(self): + try: + print("Testing GET") + mitigator_base_url, signature = axapi_auth(a10_tps, username, password) + r = axapi_action(mitigator_base_url+"/axapi/v3/version/oper", method='GET', signature=signature) + print(str(r)) + print("Testing POST") + hostname_payload = {"hostname": {"value": hostname}} + r = axapi_action(mitigator_base_url+"/axapi/v3/hostname", payload=hostname_payload, signature=signature) + print(str(r)) + axapi_action(mitigator_base_url+"/axapi/v3/logoff") + + except Exception as e: + self.fail("Failed") + +if __name__ == "__main__": + unittest.main() + +