symbiosis-firewall-whitelist 5.88 KB
Newer Older
Patrick J Cherry's avatar
Patrick J Cherry committed
1
#! /usr/bin/ruby1.8
2
#
Steve Kemp's avatar
Steve Kemp committed
3
# NAME
4
#   symbiosis-firewall-whitelist -- Automatically whitelist IP addresses.
Steve Kemp's avatar
Steve Kemp committed
5
6
#
# SYNOPSIS
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#  symbiosis-firewall-whitelist [ -h | --help ] [-m | --manual]
#       [ -v | --verbose ] [ -x | --no-exec] [ -d | --no-delete ]
#       [ -e | --expire-after <n> ] [ -w | --wtmp-file <file> ]
#       [ -p | --prefix <dir> ] [ -t | --template-d <dir> ] 
#
# OPTIONS
#  -h, --help              Show a help message, and exit.
# 
#  -m, --manual            Show this manual, and exit.
#
#  -v, --verbose           Show verbose errors.
#
#  -x, --no-exec           Do not execute the generated firewall rules.
#
#  -d, --no-delete         Do not delete the generated script.
#
#  -e, --expire-after <n>  Number of days after which whitelisted IPs should be
#                          expired. Defaults to 8.
#
#  -w, --wtmp-file <file>  wtmp(5) file to read to find IPs to whitelist.
#                          Defaults to /var/log/wtmp.
Steve Kemp's avatar
Steve Kemp committed
28
#
29
#
30
31
#  -p, --prefix <dir>      Directory where action.d, incoming.d, outgoing.d etc.
#                          are located. Defaults to /etc/symbiosis/firewall.
32
#
33
#  -t, --template-d <dir>  Additional directory to search for templates.
Steve Kemp's avatar
Steve Kemp committed
34
#
35
# USAGE
Steve Kemp's avatar
Steve Kemp committed
36
37
38
39
#
# This script is designed to automatically whitelist IP addresses which
# have been used to successfully login via SSH.
#
40
41
42
43
44
45
46
47
48
49
50
# It does this by opening the wtmp file, and looking for IP addresses. Once it
# has found some, it records them in /etc/symbiosis/firewall/whitelist.d/.
# Each addition is one of the two forms:
#
#   1.2.3.4.auto                The IPv4 address 1.2.3.4
#   2001:123:456:789::-64.auto  The IPv6 range 2001:123:456:789::/64
#
# Once that directory has been written, symbiosis-firewall(1) is called with
# the reload-whitelist action.
#
# Most of the flags above are passed straight on to symbiosis-firewall(1).
Steve Kemp's avatar
Steve Kemp committed
51
52
53
#
# AUTHOR
#
54
#  Steve Kemp <steve@bytemark.co.uk>
Steve Kemp's avatar
Steve Kemp committed
55
56
57
58
59
60
61
#

#
#  Modules we require
#

require 'getoptlong'
62
require 'tempfile'
Patrick J Cherry's avatar
Patrick J Cherry committed
63
require 'symbiosis/utmp'
64
65
require 'symbiosis/firewall/directory'
require 'symbiosis/firewall/template'
66
require 'symbiosis/firewall/ipaddr'
Steve Kemp's avatar
Steve Kemp committed
67
68


69
70
71
#
#  The options set by the command line.
#
72
73
74
75
76
77
78
help         = false
manual       = false
$VERBOSE     = false
base_dir     = "/etc/symbiosis/firewall/"
wtmp_file    = "/var/log/wtmp"
delete       = false
execute      = false
79
template_dir = nil
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
force        = true
expire_after = 8

opts = GetoptLong.new(
         [ '--help',       '-h', GetoptLong::NO_ARGUMENT ],
         [ '--manual',     '-m', GetoptLong::NO_ARGUMENT ],
         [ '--verbose',    '-v', GetoptLong::NO_ARGUMENT ],
         [ '--no-execute', '-x', GetoptLong::NO_ARGUMENT ],
         [ '--no-delete',  '-d', GetoptLong::NO_ARGUMENT ],
         [ '--force',      '-f', GetoptLong::NO_ARGUMENT ],
         [ '--prefix',     '-p', GetoptLong::REQUIRED_ARGUMENT ],
         [ '--template-d', '-t', GetoptLong::REQUIRED_ARGUMENT ],
         [ '--wtmp-file',  '-w', GetoptLong::REQUIRED_ARGUMENT ],
         [ '--expire-after', '-e', GetoptLong::REQUIRED_ARGUMENT ]
       )
Steve Kemp's avatar
Steve Kemp committed
95
96
97

opts.each do |opt,arg|
  case opt
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
  when '--help'
    help = true
  when '--manual'
    manual = true
  when '--verbose'
    $VERBOSE = true
  when '--test'
    test = true
  when '--no-execute'
    execute = false
  when '--no-delete'
    delete = false
  when '--force'
    force = false
  when '--prefix'
    base_dir     = File.expand_path(arg)
  when '--template-d'
    template_dir = File.expand_path(arg)
  when '--expire-after'
    expire_after = arg.to_i
  when '--wtmp-file'
    wtmp_file = arg
Steve Kemp's avatar
Steve Kemp committed
120
121
122
123
124
125
  end
end

#
# CAUTION! Here be quality kode.
#
126
if manual or help
Steve Kemp's avatar
Steve Kemp committed
127
  # Open the file, stripping the shebang line
128
129
130
  lines = File.open(__FILE__){|fh| fh.readlines}[1..-1]

  found_synopsis = false
Steve Kemp's avatar
Steve Kemp committed
131
132

  lines.each do |line|
133

Steve Kemp's avatar
Steve Kemp committed
134
135
    line.chomp!
    break if line.empty?
136
137
138
139
140
141

    if help and !found_synopsis
      found_synopsis = (line =~ /^#\s+SYNOPSIS\s*$/)
      next
    end

Steve Kemp's avatar
Steve Kemp committed
142
    puts line[2..-1].to_s
143
144
145

    break if help and found_synopsis and line =~ /^#\s*$/

Steve Kemp's avatar
Steve Kemp committed
146
147
148
149
150
  end

  exit 0
end

151
152
153
154
155
#  Expire old entries first of all, then add new ones.
#
puts "Expiring old whitelist entries" if ( $VERBOSE )

expired = 0
156
whitelist_d = File.join($PREFIX, "whitelist.d")
157

158
159
160
161
162
163
164
# ensure the directory exists.
if ( ! File.directory?( "#{whitelist_d}" ) )
  system( "mkdir -p #{whitelist_d}" )
end

if ( File.directory?( whitelist_d ) )
  Dir.foreach( whitelist_d ) do |entry|
165
    if ( ( entry =~ /\.auto$/i ) &&
166
         (File.mtime( "#{whitelist_d}/#{entry}" ) <  ( Time.now - 8 * 24 * 60 * 60 ) ) )
167
    then
168
169
      puts "Removing #{whitelist_d}/#{entry}" if ( $VERBOSE )
      File.unlink("#{whitelist_d}/#{entry}")
170
171
172
173
174
175
176
      expired += 1
    end
  end
end
puts "Expiring done - removed #{expired} file(s)" if ( $VERBOSE )


Steve Kemp's avatar
Steve Kemp committed
177
#
Patrick J Cherry's avatar
Patrick J Cherry committed
178
# Fetch the IP addresses
Steve Kemp's avatar
Steve Kemp committed
179
#
180
ip_addresses = Symbiosis::Utmp.read(wtmp_file).collect{|entry| entry["ip"]}
Steve Kemp's avatar
Steve Kemp committed
181
182
183
184
185
186
187
188
189

#
#  Did we update?
#
updated=false

#
#  Iterate over each IP
#
Patrick J Cherry's avatar
Patrick J Cherry committed
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
ip_addresses.each do |ip|
  #
  # We only want IP addresses.
  #
  next unless ip.is_a?(IPAddr)

  #
  # FIXME: Need better IPv6 conditions.
  #
  if ( ( ip.ipv4? and 
         !( IPAddr.new("127.0.0.1/8").include?(ip) or IPAddr.new("0.0.0.0") == ip )
       ) or (
         ip.ipv6? and IPAddr.new("2000::/3").include?(ip)
       ) 
     )
Steve Kemp's avatar
Steve Kemp committed
205

206
207
208
209
210
    #
    # Mask IPv6 to /64s.
    #
    ip = ip.mask(64) if ip.ipv6?

Steve Kemp's avatar
Steve Kemp committed
211
212
    puts "Found IP address: #{ip}" if ( $VERBOSE )

213
214
215
    fn = File.join(whitelist_d,ip.to_s.gsub("/","-")+".auto" )

    if ( File.exists?(fn) )
Steve Kemp's avatar
Steve Kemp committed
216
217
218
      puts "\tAlready whitelisted" if ( $VERBOSE )
    else
      # create the file
219
      system( "touch #{fn}" )
Steve Kemp's avatar
Steve Kemp committed
220
221
222
223
224
225
226
227
228
229

      updated=true
      puts "\tAdding to whitelist" if ( $VERBOSE )
    end
  end
end



#
230
# Re-generate the whitelist chain
Steve Kemp's avatar
Steve Kemp committed
231
#
232
if ( updated || expired > 0 || force )
233
234
235
236
237
238
239
240
  cmd = %w(symbiosis-firewall)
  cmd << "--verbose" if $VERBOSE
  cmd << "--no-execute" unless execute
  cmd << "--no-delete"  unless delete
  cmd += ["--prefix", base_dir]
  cmd += ["--template-d", template_dir] unless template_dir.nil?
  cmd << "reload-whitelist"
  exec(cmd)
Steve Kemp's avatar
Steve Kemp committed
241
end
242

243