catflap 0.0.1.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/bin/catflap +70 -0
  2. data/lib/catflap-http.rb +108 -0
  3. data/lib/catflap.rb +75 -0
  4. 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
+
@@ -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: []