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
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'catflap'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
require 'pry'
|
11
|
+
Pry.start
|
12
|
+
|
13
|
+
require 'irb'
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/catflap.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'catflap/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = 'catflap'
|
8
|
+
s.version = Catflap::VERSION
|
9
|
+
s.summary = 'Manage NetFilter-based rules to grant port access on-demand ' \
|
10
|
+
'commandline or REST API requests.'
|
11
|
+
s.description = 'A simple solution to provide on-demand service access (e.g.
|
12
|
+
port 80 on webserver), where a more robust and secure VPN solution is not
|
13
|
+
available. Essentially, it is a more user-friendly form of "port knocking".
|
14
|
+
The original proof-of-concept implementation was run for almost three years
|
15
|
+
by Demotix, to protect development and staging servers from search engine
|
16
|
+
crawlers and other unwanted traffic.'
|
17
|
+
s.authors = ['Nyk Cowham']
|
18
|
+
s.email = 'nykcowham@gmail.com'
|
19
|
+
s.homepage = 'https://github.com/nyk/catflap'
|
20
|
+
s.files = `git ls-files -z`
|
21
|
+
.split("\x0")
|
22
|
+
.reject { |f| f.match(%r{^(test|spec|tasks|features)/}) }
|
23
|
+
s.executables = ['catflap']
|
24
|
+
s.licenses = ['MIT']
|
25
|
+
s.requirements = 'NetFilters (iptables) installed and working.'
|
26
|
+
s.require_paths = ['lib']
|
27
|
+
s.add_dependency 'json', '>= 1.8.3'
|
28
|
+
s.add_development_dependency 'bundler', '~> 1.11'
|
29
|
+
s.add_development_dependency 'rake', '~> 10.0'
|
30
|
+
s.add_development_dependency 'rspec', '~> 3.0'
|
31
|
+
s.bindir = 'bin'
|
32
|
+
end
|
data/etc/config.yaml
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# This is an example of the main configuration file for catflap.
|
2
|
+
# By default catflap looks for this file in /usr/local/etc/catflap/config.yaml
|
3
|
+
# but you can change the location by using the --config-file <filepath> option
|
4
|
+
# of the bin/catflap command line interface.
|
5
|
+
|
6
|
+
server:
|
7
|
+
listen_addr: '0.0.0.0' # what ip address the catflap server should listen on.
|
8
|
+
port: 8778 # the TCP port that the catflap server listens to.
|
9
|
+
docroot: './ui' # you can override the ui location.
|
10
|
+
endpoint: '/catflap' # the endpoint for the REST API.
|
11
|
+
passfile: './etc/passfile.yaml' # pass phrases are stored here.
|
12
|
+
token_ttl: 15 # expire tokens after 15 seconds.
|
13
|
+
pid_path: '/var/run' # The path where the pid file should be written.
|
14
|
+
|
15
|
+
https:
|
16
|
+
port: 4773 # TCP oport that catflap https server listens to.
|
17
|
+
force: true # Force HTTP requests to redirect to HTTPS.
|
18
|
+
certificate: '' # Path to your SSL certificate file.
|
19
|
+
private_key: '' # Private key for your SSL certificate.
|
20
|
+
|
21
|
+
firewall:
|
22
|
+
plugin: 'netfilter' # options are netfilter or iptables
|
23
|
+
dports: '80,443' # lock multiple ports separating them by commas.
|
24
|
+
options: # options are specific to each firewall plugin driver.
|
25
|
+
chain: 'CATFLAP' # Namespace for the chains (e.g. CATFLAP-ALLOW, CATFLAP-DENY).
|
26
|
+
forward:
|
27
|
+
80: 8778
|
28
|
+
443: 4773
|
29
|
+
log_rejected: true
|
30
|
+
accept_local: true # this is set to false only when devs are testing catflap.
|
data/etc/init.d/catflap
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
### BEGIN INIT INFO
|
2
|
+
# Provides: catflap
|
3
|
+
# Default-Start: 2 3 4 5
|
4
|
+
# Default-Stop: 0 1 6
|
5
|
+
# Short-Description: Catflap daemon to provide API web service.
|
6
|
+
# Description: Provides a means to request and gain access
|
7
|
+
# to certain restricted ports like 80 and 443
|
8
|
+
### END INIT INFO
|
9
|
+
|
10
|
+
# Using the lsb functions to perform the operations.
|
11
|
+
. /lib/lsb/init-functions
|
12
|
+
# Process name ( For display )
|
13
|
+
NAME=catflap
|
14
|
+
# Daemon name, where is the actual executable
|
15
|
+
DAEMON=/usr/local/bin/catflap
|
16
|
+
|
17
|
+
# pid file for the daemon
|
18
|
+
PIDFILE=/var/run/catflap.pid
|
19
|
+
|
20
|
+
# Include catflap defaults if available
|
21
|
+
if [ -f /etc/default/catflap ] ; then
|
22
|
+
. /etc/default/catflap
|
23
|
+
fi
|
24
|
+
|
25
|
+
# If the daemon is not there, then exit.
|
26
|
+
test -x $DAEMON || exit 5
|
27
|
+
|
28
|
+
case $1 in
|
29
|
+
start)
|
30
|
+
# Checked the PID file exists and check the actual status of process
|
31
|
+
if [ -e $PIDFILE ]; then
|
32
|
+
status_of_proc -p $PIDFILE $DAEMON "$NAME process" && status="0" || status="$?"
|
33
|
+
# If the status is SUCCESS then don't need to start again.
|
34
|
+
if [ $status = "0" ]; then
|
35
|
+
exit # Exit
|
36
|
+
fi
|
37
|
+
fi
|
38
|
+
# Start the daemon.
|
39
|
+
log_daemon_msg "Starting the process" "$NAME"
|
40
|
+
# Start the daemon with the help of start-stop-daemon
|
41
|
+
# Log the message appropriately
|
42
|
+
if start-stop-daemon --start --quiet --oknodo --background --make-pidfile --pidfile $PIDFILE --exec $DAEMON -- $DAEMON_OPTS " start" 2>&1; then
|
43
|
+
log_end_msg 0
|
44
|
+
else
|
45
|
+
log_end_msg 1
|
46
|
+
fi
|
47
|
+
;;
|
48
|
+
stop)
|
49
|
+
# Stop the daemon.
|
50
|
+
if [ -e $PIDFILE ]; then
|
51
|
+
status_of_proc -p $PIDFILE $DAEMON "Stoppping the $NAME process" && status="0" || status="$?"
|
52
|
+
if [ "$status" = 0 ]; then
|
53
|
+
start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE
|
54
|
+
/bin/rm -rf $PIDFILE
|
55
|
+
fi
|
56
|
+
else
|
57
|
+
log_daemon_msg "$NAME process is not running"
|
58
|
+
log_end_msg 0
|
59
|
+
fi
|
60
|
+
;;
|
61
|
+
restart)
|
62
|
+
# Restart the daemon.
|
63
|
+
$0 stop && sleep 2 && $0 start
|
64
|
+
;;
|
65
|
+
status)
|
66
|
+
# Check the status of the process.
|
67
|
+
if [ -e $PIDFILE ]; then
|
68
|
+
status_of_proc -p $PIDFILE $DAEMON "$NAME process" && exit 0 || exit $?
|
69
|
+
else
|
70
|
+
log_daemon_msg "$NAME Process is not running"
|
71
|
+
log_end_msg 0
|
72
|
+
fi
|
73
|
+
;;
|
74
|
+
reload)
|
75
|
+
# Reload the process. Basically sending some signal to a daemon to reload
|
76
|
+
# it configurations.
|
77
|
+
if [ -e $PIDFILE ]; then
|
78
|
+
start-stop-daemon --stop --signal USR1 --quiet --pidfile $PIDFILE --name $NAME
|
79
|
+
log_success_msg "$NAME process reloaded successfully"
|
80
|
+
else
|
81
|
+
log_failure_msg "$PIDFILE does not exists"
|
82
|
+
fi
|
83
|
+
;;
|
84
|
+
*)
|
85
|
+
# For invalid arguments, print the usage message.
|
86
|
+
echo "Usage: $0 {start|stop|restart|reload|status}"
|
87
|
+
exit 2
|
88
|
+
;;
|
89
|
+
esac
|
data/etc/passfile.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# This is a sample passfile. It is recommened that you use your own
|
2
|
+
# pass phrases. A pass phrase must consist of at least two words separated by
|
3
|
+
# a space. The first word of the phrase is used as a unique key for efficient
|
4
|
+
# lookup of multiple pass phrases.
|
5
|
+
passphrases:
|
6
|
+
frisky: 'frisky kitten'
|
7
|
+
peeky: 'peeky blinders'
|
data/lib/catflap.rb
CHANGED
@@ -1,87 +1,131 @@
|
|
1
|
+
require 'catflap/version'
|
1
2
|
require 'yaml'
|
2
|
-
require '
|
3
|
+
require 'digest'
|
3
4
|
|
5
|
+
##
|
6
|
+
# Catflap controller class
|
7
|
+
#
|
8
|
+
# This class controls the initialization of the configuration file, setting
|
9
|
+
# options, initializing the firewall plugin driver and loading pass phrases.
|
10
|
+
#
|
11
|
+
# @author Nyk Cowham <nykcowham@gmail.com>
|
4
12
|
class Catflap
|
13
|
+
# @!attribute noop
|
14
|
+
# @return [Boolean] true if no operation is to be performed.
|
15
|
+
# @!attribute verbose
|
16
|
+
# @return [Boolean] true if command output should be written to stdout.
|
17
|
+
# @!attribute [r] fwplugin
|
18
|
+
# @return [String] the name of the firewall driver plugin file and class.
|
19
|
+
# @!attribute [r] bind_addr
|
20
|
+
# @return [String] the IP address the Catflap server should listen to.
|
21
|
+
# @!attribute [r] port
|
22
|
+
# @return [Integer] the port number the Catflap server should listen to.
|
23
|
+
# @!attribute [r] docroot
|
24
|
+
# @return [String] file path location that HTML/CSS/JS files are located.
|
25
|
+
# @!attribute [r] endpoint
|
26
|
+
# @return [String] the name of the REST service endpoint (e.g. /catflap).
|
27
|
+
# @!attribute [r] https
|
28
|
+
# @return [Hash<Symbol, String>] the HTTPS server configuration options.
|
29
|
+
# @!attribute [r] daemonize
|
30
|
+
# @return [Boolean] true if the web server should run in the background.
|
31
|
+
# @!attribute [r] dports
|
32
|
+
# @return [String] comma-separated list of ports to guard. (e.g. '80,443').
|
33
|
+
# @!attribute [r] passphrases
|
34
|
+
# @return [Hash<String, String>] associative array of pass phrases. The
|
35
|
+
# key is the first word of the pass phrase where words are separated by
|
36
|
+
# a space or non-alphanumeric character (except for the underscore).
|
37
|
+
# @!attribute [r] passfile
|
38
|
+
# @return [String] file path of the YAML file containg pass phrases.
|
39
|
+
# @!attribute [r] token_ttl
|
40
|
+
# @return [Integer] the time-to-live in seconds before tokens expire.
|
41
|
+
# @!attribute [r] pid_path
|
42
|
+
# @return [String] path/directory where pid file should be written.
|
43
|
+
# @!attribute [r] redirect_url
|
44
|
+
# @return [String] URL browser should be redirect to after authentication.
|
45
|
+
# @!attribute [r] firewall
|
46
|
+
# @return [FirewallPlugin]
|
47
|
+
# a firewall object that is implemented by the firewall driver plugin.
|
5
48
|
|
6
|
-
attr_accessor :
|
49
|
+
attr_accessor :verbose, :noop, :daemonize
|
7
50
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
@config = YAML.load_file config_file if File.readable? config_file
|
12
|
-
@port = @config['server']['port'] || 4777
|
13
|
-
@chain = @config['rules']['chain'] || 'catflap-accept'
|
14
|
-
@dports = @config['rules']['dports'] || '80,443'
|
15
|
-
@print = false
|
16
|
-
@noop = false
|
17
|
-
@log_rejected = true
|
18
|
-
end
|
51
|
+
attr_reader :fwplugin, :listen_addr, :port, :docroot, :endpoint, :dports,
|
52
|
+
:passfile, :passphrases, :token_ttl, :pid_path, :redirect_url,
|
53
|
+
:firewall, :https
|
19
54
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
55
|
+
# @param [String, nil] file_path file path of the YAML configuration file.
|
56
|
+
# @param [Boolean] noop set to true to suppress destructive operations.
|
57
|
+
# @param [Boolean] verbose set to true to print operations to stdout stream.
|
58
|
+
# @return [Catflap]
|
59
|
+
def initialize(file_path = nil, noop = false, verbose = false)
|
60
|
+
@noop = noop
|
61
|
+
@verbose = verbose
|
62
|
+
initialize_config(file_path)
|
63
|
+
initialize_firewall_plugin
|
64
|
+
load_passphrases
|
27
65
|
end
|
28
66
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
execute! output
|
36
|
-
end
|
67
|
+
# Initialize the configuration options from the YAML configuration file.
|
68
|
+
# @param [String, nil] file_path file path of the YAML configuration file.
|
69
|
+
# @return void
|
70
|
+
def initialize_config(file_path = nil)
|
71
|
+
@config = read_config_file(file_path)
|
72
|
+
alias_server_attributes
|
37
73
|
|
38
|
-
|
39
|
-
output = "iptables -F #{@chain}"
|
40
|
-
execute! output
|
74
|
+
@https ||= {}
|
41
75
|
end
|
42
76
|
|
43
|
-
|
44
|
-
|
77
|
+
# Alias the server configuration attributes to instance variables.
|
78
|
+
def alias_server_attributes
|
79
|
+
@config['server'].each do |key, value|
|
80
|
+
instance_variable_set('@' + key, value)
|
81
|
+
end
|
45
82
|
end
|
46
83
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
84
|
+
# Find YAML configuration file, load and read it.
|
85
|
+
# @param [String, nil] file_path if one was passed on command line.
|
86
|
+
# @return [Hash] the parsed YAML file as a nested hash.
|
87
|
+
def read_config_file(file_path)
|
88
|
+
# Look for config file in order of precedence.
|
89
|
+
unless file_path
|
90
|
+
['~/.catflap', '/usr/local/etc/catflap', '/etc/catflap'].each do |path|
|
91
|
+
file = File.expand_path(path + '/config.yaml')
|
92
|
+
if File.readable? file
|
93
|
+
file_path = file
|
94
|
+
break
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
51
98
|
|
52
|
-
|
53
|
-
check_user_input ip
|
54
|
-
output = "iptables -I #{@chain} 1 -s #{ip} -p tcp -m multiport --dports #{@dports} -j ACCEPT\n"
|
55
|
-
execute! output
|
99
|
+
YAML.load_file(file_path || './etc/config.yaml')
|
56
100
|
end
|
57
101
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
102
|
+
# Initialize the firewall plugin driver.
|
103
|
+
# @return [FirewallPlugin] an object of a class inheriting from FirewallPlugin
|
104
|
+
def initialize_firewall_plugin
|
105
|
+
plugin = @config['firewall']['plugin']
|
106
|
+
driver = plugin.capitalize + 'Driver'
|
107
|
+
require_relative "catflap/plugins/firewall/#{plugin}.rb"
|
108
|
+
@firewall =
|
109
|
+
Object.const_get(driver).new(@config, @noop, @verbose)
|
110
|
+
end
|
63
111
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
execute! output
|
112
|
+
# Load the pass phrase YAML file.
|
113
|
+
# @raise [IOError] if the file is missing or not readable.
|
114
|
+
# @return void
|
115
|
+
def load_passphrases
|
116
|
+
if @passfile && File.readable?(@passfile)
|
117
|
+
phrases = YAML.load_file(@passfile)
|
118
|
+
@passphrases = phrases['passphrases']
|
72
119
|
else
|
73
|
-
|
74
|
-
exit 1
|
120
|
+
raise IOError, "Cannot read the passfile: #{@passfile}!"
|
75
121
|
end
|
76
122
|
end
|
77
123
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
return IPAddr.new(suspect)
|
124
|
+
# Generates a SHA256 encrypted token based on a passphrase and a timestamp
|
125
|
+
# @param [String] pass the passphrase stored in passfile.
|
126
|
+
# @param [String] timestamp to salt the digest.
|
127
|
+
# @return [String] a token in the form of a SHA256 digest of a string.
|
128
|
+
def generate_token(pass, salt)
|
129
|
+
Digest::SHA256.hexdigest(pass + salt)
|
85
130
|
end
|
86
|
-
|
87
131
|
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'catflap'
|
2
|
+
require 'catflap/http'
|
3
|
+
|
4
|
+
##
|
5
|
+
# Command controller class.
|
6
|
+
#
|
7
|
+
# This class separates the implementation details of the command line
|
8
|
+
# interface from the actual interface. This allows for creating custom
|
9
|
+
# command tools by directly implementing this class.
|
10
|
+
# @author Nyk Cowham <nykcowham@gmail.com>
|
11
|
+
class CfCommand
|
12
|
+
include CfWebserver
|
13
|
+
|
14
|
+
# Initialize a new CatflapCli object
|
15
|
+
# @param [Hash<String, String>] options an associative array of options
|
16
|
+
# read from the configuration file built by Catflap::initialize_config().
|
17
|
+
# @return CatflapCli
|
18
|
+
# @see
|
19
|
+
# Catflap - options are generated from file: Catflap::initialize_config()
|
20
|
+
def initialize(options)
|
21
|
+
@options = options
|
22
|
+
@cf = Catflap.new(
|
23
|
+
options[:config_file], options[:noop], options[:verbose]
|
24
|
+
)
|
25
|
+
@cf.daemonize = @options[:daemonize]
|
26
|
+
end
|
27
|
+
|
28
|
+
# A handler function to dispatch commands received from the front-end to the
|
29
|
+
# firewall driver class or the Catflap web service.
|
30
|
+
# @param [String] command the command that is to be executed.
|
31
|
+
# @param [String] arg and argument for the command, (e.g. an IP address).
|
32
|
+
# @return void
|
33
|
+
# @raise ArgumentError when a required command argument is missing.
|
34
|
+
# @raise NameError when the command is not recognized.
|
35
|
+
def dispatch_commands(command, arg)
|
36
|
+
# handle commands and options.
|
37
|
+
case command
|
38
|
+
when 'version'
|
39
|
+
"Catflap version #{Catflap::VERSION}"
|
40
|
+
when 'start'
|
41
|
+
server_start(@cf, @options[:https])
|
42
|
+
when 'stop'
|
43
|
+
server_stop(@cf, @options[:https])
|
44
|
+
when 'status'
|
45
|
+
server_status(@cf, @options[:https])
|
46
|
+
when 'restart'
|
47
|
+
begin
|
48
|
+
server_stop(@cf, @options[:https])
|
49
|
+
server_start(@cf, @options[:https])
|
50
|
+
end
|
51
|
+
when 'reload'
|
52
|
+
@cf.load_passphrases
|
53
|
+
when 'purge'
|
54
|
+
@cf.firewall.purge_rules
|
55
|
+
when 'install'
|
56
|
+
@cf.firewall.install_rules
|
57
|
+
when 'uninstall'
|
58
|
+
@cf.firewall.uninstall_rules
|
59
|
+
when 'list'
|
60
|
+
puts @cf.firewall.list_rules
|
61
|
+
when 'grant'
|
62
|
+
raise ArgumentError, 'You must provide a valid IP address' if arg.nil?
|
63
|
+
@cf.firewall.add_address(arg) unless @cf.firewall.check_address(arg)
|
64
|
+
when 'revoke'
|
65
|
+
raise ArgumentError, 'You must provide a valid IP address' if arg.nil?
|
66
|
+
@cf.firewall.delete_address(arg) if @cf.firewall.check_address(arg)
|
67
|
+
when 'check'
|
68
|
+
raise ArgumentError, 'You must provide a valid IP address' unless arg
|
69
|
+
return @cf.firewall.check_address(arg)
|
70
|
+
when 'bulkload'
|
71
|
+
raise ArgumentError, 'You must provide a file path' unless arg
|
72
|
+
add_addresses_from_file(arg)
|
73
|
+
when nil # catflap --version can be run with no command, so that's ok.
|
74
|
+
else
|
75
|
+
raise NameError, "there is no command '#{command}'"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
# rubocop:enable Metrics/CyclomaticComplexity,Metrics/MethodLength
|
79
|
+
# rubocop:enable Metrics/PerceivedComplexity,Metrics/AbcSize
|
80
|
+
|
81
|
+
# Handler function to bulkload IP's to the firewall.
|
82
|
+
#
|
83
|
+
# Checking that the file path points to readable file ensures that we can
|
84
|
+
# safely accept the user-submitted parameter without any additional data
|
85
|
+
# sanitization.
|
86
|
+
# @param [String] filepath path to the bulkload file of IP addresses to add.
|
87
|
+
# @return void
|
88
|
+
# @raise IOError if the file cannot be found or is not readable.
|
89
|
+
#
|
90
|
+
# @note Every IP address in the file is validated to ensure that it resolves
|
91
|
+
# to a valid IP address.
|
92
|
+
# @see Firewall Firewall::assert_valid_ipaddr(ip)
|
93
|
+
def add_addresses_from_file(filepath)
|
94
|
+
if File.readable? filepath
|
95
|
+
File.open(filepath, 'r').each_line do |ip|
|
96
|
+
@cf.firewall.add_address(ip.chomp)
|
97
|
+
end
|
98
|
+
else
|
99
|
+
raise IOError, "The file #{filepath} is not readable!"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|