防火牆Deny記錄檢測
實現思路
部署在不同區域的網絡日誌採集服務器實時收集不同型號防火牆設備日誌,通過syslog推送到日誌採集服務器,日誌採集服務器利用流式處理的方式,實時過濾其中deny的記錄,本地入庫記錄,並推送郵件負責人。必要時驅動網絡變更接口,自動提交防火牆變更申請,減少因防火牆不通引發的環境問題。
具體原則如下:
1、過濾日誌中Deny關鍵字,並不同網絡設備中的IP和端口信息
2、根據IP地址信息匹配CMDB的應用信息,當源IP和目標IP其中有一個在CMDB登記過,即入庫記錄
3、當源IP和目標IP同時在CMDB中存在記錄時,觸發郵件提醒,收件人是CMDB中登記角色的人員。
4、利用redis緩存做報警間隔的控制,目前重複報警一個小時推送一次
防火牆Deny的兩種類型
參考提醒郵件中“網絡設備日誌”項,報警日誌可以分爲兩種類型
1、因防火牆限制,直接被拒絕的請求,這類報警需要提交開通防火牆的申請。例如以下兩種網絡設備的日誌:
Deny tcp src YEWU2:123.25.142.29/60452 dst JIERU1:152.186.218.90/445 by access-group “YEWU2_in_ALL” [0x0, 0x0]
NetScreen device_id=BS52FW0A-M5 [Root]system-notification-00257(traffic): start_time=”2018-06-12 08:33:28” duration=0 policy_id=1 service=syslog proto=17 src zone=CS dst zone=M5 action=Deny sent=0 rcvd=177 src=123.1.2.143 dst=123.7.35.2 src_port=1041 dst_port=514 session_id=0
2、防火牆上面開通策略的連接,session因超時被防火牆斷開,但是後續應用發送的數據包沒有sync包,所以連接建立不起來被丟棄的日誌,這種應用一般要開通長連接。如果應用自身有keep-alive機制,則不需要網絡上開通長連接。例如如下日誌,存在 no connecttion 關鍵字:
Deny TCP (no connection) from 123.253.19.3/443 to 83.28.254.14/55312 flags RST on interface JIERU1
數據流圖
郵件提醒
【標題】:提醒:<網絡設備記錄的報警時間> <源應用> 到 <目標應用> 訪問被防火牆Deny
【示例】:提醒:2018-06-12 08:56:51 AAA 到 BBB 訪問被防火牆Deny
【正文】:
具體信息如下:
時間點:<網絡設備記錄的報警時間>
原地址:<源IP>
原端口:<原端口>
原地址信息:<原IP地址在CMDB中登記的信息>
目標地址:<目標IP>
目標端口:<目標端口>
目標地址信息:<目標IP地址在CMDB中登記的信息>
網絡設備日誌:<網絡設備日誌>
代碼
import re
import redis
from pykafka import KafkaClient
from sqlalchemy.orm import *
from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base
import datetime
import os
Base = declarative_base()
os.environ['NLS_LANG']='AMERICAN_AMERICA.AL32UTF8'
from ELK.NOTES import *
#mysql中記錄deny連接記錄
class DENYLOG(Base):
__tablename__ = "DENYLOG"
logtime = Column("logtime", DateTime(), primary_key=True)
srcip = Column("srcip", String(20))
srcport = Column("srcport", String(10))
srcdesc = Column("srcdesc", String(2000))
tgtip = Column("tgtip", String(20))
tgtport = Column("tgtport", String(10))
tgtdesc = Column("tgtdesc", String(2000))
mesg = Column("mesg", Text())
regex_ipport0=r'((?<![0-9])(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))(?![0-9])*)/([1-9][0-9]*)'
regex_ipport1=r'src=(.*) dst=(.*) src_port=(.*) dst_port=([1-9][0-9]*)'
regex_ipport2=r'source-ip=(.*?), source-port=(.*?), destination-ip=(.*?), destination-port=(.*?), '
blacklist=["123.4.19.11","123.6.35.7","123.6.131.101"]
whitelist=["123","124","125"]
#黑名單和白名單過濾
def IpFilter(ip):
ipstr=ip.split(".")[0]
#print("ipstr",ipstr)
if ipstr in whitelist and ip not in blacklist:
return 0
else:
print(ip,"命中過濾條件")
return 1
#按照應用過濾,過濾一些監控類或運維類相關的廣播數據
def AppFilter(srcapp,tgtapp):
#print("ipstr",ipstr)
if srcapp=="F-AAA" and tgtapp=="F-AAA":
print("命中APP過濾條件")
return 1
elif srcapp=="F-BBB" and tgtapp=="F-BBB":
print("命中APP過濾條件")
return 1
elif srcapp in ["V-z1",'V-z2','V-z3','V-z4'] or tgtapp in ["V-z1",'V-z2','V-z3','V-z4']:
print("命中APP過濾條件")
return 1
else:
return 0
#根據CMDB綁定應用節點信息
def getdetail(ip):
engine = create_engine('oracle://test:[email protected]/test')
DBsession = sessionmaker(bind=engine)
session = DBsession()
str = "SELECT APP,ENV,SRVZONE,NODE,FUNC TYPE FROM V_APPNODE WHERE IP='%s' ORDER BY CHECKTIME DESC"%ip
e=session.execute(str)
r=e.fetchone()
session.close()
if r:
app=r[0]
env=r[1]
srvzone=r[2]
node=r[3]
func=r[4]
return {"app": app, "env": env, "srvzone": srvzone, "node": node, "func": func}
else:
return {"app":"nodefine"}
def setmysql(logtime,srcip,srcport,srcdesc,tgtip,tgtport,tgtdesc,mesg):
engine = create_engine("mysql://test:[email protected]:3307/test?charset=utf8")
DBsession = sessionmaker(bind=engine)
session = DBsession()
denylog=DENYLOG(
logtime=logtime,
srcip=srcip,
srcport=srcport,
srcdesc=srcdesc,
tgtip=tgtip,
tgtport=tgtport,
tgtdesc=tgtdesc,
mesg=mesg
)
session.add(denylog)
session.commit()
#利用redis做報警收斂
def setredis(key,value):
r = redis.StrictRedis(host='123.28.206.32', port=6379)
logtime=value
rvalue=r.get(key)
if rvalue:
lasttime=str(rvalue,encoding='utf-8')
lasttime=datetime.datetime.strptime(lasttime, "%Y-%m-%d %H:%M:%S")
if lasttime+ datetime.timedelta(hours=1)<logtime:
r.set(key, logtime)
return 0
else:
print(lasttime,logtime,key,'距離上次報警沒有超過1小時')
return 1
else:
r.set(key,logtime)
return 0
def findcase(logtext):
r = redis.StrictRedis(host='123.28.206.32', port=6379)
try:
# 提取tradeid,apitext,非貪婪模式
s = logtext.split("###")
# print('s', s)
host = s[0]
timestamp = s[1]
message = s[2]
logtime=datetime.datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%fZ") + datetime.timedelta(hours=8)
# print("message",message)
rule = re.compile(r'deny', re.S | re.I)
# print("tradeid",tradeid)
denymsg = rule.search(message)
if denymsg:
if re.compile(regex_ipport0, re.S | re.I).findall(message):
ipport=re.compile(regex_ipport0, re.S | re.I).findall(message)
srcip=ipport[0][0]
srcport=ipport[0][1]
tgtip=ipport[1][0]
tgtport=ipport[1][1]
elif re.compile(regex_ipport1, re.S | re.I).findall(message):
ipport = re.compile(regex_ipport1, re.S | re.I).findall(message)
srcip=ipport[0][0]
srcport=ipport[0][2]
tgtip=ipport[0][1]
tgtport=ipport[0][3]
else:
ipport = re.compile(regex_ipport2, re.S | re.I).findall(message)
srcip=ipport[0][0]
srcport=ipport[0][1]
tgtip=ipport[0][2]
tgtport=ipport[0][3]
key=srcip+"_"+tgtip+"_"+tgtport
if IpFilter(srcip)==0:
flag=setredis(key,logtime)
#if flag=0 發郵件,開火牆
srcdesc=getdetail(srcip)
tgtdesc=getdetail(tgtip)
#print(logtime, srcip, srcport, srcdesc, tgtip, tgtport, tgtdesc, message)
setmysql(logtime, srcip, srcport, srcdesc, tgtip, tgtport, tgtdesc, message)
if flag==0:
if "nodefine" not in [srcdesc["app"],tgtdesc["app"]]:
notesid=getuser_by_ip(srcip)
text="您的應用出現被防火牆Deny的記錄,請了解<br/>具體信息如下:<br/>" \
"時間點:%s<br/>" \
"原地址:%s<br/>" \
"原端口:%s<br/>" \
"原地址信息:%s<br/>" \
"目標地址:%s<br/>" \
"目標端口:%s<br/>" \
"目標地址信息:%s<br/>" \
"網絡設備日誌:%s<br/>" \
"報警規則請參考: <link>http://123.20.17.88/article/81/</link> <br/>"%(logtime,srcip,srcport,srcdesc,tgtip,tgtport,tgtdesc,message)
subject="提醒:%s %s 到 %s 訪問被防火牆Deny"%(logtime,srcdesc["app"],tgtdesc["app"])
if AppFilter(srcdesc["app"],tgtdesc["app"])==0:
sendmail(notesid,text,subject)
print(notesid,text,subject)
else:
pass
#print("notfound")
except Exception as e:
#print(message)
print(e)
pass
def myjob(x):
print(" JOB %s START ....."%x)
# 啓動kafka監控進程
client = KafkaClient(hosts="123.28.206.32:9092")
topic = client.topics[str.encode("netsyslog")]
consumer = topic.get_simple_consumer(
consumer_group=b"t1",
auto_commit_enable=True,
auto_commit_interval_ms=1,
consumer_id=str.encode(str(topic))
)
for x in consumer:
if x is not None:
#seq = int(datetime.datetime.strftime(datetime.datetime.now(), "%Y%m%d%H%M%S%f"))
# print(x.value.decode('utf-8'))
findcase(x.value.decode('utf-8'))
if __name__ == '__main__':
myjob("1")