Skip to content

Instantly share code, notes, and snippets.

@bashtheshell
Last active May 15, 2023 17:22
Show Gist options
  • Save bashtheshell/4a31f1c3704107c591d1156c0b995a12 to your computer and use it in GitHub Desktop.
Save bashtheshell/4a31f1c3704107c591d1156c0b995a12 to your computer and use it in GitHub Desktop.
Translate manually-written '-A INPUT' lines in 'rules.v4' iptables file to the format identical to `iptables -S` output.
#!/usr/bin/env python3
# The purpose of this program is to only slightly modify and rearrange all 'A-INPUT' arguments, that may be manually written to the `iptables`'s `rules.v4`
# file, in the order that the `iptables -S` command uses. This would allow one to see less lines when using `diff` command to compare
# the newly-translated output from this program with `iptables -S` output. Ideally, you should not find any '-A INPUT' in the `diff` output. Otherwise, there
# may have been a bug with the program (e.g. some strings elude the regular expression pattern). Please remember that lines beginning with comment ('#')
# are ignored.
# USAGE:
# You can either give it a file as its argument or pass a file to the program using STDIN. Here are few ways:
#
# ❯ ./iptables-A_INPUT-translate.py /etc/iptables/rules.v4 > new_temp_rules.v4
# ❯ cat /etc/iptables/rules.v4 | ./iptables-A_INPUT-translate.py > new_temp_rules.v4
# ❯ ./iptables-A_INPUT-translate.py < /etc/iptables/rules.v4 > new_temp_rules.v4
# # EXAMPLE:
# ❯ echo '-A INPUT -m state --state RELATED,ESTABLISHED,NEW -m tcp -p tcp -s 123.100.200.99/24 --dport 8080 -m comment --comment "Something here and there - created on 02-14-20" -j ACCEPT' | ./iptables-A_INPUT-translate.py
# -A INPUT -s 123.100.200.99/24 -p tcp -m state --state NEW,RELATED,ESTABLISHED -m tcp --dport 8080 -m comment --comment "Something here and there - created on 02-14-20" -j ACCEPT
# ❯ echo '-A INPUT -m state --state ESTABLISHED,RELATED -m tcp -p tcp -s mail.google.com --dport 993 -m comment --comment "pending deletion - 10-20-30" -j ACCEPT' | ./iptables-A_INPUT-translate.py
# -A INPUT -s 142.251.40.197/32 -p tcp -m state --state RELATED,ESTABLISHED -m tcp --dport 993 -m comment --comment "pending deletion - 10-20-30" -j ACCEPT
import re
import socket
import sys
def rearrange_A_INPUT_arguments(parameter):
# Split up the line string into portions
parameter_line = [" ".join(pair) for pair in re.findall( r'(-{1,2}\w+)\s*([-\w./,]+|".+?")' , parameter)]
new_line = []
if '-A INPUT' in parameter and parameter.index('-A INPUT') == 0:
# Automatically add the first portion as it's always the case
new_line.append(parameter_line[0])
temporary_line = {}
for each_item in parameter_line:
if re.match(r'-s', each_item) or re.match(r'--source', each_item):
temporary_string = ""
# Strip both variations of the source option
if re.match(r'-s', each_item):
temporary_string = each_item.split('-s', 1)[-1].lstrip()
else:
temporary_string = each_item.split('--source', 1)[-1].lstrip()
# Get the address (either IP address or hostname) portion of the string
address_string = temporary_string.rsplit('/', 1)
ip_address_pattern = re.compile("^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
# Convert the address to the appropriate format
if re.match(ip_address_pattern, address_string[0]):
if len(address_string) > 1:
temporary_string = '-s ' + address_string[0] + '/' + address_string[1]
else:
temporary_string = '-s ' + address_string[0] + '/32'
else:
temporary_string = '-s ' + socket.gethostbyname(address_string[0]) + '/32'
temporary_line['source'] = temporary_string
if re.match(r'-i', each_item):
temporary_line['interface'] = each_item
if re.match(r'-p', each_item):
temporary_line['protocol'] = each_item
if re.match(r'-m state', each_item):
temporary_line['match_state'] = each_item
if re.match(r'--state', each_item):
# Sort the conntrack value in this order if possible: (NEW,RELATED,ESTABLISHED)
temporary_string = []
conntrack_string = each_item.split('--state', 1)[-1].lstrip().split(',')
if len(conntrack_string) > 1:
for conntrack in ['NEW', 'RELATED', 'ESTABLISHED']:
if conntrack in conntrack_string:
temporary_string.append(conntrack)
conntrack_string.remove(conntrack)
for conntrack in conntrack_string:
temporary_string.append(conntrack)
temporary_line['state'] = '--state ' + ','.join(temporary_string)
else:
temporary_line['state'] = each_item
if re.match(r'-m (tc|ud)p', each_item):
temporary_line['match_protocol'] = each_item
if re.match(r'--dport', each_item):
temporary_line['destination_port'] = each_item
if re.match(r'-m comment', each_item):
temporary_line['match_comment'] = each_item
if re.match(r'--comment', each_item):
# Remove quotations if comment contains no space except for periods
temporary_string = each_item.split('--comment', 1)[-1].lstrip(' "').rstrip('" ')
if temporary_string.find(' ') < 0 and temporary_string.find('.') < 0:
temporary_line['comment'] = '--comment ' + temporary_string
elif temporary_string.find('.') >= 0:
temporary_line['comment'] = '--comment ' + '"' + temporary_string + '"'
else:
temporary_line['comment'] = each_item
if re.match(r'-j', each_item):
temporary_line['jump'] = each_item
# Build the new line (putting the arguments in the correct order)
new_argument_order = ['source', 'interface', 'protocol', 'match_state', 'state', 'match_protocol', 'destination_port', 'match_comment', 'comment', 'jump']
for i in new_argument_order:
if i in temporary_line:
new_line.append(temporary_line[i])
# Return the new rearranged line string
return ' '.join(new_line) + '\n'
else:
return parameter
if __name__ == "__main__":
# Read file from the first argument only
if len(sys.argv) == 2:
with open(sys.argv[1], 'r') as f:
for line in f:
if not line:
break
sys.stdout.write(str(rearrange_A_INPUT_arguments(line)))
# Alternatively, read from STDIN
elif not sys.stdin.isatty():
for line in sys.stdin:
if not line:
break
sys.stdout.write(str(rearrange_A_INPUT_arguments(line)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment