catflap 0.0.2 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.rubocop.yml +21 -0
- data/.rubocop_todo.yml +7 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +49 -0
- data/LICENSE +20 -0
- data/README.md +134 -0
- data/Rakefile +6 -0
- data/bin/catflap +71 -64
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/catflap.gemspec +32 -0
- data/etc/config.yaml +30 -0
- data/etc/init.d/catflap +89 -0
- data/etc/passfile.yaml +7 -0
- data/lib/catflap.rb +108 -64
- data/lib/catflap/command.rb +102 -0
- data/lib/catflap/firewall.rb +56 -0
- data/lib/catflap/http.rb +288 -0
- data/lib/catflap/netfilter/writer.rb +127 -0
- data/lib/catflap/plugins/firewall/iptables.rb +104 -0
- data/lib/catflap/plugins/firewall/netfilter.rb +114 -0
- data/lib/catflap/plugins/firewall/plugin.rb +67 -0
- data/lib/catflap/version.rb +5 -0
- data/lib/netfilter/writer.rb +125 -0
- data/ui/css/catflap.css +44 -0
- data/ui/images/catflap.png +0 -0
- data/ui/index.rhtml +23 -0
- data/ui/js/catflap.js +85 -0
- data/ui/js/sha256.js +166 -0
- metadata +109 -11
- data/lib/catflap-http.rb +0 -111
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'catflap/plugins/firewall/plugin'
|
2
|
+
require 'netfilter/writer'
|
3
|
+
include NetfilterWriter
|
4
|
+
|
5
|
+
##
|
6
|
+
# A firewall plugin driver to implement rules on the NetFilter filter table.
|
7
|
+
#
|
8
|
+
# This driver passes rules to the filter table via the iptables user-space
|
9
|
+
# client.Two new chains are installed in the filter table, named by default:
|
10
|
+
# CATFLAP-DENY and CATFLAP-ALLOW. These are installed in the INPUT chain. You
|
11
|
+
# can configure the driver to install a LOG to log rejected packets, and also
|
12
|
+
# whether to REJECT or DROP denied packets.
|
13
|
+
#
|
14
|
+
# If you want to redirect to the Catflap server login port instead, then you
|
15
|
+
# should use the 'netfilter' driver instead, which is the default.
|
16
|
+
#
|
17
|
+
# @example firewall: plugin: 'iptables'
|
18
|
+
#
|
19
|
+
# @author Nyk Cowham <nykcowham@gmail.com>
|
20
|
+
class IptablesDriver < FirewallPlugin
|
21
|
+
# Initialize the driver class.
|
22
|
+
# @param [Hash<String, Hash>] config built by Catflap::initialize_config()
|
23
|
+
# @param [Boolean] noop set true to not send commands to firewall client.
|
24
|
+
# @param [Boolean] verbose set true to print command output to stdout stream.
|
25
|
+
# @return void
|
26
|
+
def initialize(config, noop, verbose)
|
27
|
+
super
|
28
|
+
@chain = config['firewall']['options']['chain'] || 'CATFLAP'
|
29
|
+
@log_rejected = config['firewall']['options']['log_rejected'] || false
|
30
|
+
@accept_local = config['firewall']['options']['accept_local'] || false
|
31
|
+
@policy = config['firewall']['options']['reject_policy'].to_sym || :drop
|
32
|
+
@allow = @chain + '-ALLOW'
|
33
|
+
@deny = @chain + '-DENY'
|
34
|
+
@r = Rules.new(:filter, @dports)
|
35
|
+
@r.match('multiport')
|
36
|
+
@r.noop = noop
|
37
|
+
@r.verbose = verbose
|
38
|
+
end
|
39
|
+
|
40
|
+
# Method to install the driver rules into iptables.
|
41
|
+
# @return void
|
42
|
+
# @raise StandardError when iptables reports an error.
|
43
|
+
def install_rules
|
44
|
+
target = (@policy == :reject) ? 'REJECT' : 'DROP'
|
45
|
+
log = @log_rejected
|
46
|
+
local = @accept_local
|
47
|
+
@r.chain(:new, @allow)
|
48
|
+
.chain(:new, @deny)
|
49
|
+
.rule(:add, chain: 'INPUT', jump: @allow)
|
50
|
+
.rule(:add, chain: 'INPUT', jump: @deny)
|
51
|
+
.rule(:add, chain: @deny, jump: 'LOG') { log }
|
52
|
+
.rule(:add, chain: @deny, jump: target)
|
53
|
+
.rule(:add, chain: @allow, jump: 'ACCEPT',
|
54
|
+
src: 'localhost') { local }
|
55
|
+
.do
|
56
|
+
end
|
57
|
+
|
58
|
+
# Method to uninstall the driver rules from iptables.
|
59
|
+
# @return void
|
60
|
+
# @raise StandardError when iptables reports an error.
|
61
|
+
def uninstall_rules
|
62
|
+
@r.rule(:delete, chain: 'INPUT', jump: @allow)
|
63
|
+
.rule(:delete, chain: 'INPUT', jump: @deny)
|
64
|
+
.chain(:flush, @allow)
|
65
|
+
.chain(:delete, @allow)
|
66
|
+
.chain(:flush, @deny)
|
67
|
+
.chain(:delete, @deny)
|
68
|
+
.do
|
69
|
+
end
|
70
|
+
|
71
|
+
# Method to purge all rules from CATFLAP-ALLOW chain.
|
72
|
+
# @return void
|
73
|
+
# @raise StandardError when iptables reports an error.
|
74
|
+
def purge_rules
|
75
|
+
@r.chain(:flush, @allow).do
|
76
|
+
end
|
77
|
+
|
78
|
+
# Method to list rules in CATFLAP-ALLOW chain.
|
79
|
+
# @return void
|
80
|
+
# @raise StandardError when iptables reports an error.
|
81
|
+
def list_rules
|
82
|
+
@r.chain(:list, @allow).do
|
83
|
+
end
|
84
|
+
|
85
|
+
# Method to check the CATFLAP-ALLOW chain for an allowed IP.
|
86
|
+
# @return [Boolean] true indicates that IP address already has access.
|
87
|
+
def check_address(ip)
|
88
|
+
@r.rule(:check, src: ip, chain: @allow, jump: 'ACCEPT').do?
|
89
|
+
end
|
90
|
+
|
91
|
+
# Method to add/grant an IP access in the CATFLAP-ALLOW chain.
|
92
|
+
# @return void
|
93
|
+
# @raise StandardError when iptables reports an error.
|
94
|
+
def add_address(ip)
|
95
|
+
@r.rule(:insert, src: ip, chain: @allow, jump: 'ACCEPT').do
|
96
|
+
end
|
97
|
+
|
98
|
+
# Method to delete/revoke access for an IP in the CATFLAP-ALLOW chain.
|
99
|
+
# @return void
|
100
|
+
# @raise StandardError when iptables reports an error.
|
101
|
+
def delete_address(ip)
|
102
|
+
@r.rule(:delete, src: ip, chain: @allow, jump: 'ACCEPT').do
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'catflap/plugins/firewall/plugin'
|
2
|
+
require 'netfilter/writer'
|
3
|
+
include NetfilterWriter
|
4
|
+
|
5
|
+
##
|
6
|
+
# A firewall plugin driver to implement rules on the NetFilter NAT table.
|
7
|
+
#
|
8
|
+
# This driver passes rules to the NAT table via the iptables user-space client.
|
9
|
+
# Two new chains are installed in the NAT table, named by default:
|
10
|
+
# CATFLAP-DENY and CATFLAP-ALLOW. These are installed in the PREROUTING chain.
|
11
|
+
# You can configure the driver to install a LOG to log denied packets.
|
12
|
+
#
|
13
|
+
# This is the default and recommended driver for Linux systems running iptables.
|
14
|
+
#
|
15
|
+
# @example firewall: plugin: 'netfilter'
|
16
|
+
#
|
17
|
+
# @author Nyk Cowham <nykcowham@gmail.com>
|
18
|
+
class NetfilterDriver < FirewallPlugin
|
19
|
+
# Initialize the driver class.
|
20
|
+
# @param [Hash<String, Hash>] config hash: Catflap::initialize_config()
|
21
|
+
# @param [Boolean] noop send the commands to the firewall client.
|
22
|
+
# @param [Boolean] verbose print the command output to stdout stream.
|
23
|
+
# @return void
|
24
|
+
def initialize(config, noop = false, verbose = false)
|
25
|
+
super
|
26
|
+
@chain = config['firewall']['options']['chain'] || 'CATFLAP'
|
27
|
+
@forward = config['firewall']['options']['forward']
|
28
|
+
@log_rejected = config['firewall']['options']['log_rejected'] || false
|
29
|
+
@accept_local = config['firewall']['options']['accept_local'] || false
|
30
|
+
@allow = @chain + '-ALLOW'
|
31
|
+
@deny = @chain + '-DENY'
|
32
|
+
@r = Rules.new(:nat, @dports)
|
33
|
+
@r.match('multiport')
|
34
|
+
@r.noop = noop
|
35
|
+
@r.verbose = verbose
|
36
|
+
end
|
37
|
+
|
38
|
+
# Method to install the driver rules into iptables.
|
39
|
+
# @return void
|
40
|
+
# @raise StandardError when iptables reports an error.
|
41
|
+
def install_rules
|
42
|
+
# We must make these local variables, so they are exposed to the blocks.
|
43
|
+
log = @log_rejected
|
44
|
+
deny_local = !@accept_local
|
45
|
+
|
46
|
+
# Create a new chain on the NAT table for our catflap netfilter allow rules.
|
47
|
+
@r.chain(:new, @allow)
|
48
|
+
.chain(:new, @deny)
|
49
|
+
.rule(:add, chain: 'PREROUTING', jump: @allow)
|
50
|
+
.rule(:add, chain: 'PREROUTING', jump: @deny)
|
51
|
+
.rule(:add, chain: @deny, jump: 'LOG') { log }
|
52
|
+
.rule(:add, chain: 'OUTPUT', out: 'lo', jump: @allow) { deny_local }
|
53
|
+
.rule(:add, chain: 'OUTPUT', out: 'lo', jump: @deny) { deny_local }
|
54
|
+
|
55
|
+
@forward.each do |src, dest|
|
56
|
+
src = src.to_s
|
57
|
+
@r.rule(:add, chain: @deny, jump: 'REDIRECT', dports: src, to_port: dest)
|
58
|
+
end
|
59
|
+
@r.do
|
60
|
+
end
|
61
|
+
|
62
|
+
# Method to uninstall the driver rules from iptables.
|
63
|
+
# @return void
|
64
|
+
# @raise StandardError when iptables reports an error.
|
65
|
+
def uninstall_rules
|
66
|
+
deny_local = !@accept_local
|
67
|
+
|
68
|
+
@r.rule(:delete, chain: 'PREROUTING', jump: @allow)
|
69
|
+
.rule(:delete, chain: 'PREROUTING', jump: @deny)
|
70
|
+
.rule(:delete, chain: 'OUTPUT', out: 'lo',
|
71
|
+
jump: @allow) { deny_local }
|
72
|
+
.rule(:delete, chain: 'OUTPUT', out: 'lo',
|
73
|
+
jump: @deny) { deny_local }
|
74
|
+
.chain(:flush, @allow)
|
75
|
+
.chain(:flush, @deny)
|
76
|
+
.chain(:delete, @allow)
|
77
|
+
.chain(:delete, @deny)
|
78
|
+
.do
|
79
|
+
end
|
80
|
+
|
81
|
+
# Method to purge all rules from CATFLAP-ALLOW chain.
|
82
|
+
# @return void
|
83
|
+
# @raise StandardError when iptables reports an error.
|
84
|
+
def purge_rules
|
85
|
+
@r.chain(:flush, @allow).do
|
86
|
+
end
|
87
|
+
|
88
|
+
# Method to list rules in CATFLAP-ALLOW chain.
|
89
|
+
# @return void
|
90
|
+
# @raise StandardError when iptables reports an error.
|
91
|
+
def list_rules
|
92
|
+
@r.chain(:list, @allow).do
|
93
|
+
end
|
94
|
+
|
95
|
+
# Method to check the CATFLAP-ALLOW chain for an allowed IP.
|
96
|
+
# @return [Boolean] true indicates that IP address already has access.
|
97
|
+
def check_address(ip)
|
98
|
+
@r.rule(:check, src: ip, chain: @allow, jump: 'ACCEPT').do?
|
99
|
+
end
|
100
|
+
|
101
|
+
# Method to add/grant an IP access in the CATFLAP-ALLOW chain.
|
102
|
+
# @return void
|
103
|
+
# @raise StandardError when iptables reports an error.
|
104
|
+
def add_address(ip)
|
105
|
+
@r.rule(:insert, src: ip, chain: @allow, jump: 'ACCEPT').do
|
106
|
+
end
|
107
|
+
|
108
|
+
# Method to delete/revoke access for an IP in the CATFLAP-ALLOW chain.
|
109
|
+
# @return void
|
110
|
+
# @raise StandardError when iptables reports an error.
|
111
|
+
def delete_address(ip)
|
112
|
+
@r.rule(:delete, src: ip, chain: @allow, jump: 'ACCEPT').do
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# Abstract class plugin driver to implement rules on the NetFilter filter table.
|
2
|
+
#
|
3
|
+
# Firewall drivers should inherit from this abstract class and implement each
|
4
|
+
# method. This serves as a contract for firewalls to follow to ensure that the
|
5
|
+
# driver can respond to firewall calls.
|
6
|
+
#
|
7
|
+
# @example firewall: plugin: 'iptables'
|
8
|
+
#
|
9
|
+
# @author Nyk Cowham <nykcowham@gmail.com>
|
10
|
+
class FirewallPlugin
|
11
|
+
attr_reader :dports, :catflap_port
|
12
|
+
|
13
|
+
def initialize(config, noop = false, verbose = false)
|
14
|
+
@noop = noop
|
15
|
+
@verbose = verbose
|
16
|
+
@catflap_port = config['server']['port']
|
17
|
+
@dports = config['firewall']['dports']
|
18
|
+
end
|
19
|
+
|
20
|
+
# Implement method to install the driver rules into iptables.
|
21
|
+
# @return void
|
22
|
+
# @raise StandardError when iptables reports an error.
|
23
|
+
def install_rules
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
# Implement method to uninstall the driver rules from iptables.
|
28
|
+
# @return void
|
29
|
+
# @raise StandardError when iptables reports an error.
|
30
|
+
def uninstall_rules
|
31
|
+
raise NotImplementedError
|
32
|
+
end
|
33
|
+
|
34
|
+
# Implement method to purge all rules from CATFLAP-ALLOW chain.
|
35
|
+
# @return void
|
36
|
+
# @raise StandardError when iptables reports an error.
|
37
|
+
def purge_rules
|
38
|
+
raise NotImplementedError
|
39
|
+
end
|
40
|
+
|
41
|
+
# Implement method to list rules in CATFLAP-ALLOW chain.
|
42
|
+
# @return void
|
43
|
+
# @raise StandardError when iptables reports an error.
|
44
|
+
def list_rules
|
45
|
+
raise NotImplementedError
|
46
|
+
end
|
47
|
+
|
48
|
+
# Implement method to check the CATFLAP-ALLOW chain for an allowed IP.
|
49
|
+
# @return [Boolean] true indicates that IP address already has access.
|
50
|
+
def check_address(_)
|
51
|
+
raise NotImplementedError
|
52
|
+
end
|
53
|
+
|
54
|
+
# Implement method to add/grant an IP access in the CATFLAP-ALLOW chain.
|
55
|
+
# @return void
|
56
|
+
# @raise StandardError when iptables reports an error.
|
57
|
+
def add_address(_)
|
58
|
+
raise NotImplementedError
|
59
|
+
end
|
60
|
+
|
61
|
+
# Implement method to delete/revoke access in the CATFLAP-ALLOW chain.
|
62
|
+
# @return void
|
63
|
+
# @raise StandardError when iptables reports an error.
|
64
|
+
def delete_address(_)
|
65
|
+
raise NotImplementedError
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'catflap/firewall'
|
2
|
+
include Firewall
|
3
|
+
|
4
|
+
# Mixin module to add rule handling functions to netfilter-based drivers.
|
5
|
+
#
|
6
|
+
# @author Nyk Cowham <nykcowham@gmail.com>
|
7
|
+
module NetfilterWriter
|
8
|
+
# Class providing a DSL for defining netfilter rules
|
9
|
+
# @author Nyk Cowham <nykcowham@gmail.com>
|
10
|
+
class Rules
|
11
|
+
attr_accessor :noop, :verbose
|
12
|
+
|
13
|
+
def initialize(table, ports = nil)
|
14
|
+
@table = table
|
15
|
+
@ports = ports
|
16
|
+
@buffer = ''
|
17
|
+
end
|
18
|
+
|
19
|
+
def table(table)
|
20
|
+
@table = table
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def ports(ports)
|
25
|
+
@ports = ports
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def match(match)
|
30
|
+
@match = match
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
# Create, flush and delete chains
|
35
|
+
# @param [String] cmd the operation to perform (add, delete, flush)
|
36
|
+
# @param [String] chain name of the chain (e.g. INPUT, CATFLAP-DENY, etc.)
|
37
|
+
# @return self
|
38
|
+
def chain(cmd, chain, a = {})
|
39
|
+
cmds = {
|
40
|
+
new: '-N', rename: '-E', delete: '-X', flush: '-F',
|
41
|
+
list_rules: '-S', list: '-L', zero: '-Z', policy: '-P'
|
42
|
+
}
|
43
|
+
table = build_option('-t', @table)
|
44
|
+
numeric = build_option('-n', a[:numeric])
|
45
|
+
rulenum = build_option(true, a[:rulenum])
|
46
|
+
to = build_option(true, a[:to])
|
47
|
+
@buffer << [
|
48
|
+
'iptables', table, numeric, cmds[cmd], chain, rulenum, to
|
49
|
+
].compact.join(' ') << "\n"
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
# Create, flush and delete chains
|
54
|
+
# @param [String] cmd the operation to perform (add, delete, insert, etc.)
|
55
|
+
# @param [String] chain name of the chain (e.g. INPUT, CATFLAP-DENY, etc.)
|
56
|
+
# @return self
|
57
|
+
def rule(cmd, a, &block)
|
58
|
+
# Evaluate a block expression and return early if it evaluates to false.
|
59
|
+
# If no block is passed it is equivalent to the block: { true }.
|
60
|
+
return self if block_given? && !instance_eval(&block)
|
61
|
+
|
62
|
+
raise ArgumentError, 'chain is a required argument' unless a[:chain]
|
63
|
+
assert_valid_ipaddr(a[:src]) if a[:src]
|
64
|
+
assert_valid_ipaddr(a[:dst]) if a[:dst]
|
65
|
+
|
66
|
+
# Map of commands for rules
|
67
|
+
cmds = {
|
68
|
+
add: '-A', delete: '-D', insert: '-I', replace: '-R',
|
69
|
+
check: '-C'
|
70
|
+
}
|
71
|
+
|
72
|
+
a[:proto] ||= 'tcp'
|
73
|
+
table = build_option('-t', @table)
|
74
|
+
jump = build_option('-j', a[:jump])
|
75
|
+
goto = build_option('-g', a[:goto])
|
76
|
+
proto = build_option('-p', a[:proto])
|
77
|
+
inface = build_option('-i', a[:in])
|
78
|
+
outface = build_option('-o', a[:out])
|
79
|
+
src = build_option('-s', a[:src])
|
80
|
+
dst = build_option('-d', a[:dst])
|
81
|
+
match = build_option('-m', a[:match] || @match)
|
82
|
+
ports = build_option('--dport', @ports)
|
83
|
+
to_port = build_option('--to-port', a[:to_port])
|
84
|
+
@buffer << [
|
85
|
+
'iptables', table, cmds[cmd], a[:chain], src, dst, outface,
|
86
|
+
inface, proto, match, ports, jump || goto, to_port
|
87
|
+
].compact.join(' ') << "\n"
|
88
|
+
self
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def build_option(flag, value)
|
93
|
+
return flag if value.is_a?(TrueClass)
|
94
|
+
return value if flag.is_a?(TrueClass)
|
95
|
+
return flag << ' ' << value.to_s if flag && value
|
96
|
+
end
|
97
|
+
|
98
|
+
# Add a raw text rule, (e.g.: iptables -t nat CATFLAP-ALLOW ...)
|
99
|
+
# @param [String] raw_rule custom raw iptables command.
|
100
|
+
# @return self
|
101
|
+
def raw(raw_rule)
|
102
|
+
@buffer = raw_rule
|
103
|
+
self
|
104
|
+
end
|
105
|
+
|
106
|
+
# Flush the rule buffer and output the resulting iptables commands.
|
107
|
+
# @return [String] rule text that can be sent iptables user-space client.
|
108
|
+
def flush
|
109
|
+
out = @buffer
|
110
|
+
@buffer = ''
|
111
|
+
out
|
112
|
+
end
|
113
|
+
|
114
|
+
# Flush the rule and execute in iptables user-space client.
|
115
|
+
# @return void
|
116
|
+
def do
|
117
|
+
execute flush
|
118
|
+
end
|
119
|
+
|
120
|
+
# Flush the rule and execute commands and return success/fail value.
|
121
|
+
# @return [Boolean] true if the execution was successful.
|
122
|
+
def do?
|
123
|
+
execute_true? flush
|
124
|
+
end
|
125
|
+
end
|
data/ui/css/catflap.css
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
|
2
|
+
.centered {
|
3
|
+
margin: 0px auto;
|
4
|
+
display: block;
|
5
|
+
}
|
6
|
+
|
7
|
+
.hidden {
|
8
|
+
display: none;
|
9
|
+
}
|
10
|
+
|
11
|
+
input[type="text"]:focus.failed {
|
12
|
+
background-color: pink;
|
13
|
+
border-color: red;
|
14
|
+
color: black;
|
15
|
+
}
|
16
|
+
|
17
|
+
input[type="text"] {
|
18
|
+
display: block;
|
19
|
+
margin: 0px auto;
|
20
|
+
margin-top: 1em;
|
21
|
+
padding-left: 0.4em;
|
22
|
+
height: 1.7em;
|
23
|
+
width: 50%;
|
24
|
+
color: #999;
|
25
|
+
font-family: sans-serif;
|
26
|
+
font-size: 2.2em;
|
27
|
+
border: solid 2px #dcdcdc;
|
28
|
+
transition: box-shadow 0.3s, border 0.3s;
|
29
|
+
box-shadow: inset 1px 1px 2px 0 #707070;
|
30
|
+
appearance: none;
|
31
|
+
border-radius: 2px;
|
32
|
+
}
|
33
|
+
|
34
|
+
input[type="text"]:focus {
|
35
|
+
border: solid 2px #707070;
|
36
|
+
box-shadow: inset 1px 1px 2px 0 #c9c9c9;
|
37
|
+
}
|
38
|
+
|
39
|
+
.message {
|
40
|
+
width: 50%;
|
41
|
+
font-size: 2em;
|
42
|
+
margin-top: 1.5em;
|
43
|
+
color: #860000;
|
44
|
+
}
|