Mini Shell
Direktori : /usr/sbin/ |
|
Current File : //usr/sbin/imunify360-webshield-compose-lists |
#!/opt/imunify360/venv/bin/python3
import glob
import ipaddress
import json
import os
import random
import shutil
import string
import sys
DESCRIPTION_PATH = '/var/imunify360/files/whitelist/v2/description.json'
COMMON_PROXY_PATH = '/etc/imunify360-webshield/common-proxies.conf'
WHITELISTED_PATH = ('/etc/imunify360-webshield/webshield-http.conf.d'
'/static-whitelist.conf')
GEO_SRC_DIR = '/var/imunify360/files/geo/v1'
GEO_DST_CFG = '/etc/imunify360-webshield/country_ips.conf'
CUSTOM_WHITELIST_GLOB = '/etc/imunify360/whitelist/*.txt'
CUSTOM_WHITELIST_CONF = '/etc/imunify360-webshield/custom-whitelisted.conf'
CUSTOM_BLACKLIST_GLOB = '/etc/imunify360/blacklist/*.txt'
CUSTOM_BLACKLIST_CONF = '/etc/imunify360-webshield/custom-blacklisted.conf'
GEO_DST_CFG_HDR = """# THIS FILE IS GENERATED AUTOMATICALLY
# BY IMUNIFY360-WEBSHIELD. DO NOT MODIFY IT
"""
def subnet_valid(subnet):
if not subnet:
return False
try:
if '.' in subnet:
ipaddress.IPv4Network(subnet, strict=False)
else:
ipaddress.IPv6Network(subnet, strict=False)
except ipaddress.AddressValueError:
return False
return True
def get_config():
"""
Reads JSON data from description.json and returns parsed dict
:return: config -> dict
"""
try:
with open(DESCRIPTION_PATH) as i:
return json.load(i)
except Exception as e:
return
def get_files(config):
"""
Gets paths of IP list files. Depending on is the list is a proxy list
or not.
:param config: dict -> descriptions
:return: tuple -> tuple of lists (proxies and whitelisted)
"""
proxies = []
whitelisted = []
items = config.get('items')
if not items:
return
for item in items:
url = item.get('url')
if not url:
continue
_dir = os.path.dirname(DESCRIPTION_PATH)
_base = os.path.basename(url)
path = os.path.join(_dir, _base)
name = item.get('name', _base)
if os.path.extsep in name:
name, _ = os.path.splitext(name)
if 'proxy' in item.get('groups', []):
proxies.append((path, name))
# whitelists are no more used so empty list returned
return proxies, whitelisted
def get_ips_from_files(pairs):
"""
Reads all filepaths and places its addresses into common list
:param paths: list -> filepaths to be read
:return: list -> list of IP addresses
"""
ips = []
for path, name in pairs:
try:
with open(path) as i:
for line in i:
if line.startswith('#'):
continue
ip = line.strip()
if not ip:
continue
ips.append((ip, name))
except Exception:
continue
return ips
def generate(length=8):
"""
Generates random string or specified length
:param length: int -> random string length
:return: str -> generated random string
"""
sample = string.digits + string.ascii_letters
return ''.join(random.sample(sample, length))
def save(ips, path, wrap=False):
"""
Saves IP lists as nginx configs
:param ips: list -> list of IP addresses to be saved
:param path: str -> path to config to generate
:param wrap: boolean -> if the IPs in config are to be wrapped in nginx
'geo' structure
"""
temp = path + '.' + generate()
tpl = '{} {};\n'
try:
with open(temp, 'w') as o:
if wrap:
o.write('geo $static_whitelisted {\n')
if ips:
for ip, token in dict(ips).items():
o.write(tpl.format(ip, token))
if wrap:
o.write('}\n')
except Exception as e:
return
shutil.move(temp, path)
def make_geo():
"""
Generates mapping subnet -> country for webshield
Update: as we moved blacklisted countries processing into WAFD,
there's no more need to keep all countries IPs in the webshield.
However we still need to keep China IP addresses because of
splash_as_captcha functionality
"""
if not os.path.isdir(GEO_SRC_DIR):
return
temp = GEO_DST_CFG + '.' + generate()
line_fmt = '{} {};\n'
with open(temp, 'w') as w:
w.write(GEO_DST_CFG_HDR)
for name in os.listdir(GEO_SRC_DIR):
if name.startswith('CountrySubnets-') and name.endswith('.txt'):
code = name[15:17]
if code != "CN": # Keep China addresses only
continue
full_path = os.path.join(GEO_SRC_DIR, name)
try:
with open(full_path) as r:
for line in r:
stripped = line.strip()
if not stripped:
continue
w.write(line_fmt.format(stripped, code))
except Exception:
continue
os.rename(temp, GEO_DST_CFG)
def make_custom():
"""
Reads all IP addresses from custom directories and forms webshield configs
from them
"""
pairs = (
(CUSTOM_WHITELIST_GLOB, CUSTOM_WHITELIST_CONF),
(CUSTOM_BLACKLIST_GLOB, CUSTOM_BLACKLIST_CONF))
fmt = '{} 1;\n'
for read_glob, conf_path in pairs:
read_paths = glob.glob(read_glob)
if not read_paths:
continue
temp_path = conf_path + '.' + generate()
try:
with open(temp_path, 'w') as w:
for read_path in read_paths:
with open(read_path) as r:
for line in r:
subnet = line.partition('#')[0].strip()
if not subnet:
# blank line / [full line/end-of-line] comment
continue
if not subnet_valid(subnet):
continue
w.write(fmt.format(subnet))
os.rename(temp_path, conf_path)
except Exception:
continue
def main():
"""
The main workflow routine. Read config, parse files, make lists, save them
"""
make_geo()
config = get_config()
if config is None:
save(None, WHITELISTED_PATH, True)
return
paths = get_files(config)
if not paths or len(paths) != 2:
print("Incomplete data. Return", file=sys.stderr)
save(None, WHITELISTED_PATH, True)
return
proxies, whitelisted = paths
proxies_ips = get_ips_from_files(proxies)
whitelisted_ips = get_ips_from_files(whitelisted)
destinations = ((proxies_ips, COMMON_PROXY_PATH, False),
(whitelisted_ips, WHITELISTED_PATH, True))
for ips, path, wrap in destinations:
save(ips, path, wrap)
def dispatcher():
if len(sys.argv) > 1 and sys.argv[1] == '--custom-lists-only':
make_custom()
else:
main()
if __name__ == '__main__':
dispatcher()
Zerion Mini Shell 1.0