catflap 0.0.1.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/catflap +70 -0
- data/lib/catflap-http.rb +108 -0
- data/lib/catflap.rb +75 -0
- metadata +52 -0
data/bin/catflap
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'catflap'
|
5
|
+
|
6
|
+
options = {}
|
7
|
+
optparse = OptionParser.new do|opts|
|
8
|
+
opts.separator ""
|
9
|
+
opts.separator "Install/Configure/Service options:"
|
10
|
+
opts.on('-i', '--install', 'Install and initialize the catflap chain') do
|
11
|
+
options['install'] = true
|
12
|
+
end
|
13
|
+
opts.on('-u', '--uninstall', 'Uninstall catflap from iptables') do
|
14
|
+
options['uninstall'] = true
|
15
|
+
end
|
16
|
+
opts.on('-f', '--config-file <filepath>', String, 'Use config file to override default values') do |filepath|
|
17
|
+
options['config_file'] = true
|
18
|
+
end
|
19
|
+
opts.on('-s', '--start-server', 'Start the web api server daemon') do
|
20
|
+
options['start_server'] = true
|
21
|
+
end
|
22
|
+
opts.separator ""
|
23
|
+
opts.separator "Access rule control options:"
|
24
|
+
opts.on('-c', '--check <ipaddr>', String, 'Check if an IP address has access alreqdy') do |ip|
|
25
|
+
options['check'] = ip
|
26
|
+
end
|
27
|
+
opts.on('-a', '--add <ipaddr>', String, 'IP address to which access should be granted') do |ip|
|
28
|
+
options['add'] = ip
|
29
|
+
end
|
30
|
+
opts.on('-d', '--delete <ipaddr>', String, 'IP address or range to remove access previously granted') do |ip|
|
31
|
+
options['del'] = ip
|
32
|
+
end
|
33
|
+
opts.on('-F', '--file <filepath>', String, 'Input file of whitelisted IP addresses') do |filepath|
|
34
|
+
options['filepath'] = filepath
|
35
|
+
end
|
36
|
+
opts.on('-x', '--purge', 'Purge all catflap rules from iptables') do
|
37
|
+
options['purge'] = true
|
38
|
+
end
|
39
|
+
opts.on('-l', '--list', 'List catflap access rules') do
|
40
|
+
options['list'] = true
|
41
|
+
end
|
42
|
+
opts.separator ""
|
43
|
+
opts.separator "Output and process options:"
|
44
|
+
opts.on('-n', '--noop', 'Do not run the operation on iptables - no operation') do
|
45
|
+
cf.noop = true
|
46
|
+
end
|
47
|
+
opts.on('-p', '--print', 'Print the iptables generated to screen') do
|
48
|
+
cf.print = true
|
49
|
+
end
|
50
|
+
opts.on('-h', '--help', 'Print this help page.') do
|
51
|
+
puts opts
|
52
|
+
exit 0
|
53
|
+
end
|
54
|
+
end.parse! ARGV
|
55
|
+
|
56
|
+
unless options['start_server']
|
57
|
+
cf = Catflap.new(options['config_file'])
|
58
|
+
cf.purge_rules! if options['purge']
|
59
|
+
cf.install_rules! if options['install']
|
60
|
+
cf.uninstall_rules! if options['uninstall']
|
61
|
+
cf.add_address!(options['add']) if options['add']
|
62
|
+
cf.delete_address!(options['del']) if options['del']
|
63
|
+
cf.add_addresses_from_file!(options['filepath']) if options['filepath']
|
64
|
+
cf.check_address(options['check']) if options['check']
|
65
|
+
cf.list_rules if options['list']
|
66
|
+
else
|
67
|
+
require 'catflap-http'
|
68
|
+
CatflapWebserver::start_server(options['config_file'])
|
69
|
+
end
|
70
|
+
|
data/lib/catflap-http.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'catflap'
|
4
|
+
require 'webrick'
|
5
|
+
include WEBrick
|
6
|
+
|
7
|
+
module CatflapWebserver
|
8
|
+
|
9
|
+
def self.generate_server(port)
|
10
|
+
config = {:Port => port}
|
11
|
+
server = HTTPServer.new(config)
|
12
|
+
yield server if block_given?
|
13
|
+
['INT', 'TERM'].each {|signal|
|
14
|
+
trap(signal) {server.shutdown}
|
15
|
+
}
|
16
|
+
server.start
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.start_server(port = 4777)
|
20
|
+
generate_server(port) do |server|
|
21
|
+
server.mount('/', Servlet)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Servlet < HTTPServlet::AbstractServlet
|
26
|
+
def do_GET(req,resp)
|
27
|
+
# Split the path into piece
|
28
|
+
path = req.path[1..-1].split('/')
|
29
|
+
raise HTTPStatus::OK if path[0] == 'favicon.ico'
|
30
|
+
response_class = CatflapRestService.const_get("Service")
|
31
|
+
|
32
|
+
if response_class and response_class.is_a?(Class)
|
33
|
+
# There was a method given
|
34
|
+
if path[0]
|
35
|
+
response_method = path[0].to_sym
|
36
|
+
# Make sure the method exists in the class
|
37
|
+
raise HTTPStatus::NotFound if !response_class.respond_to?(response_method)
|
38
|
+
|
39
|
+
if path[0] == "enter"
|
40
|
+
url = response_class.send(response_method, req, resp)
|
41
|
+
resp.set_redirect(HTTPStatus::Redirect, url)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Remaining path segments get passed in as arguments to the method
|
45
|
+
if path.length > 1
|
46
|
+
resp.body = response_class.send(response_method, req, resp, path[1..-1])
|
47
|
+
else
|
48
|
+
resp.body = response_class.send(response_method, req, resp)
|
49
|
+
end
|
50
|
+
raise HTTPStatus::OK
|
51
|
+
|
52
|
+
# No method was given, so check for an "index" method instead
|
53
|
+
else
|
54
|
+
raise HTTPStatus::NotFound if !response_class.respond_to?(:index)
|
55
|
+
resp.body = response_class.send(:index)
|
56
|
+
raise HTTPStatus::OK
|
57
|
+
end
|
58
|
+
else
|
59
|
+
raise HTTPStatus::NotFound
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
module CatflapRestService
|
66
|
+
class Service
|
67
|
+
|
68
|
+
@@cf = Catflap.new
|
69
|
+
@@cf.dports = '80,8080,443'
|
70
|
+
|
71
|
+
def self.index()
|
72
|
+
return "hello world"
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.enter(req, resp)
|
76
|
+
ip = req.peeraddr.pop
|
77
|
+
host = req.addr[2]
|
78
|
+
@@cf.add_address!(ip) unless @@cf.check_address(ip)
|
79
|
+
return "http://" << host << ":80"
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.add(req, resp, args)
|
83
|
+
ip = args[0]
|
84
|
+
unless @@cf.check_address(ip)
|
85
|
+
@@cf.add_address!(ip)
|
86
|
+
return "#{ip} has been granted access"
|
87
|
+
else
|
88
|
+
return "#{ip} already has access"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.remove(req, resp, args)
|
93
|
+
ip = args[0]
|
94
|
+
@@cf.delete_address!(ip)
|
95
|
+
return "Access granted to #{ip} has been removed"
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.check(req, resp, args)
|
99
|
+
ip = args[0]
|
100
|
+
|
101
|
+
if @@cf.check_address(ip)
|
102
|
+
return "#{ip} has access to ports: #{@@cf.dports}"
|
103
|
+
else
|
104
|
+
return "#{ip} does not have access to ports: #{@@cf.dports}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
data/lib/catflap.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
class Catflap
|
4
|
+
|
5
|
+
attr_accessor :chain, :dports, :print, :noop, :log_rejected
|
6
|
+
|
7
|
+
def initialize(config_file = nil)
|
8
|
+
@config = YAML.load_file(config_file) if config_file and File.exists?(config_file)
|
9
|
+
@chain = (@config['rules']['chain']) ? @config['rules']['chain'] : "catflap-accept"
|
10
|
+
@dports = (@config['rules']['dports']) ? @config['rules']['dports'] : "80,443"
|
11
|
+
@print = false
|
12
|
+
@noop = false
|
13
|
+
@log_rejected = true
|
14
|
+
end
|
15
|
+
|
16
|
+
def install_rules!
|
17
|
+
output = "iptables -N #{@chain}\n" # Create a new user-defined chain as a container for our catflap netfilter rules
|
18
|
+
output << "iptables -A #{@chain} -s 127.0.0.1 -p tcp -m multiport --dports #{@dports} -j ACCEPT\n" # Accept packets to localhost
|
19
|
+
output << "iptables -A INPUT -p tcp -m multiport --dports #{@dports} -j #{@chain}\n" # Jump from INPUT chain to the catflap chain
|
20
|
+
output << "iptables -A INPUT -p tcp -m multiport --dports #{@dports} -j LOG\n" if @log_rejected # Log any rejected packets to /var/log/messages
|
21
|
+
output << "iptables -A INPUT -p tcp -m multiport --dports #{@dports} -j DROP\n" # Drop any other packets to the ports on the INPUT chain
|
22
|
+
execute!(output)
|
23
|
+
end
|
24
|
+
|
25
|
+
def uninstall_rules!
|
26
|
+
output = "iptables -D INPUT -p tcp -m multiport --dports #{@dports} -j #{@chain}\n" # Remove user-defined chain from INPUT chain
|
27
|
+
output << "iptables -F #{@chain}\n" # Flush the catflap user-defined chain
|
28
|
+
output << "iptables -X #{@chain}\n" # Remove the catflap chain
|
29
|
+
output << "iptables -D INPUT -p tcp -m multiport --dports #{@dports} -j LOG\n" # Remove the logging rule
|
30
|
+
output << "iptables -D INPUT -p tcp -m multiport --dports #{@dports} -j DROP\n" # Remove the packet dropping rule
|
31
|
+
execute!(output)
|
32
|
+
end
|
33
|
+
|
34
|
+
def purge_rules!
|
35
|
+
output = "iptables -F #{@chain}"
|
36
|
+
execute!(output)
|
37
|
+
end
|
38
|
+
|
39
|
+
def list_rules
|
40
|
+
system "iptables -S #{@chain}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def check_address(ip)
|
44
|
+
return system "iptables -C #{@chain} -s #{ip} -p tcp -m multiport --dports #{@dports} -j ACCEPT\n"
|
45
|
+
end
|
46
|
+
|
47
|
+
def add_address!(ip)
|
48
|
+
output = "iptables -I #{@chain} 1 -s #{ip} -p tcp -m multiport --dports #{@dports} -j ACCEPT\n"
|
49
|
+
execute!(output)
|
50
|
+
end
|
51
|
+
|
52
|
+
def delete_address!(ip)
|
53
|
+
output = "iptables -D #{@chain} -s #{ip} -p tcp -m multiport --dports #{@dports} -j ACCEPT\n"
|
54
|
+
execute!(output)
|
55
|
+
end
|
56
|
+
|
57
|
+
def add_addresses_from_file!(filepath)
|
58
|
+
if File.readable?(filepath)
|
59
|
+
output = ""
|
60
|
+
File.open(filepath, "r").each_line do |ip|
|
61
|
+
output << "iptables -I #{@chain} 1 -s #{ip.chomp} -p tcp -m multiport --dports #{@dports} -j ACCEPT\n"
|
62
|
+
end
|
63
|
+
execute!(output)
|
64
|
+
else
|
65
|
+
puts "The file #{filepath} is not readable!"
|
66
|
+
exit 1
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def execute!(output)
|
71
|
+
if @print then puts output end
|
72
|
+
system output unless @noop
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
metadata
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: catflap
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.pre
|
5
|
+
prerelease: 6
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Nyk Cowham
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-12-01 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: A simple solution for service access (e.g. port 80 on webserver) where
|
15
|
+
a more robust and secure VPN solution is not available
|
16
|
+
email: nyk@demotix.com
|
17
|
+
executables:
|
18
|
+
- catflap
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- lib/catflap.rb
|
23
|
+
- lib/catflap-http.rb
|
24
|
+
- bin/catflap
|
25
|
+
homepage: http://rubygems.org/gems/catflap
|
26
|
+
licenses:
|
27
|
+
- MIT
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options: []
|
30
|
+
require_paths:
|
31
|
+
- lib
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>'
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.3.1
|
44
|
+
requirements:
|
45
|
+
- NetFilters (iptables) installed and working
|
46
|
+
rubyforge_project:
|
47
|
+
rubygems_version: 1.8.11
|
48
|
+
signing_key:
|
49
|
+
specification_version: 3
|
50
|
+
summary: Manage NetFilter-based rules to grant port access on demand via commandline
|
51
|
+
or REST API requests
|
52
|
+
test_files: []
|