Module: IPAccess
- Defined in:
- lib/ipaccess/core.rb,
lib/ipaccess/ip_access_set.rb,
lib/ipaccess/ip_access_list.rb,
lib/ipaccess/ip_access_check.rb,
lib/ipaccess/patches/generic.rb
Overview
This module contains classes that are used to control IP access. There are three major components you may want to use:
IPAccess::List class
This class lets you create IP access list with blacklisted and whitelisted elements. It also has methods for checking whether given IP matches the list.
IPAccess::Set class
This class contains two objects that are instances of IPAccess::List class. It allows you to create so called access set. The access set contains members named input
and output
. All methods that validate IP access do it against one of the lists. Input access list is for incomming and output for outgoing IP traffic. In case of connection-oriented sockets and other network objects the convention is to use output access list to validate connections that we initiate. The incomming traffic in that model means the connections initiated by a remote peer.
Patching engine
IPAccess was initialy considered as a set of classes that you may use in your own programs to control IP access. That means your own classes used for communication should use access lists or sets before making any real connections or sending any datagrams.
Fortunately there are many network classes, including sockets, that Ruby ships with. It would be waste of resources to not modify them to support IP access control and automagically throw exceptions when access should be denied.
And here the special module method called IPAccess.arm
comes in. It lets you patch most of Ruby’s networking classes and objects. Besides equipping them in IPAccess::Set instance it also adds some methods for doing quick checks and changes in access lists.
The patching engine can arm network classes and single network objects. It is not loaded by default since you may not want extra code attached to a program that uses access lists or sets with own access checking code.
Variants of popular classes
Sometimes you want to write a code that uses standard Ruby’s network objects but you find it dirty to alter classes or objects. In that case you may want to use static variants of Ruby’s network classes that are not patches but derived classes.
Exceptions
When you are dealing with patched (armed) versions of classes and objects or when you are using special variants of popular network classes, you have to rely on exceptions as the only way for access checking methods to tell your program that an event (like access denied) happened.
Note that when exception is thrown the communication session is closed in case of connection-oriented network objects. You may change it by setting opened_on_deny
attribute to true
.
See IPAccess::Set#check_in to know more about tracking original network object that caused exception to happend. Note that in case of armed versions of network classes (or access-contolled variants) an information about original network object stored within an exception will be set to nil
if access had been denied before object was initialized. This shouldn’t happend often, since access checks are lazy (they are performed only when connection is going to be made).
See IPAccessDenied for more information about what you can do with exceptions.
Sockets in armed network objects
Specialized Ruby’s network classes, such as Net::HTTP or Net::Telnet and their variants created by this library, make use of socket objects. For example Net::HTTP class uses TCPSocket instance to create TCP connection. When versions of these Net::
objects with enabled access control are used then the internal routines of IPAccess will also try to patch underlying sockets and assign to them the same access set that is used by main object. It is done to avoid access leaks. However, such armed internal sockets will have opened_on_deny
flag switched on since closing session (and an eventual connection) should be settled by main object.
Ordination of elements
To properly understand what are the most important structures mentioned above it’s worth to look at the diagram:
Usage
Handling access sets and access lists
If you need just IP access lists that you will handle in your own way you may want to use two classes:
-
IPAccess::Set to maintain access sets (containing input and output access lists),
-
IPAccess::List to maintain single access list.
Using socket classes
If you want standard sockets to have access control enabled you may want to use:
-
IPAccess::Socket (or issue
IPAccess.arm Socket
) -
IPAccess::TCPSocket (or issue
IPAccess.arm TCPSocket
) -
IPAccess::UDPSocket (or issue
IPAccess.arm UDPSocket
) -
IPAccess::SOCKSocket (or issue
IPAccess.arm SOCKSocket
) -
IPAccess::TCPServer (or issue
IPAccess.arm TCPServer
)
Before using any of them you must issue:
-
require 'ipaccess/socket'
Using the IPAccess.arm causes standard socket class to be altered, while IPAccess::
classes are just new variants of socket handling classes.
Using other supported network classes
If you want some working objects to have access control enabled you may want to use:
-
IPAccess::Net::Telnet (or issue
IPAccess.arm Net::Telnet
) -
IPAccess::Net::HTTP (or issue
IPAccess.arm Net::HTTP
) -
IPAccess::Net::FTP (or issue
IPAccess.arm Net::FTP
) -
IPAccess::Net::POP3 (or issue
IPAccess.arm Net::POP3
) -
IPAccess::Net::IMAP (or issue
IPAccess.arm Net::IMAP
) -
IPAccess::Net::SMTP (or issue
IPAccess.arm Net::SMTP
)
Using single network objects
If you want to enable access control for single network object from the list shown above you may issue:
require 'ipaccess/net/http'
obj = Net::HTTP.new(host, port)
IPAccess.arm obj
or
require 'ipaccess/socket'
socket = IPAccess::TCPServer.new(31337)
IPAccess.arm socket
..and so on.
Structures
IP addresses used by the classes are internaly and interfacialy represented by NetAddr::CIDR objects (NetAddr::CIDRv4 and NetAddr::CIDRv6). Due to performance reasons any access list internally is represented as a tree (slightly modified NetAddr::Tree) with special tags assigning rules to virtual lists.
Relations
Here is a diagram which shows relations between the IPAccess::TCPSocket class and other classes from this module:
Defined Under Namespace
Modules: Net, Patches Classes: List, SOCKSSocket, Set, Socket, TCPServer, TCPSocket, UDPSocket
Class Method Summary collapse
-
.arm(*args) ⇒ Object
This special method patches Ruby’s standard library classes and enables IP access control for them.
-
.to_cidr(*addresses) ⇒ Object
This method calls IPAccess.to_cidrs and returns first obtained entry containing single IP address with mask (NetAddr::CIDR).
-
.to_cidrs(*addresses) ⇒ Object
This method converts names to NetAddr::CIDR objects.
-
.valid_acl?(obj) ⇒ Boolean
This method returns
true
if the given object can be used to initialize ACL.
Class Method Details
.arm(klass, acl = nil) ⇒ Object .arm(klass, : opened_on_deny) ⇒ Object .arm(klass, acl, : opened_on_deny) ⇒ Object
This special method patches Ruby’s standard library classes and enables IP access control for them. Instances of such altered classes will be equipped with member called acl
, which is a kind of IPAccess::Set and allows you to manipulate access rules. It is also able to patch single instance of supported classes.
This method returns object that has been patched.
Supported classes
Currently supported classes are:
– Socket, UDPSocket, SOCKSSocket, TCPSocket, TCPServer,
– Net::HTTP,
– Net::Telnet,
– Net::FTP,
– Net::POP3,
– Net::IMAP,
– Net::SMTP.
Patching classes
Passed argument may be a class object, a string representation of a class object or a symbol representing a class object.
Patching single instances
Passed argument may be an instance of supported class. It’s possible to pass second, optional argument, which should be an initial access set. If this argument is omited then IPAccess::Set.Global is used. If :opened_on_deny
is passed then any connection remains opened in case of IPAccessDenied exception during arming.
Patching Ruby’s sockets
To quickly patch all Ruby’s socket classes you may pass symbol :sockets
as an argument.
Examples
Example 1 – sockets
require 'ipaccess/socket' # load sockets subsystem and IPAccess.arm method
IPAccess.arm TCPSocket # arm TCPSocket class
IPAccess::Set::Global.output.blacklist 'randomseed.pl' # add host to black list of the global set
TCPSocket.new('randomseed.pl', 80) # try to connect
Example 2 – HTTP
require 'ipaccess/net/http' # load net/http subsystem and IPAccess.arm method
IPAccess.arm Net::HTTP # arm TCPSocket class
IPAccess::Set::Global.output.blacklist 'randomseed.pl' # add host to black list of the global set
Net::HTTP.get_print('randomseed.pl', '/i.html') # try to connect
Example 3 – single network object
require 'ipaccess/net/telnet' # load Net::Telnet version and IPAccess.arm method
opts = {}
opts["Host"] = 'randomseed.pl'
opts["Port"] = '80'
t = Net::Telnet.new(opts) # try to connect to remote host
acl = IPAccess::Set.new # create custom access set
acl.output.blacklist 'randomseed.pl' # blacklist host
IPAccess.arm t, acl # arm Telnet object and pass optional ACL
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
# File 'lib/ipaccess/patches/generic.rb', line 154 def self.arm(*args) cod = args.delete(:opened_on_deny).nil? klass, acl = *args singleton_obj = nil if klass.is_a?(Class) # regular class klass_name = klass.name elsif klass.is_a?(Symbol) || klass.is_a?(String) klass_name = klass.to_s if klass.name.downcase == "sockets" # just a bunch of sockets require 'ipaccess/arm_sockets' return else # regular class as a string or symbol klass = Kernel klass_name.to_s.split('::').each { |k| klass = klass.const_get(k) } end else # regular object (will patch singleton of this object) klass_name = klass.class.name singleton_obj = klass klass = (class <<klass; self; end) end begin patch_klass = IPAccess::Patches klass_name.split('::').each { |k| patch_klass = patch_klass.const_get(k, false) } rescue NameError raise ArgumentError, "Cannot enable IP access control for class #{klass_name}" end klass.__send__(:include, patch_klass) singleton_obj.__send__(:__ipa_singleton_hook, acl, cod) unless singleton_obj.nil? # early initial check return klass end |
.to_cidr(*addresses) ⇒ Object
This method calls IPAccess.to_cidrs and returns first obtained entry containing single IP address with mask (NetAddr::CIDR).
518 519 520 521 |
# File 'lib/ipaccess/core.rb', line 518 def self.to_cidr(*addresses) r = self.to_cidrs(*addresses) return r.respond_to?(:first) ? first : r end |
.to_cidrs(*addresses) ⇒ Object
This method converts names to NetAddr::CIDR objects. It returns an array of CIDR objects.
Allowed input are strings (DNS names or IP addresses optionally with masks), numbers (IP addresses representation), IPSocket objects, URI objects, IPAddr objects, Net::HTTP objects, IPAddrList objects, NetAddr::CIDR objects, NetAddr::Tree objects, IPAccess::List objects, symbols, objects that contain file descriptors bound to sockets (including OpenSSL sockets) and arrays of these.
In case of resolving the IPv6 link-local addresses zone index is removed. In case of DNS names there may occur Resolv::ResolvError exception. If there is an object that cannot be converted the ArgumentError exception is raised.
When an argument called :include_origins
is present then the method will attach original converted objects to results as the :Origin
tag of CIDR objects (tag[:Origin]
). This rule applies only to single objects or objects inside of arrays or sets. Objects that are kind of NetAddr::CIDR, IPAccess::Set, NetAddr::Tree and arrays will never be set as originators.
Examples
to_cidrs("127.0.0.1") # uses the IP address
to_cidrs(2130706433) # uses numeric representation of 127.0.0.1
to_cidrs(:private, "localhost") # uses special symbol and DNS hostname
to_cidrs(:private, :localhost) # uses special symbols
to_cidrs [:private, :auto] # other way to write the above
to_cidrs "10.0.0.0/8" # uses masked IP address
to_cidrs "10.0.0.0/255.0.0.0" # uses masked IP address
to_cidrs IPSocket.new("www.pl", 80) # uses the socket
to_cidrs IPAddr("10.0.0.1") # uses IPAddr object
to_cidrs NetAddr::CIDR.create("10.0.0.1") # uses NetAddr object
to_cidrs URI('http://www.pl/') # uses URI
to_cidrs 'http://www.pl/' # uses the extracted host string
to_cidrs 'somehost.xx' # uses the host string (fetches ALL addresses from DNS)
to_cidrs 'somehost.xx/16' # uses the host string and a netmask
Special symbols
When symbol is passed to this method it tries to find out if it has special meaning. That allows you to create access rules in an easy way. For most of them you may also specify IP protocol version using ipv4_
or ipv6_
prefix.
Known symbols are:
:all
(:any
, :anyone
, :world
, :internet
, :net
, :everything
, :everyone
, :everybody
, :anybody
)
variants: :ipv4_
and :ipv6_
Creates masked IP address that matches all networks:
– 0.0.0.0/0
– ::/0
:broadcast
(:brd
)
variants: :ipv4_
and :ipv6_
Creates masked IP address that matches generic broadcast address:
– 255.255.255.255/32
– ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128
:local
(:localhost
, :localdomain
, :loopback
, :lo
)
variants: :ipv4_
and :ipv6_
Creates masked IP addresses that match localhost:
– 127.0.0.1/8
– ::1/128
:auto
(:automatic
, :linklocal
)
variants: :ipv4_
and :ipv6_
Creates masked IP addresses that match automatically assigned address ranges:
– 169.254.0.0/16
– fe80::/10
:private
(:intra
, :intranet
, :internal
)
variants: :ipv4_
and :ipv6_
Creates masked IP addresses that match private ranges:
– 10.0.0.0/8
– 172.16.0.0/12
– 192.168.0.0/16
– 2001:10::/28
– 2001:db8::/32
– fc00::/7
– fdde:9e1a:dc85:7374::/64
:multicast
(:multi
, :multiemission
)
variants: :ipv4_
and :ipv6_
Creates masked IP addresses that match multicast addresses ranges:
– 224.0.0.0/4
– ff00::/8
– ff02::1:ff00:0/104
:reserved
(:example
)
variants: :ipv4_
Creates masked IP addresses that match reserved addresses ranges:
– 192.0.2.0/24
– 128.0.0.0/16
– 191.255.0.0/16
– 192.0.0.0/24
– 198.18.0.0/15
– 223.255.255.0/24
– 240.0.0.0/4
:strange
(:unusual
, :nonpublic
, :unpublic
)
Creates masked IP addressess that match the following sets (both IPv4 and IPv6):
– :local
– :auto
– :private
– :reserved
– :multicast
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 |
# File 'lib/ipaccess/core.rb', line 303 def self.to_cidrs(*addresses) obj = addresses.flatten include_origins = !!obj.reject!{ |x| x.is_a?(Symbol) && x == :include_origins } if obj.size == 1 obj = obj.first else ary = [] obj.each do |o| ary += ( include_origins ? to_cidrs(o, :include_origins) : to_cidrs(o) ) end ary.flatten! return ary end ori_obj = obj # NetAddr::CIDR - immediate generation if obj.is_a?(NetAddr::CIDR) r = obj.dup r.tag[:Originator] = ori_obj if include_origins return [r] end # IPAccess::List - immediate generation return obj.to_a if obj.is_a?(IPAccess::List) # NetAddr::Tree - immediate generation return obj.dump.map { |addr| addr[:CIDR] } if obj.is_a?(NetAddr::Tree) # number or nil - immediate generation or exception if (obj.is_a?(Numeric) || obj.nil?) r = NetAddr::CIDR.create(obj) r.tag[:Originator] = ori_obj if include_origins return [r] end # object containing socket member (e.g. Net::HTTP) - fetch socket if obj.respond_to?(:socket) obj = obj.socket elsif obj.respond_to?(:sock) obj = obj.sock elsif obj.respond_to?(:client_socket) obj = obj.client_socket elsif obj.instance_variable_defined?(:@socket) obj = obj.instance_variable_get(:@socket) elsif obj.instance_variable_defined?(:@client_socket) obj = obj.instance_variable_get(:@client_socket) elsif obj.instance_variable_defined?(:@sock) obj = obj.instance_variable_get(:@sock) end obj = obj.io if (obj.respond_to?(:io) && obj.io.respond_to?(:getpeername)) # some file descriptor but not socket - fetch socket obj = ::Socket.for_fd(obj.fileno) if (!obj.respond_to?(:getpeername) && obj.respond_to?(:fileno)) # Socket - immediate generation if obj.respond_to?(:getpeername) peeraddr = ::Socket.unpack_sockaddr_in(obj.getpeername).last.split('%').first r = NetAddr::CIDR.create(peeraddr) r.tag[:Originator] = ori_obj if include_origins return [r] end # symbol - immediate generation r_args = nil if obj.is_a?(Symbol) case obj when :ipv4_all, :ipv4_any, :ipv4_anyone, :ipv4_world, :ipv4_internet, :ipv4_net, :ipv4_everything, :ipv4_everyone, :ipv4_everybody, :ipv4_anybody obj = [ "0.0.0.0/0" ] when :ipv6_all, :ipv6_any, :ipv6_anyone, :ipv6_world, :ipv6_internet, :ipv6_net, :ipv6_everything, :ipv6_everyone, :ipv6_everybody, :ipv6_anybody obj = [ "0.0.0.0/0", "::/0" ] when :ipv4_broadcast, :ipv4_brd obj = [ "255.255.255.255/32" ] when :ipv6_broadcast, :ipv6_brd obj = [ "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128" ] when :ipv4_local, :ipv4_localhost, :ipv4_loopback, :ipv4_lo obj = [ "127.0.0.1/8" ] when :ipv6_local, :ipv6_localhost, :ipv6_loopback, :ipv6_lo obj = [ "::1/128" ] when :ipv4_auto, :ipv4_automatic, :ipv4_linklocal obj = [ "169.254.0.0/16" ] when :ipv6_auto, :ipv6_automatic, :ipv6_linklocal obj = [ "fe80::/10" ] when :ipv4_private, :ipv4_intra, :ipv4_intranet, :ipv4_internal obj = [ "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16" ] when :ipv6_private, :ipv6_intra, :ipv6_intranet, :ipv6_internal, :ipv6_ula, :ipv6_unique obj = [ "2001:10::/28", "2001:db8::/32", "fc00::/7", "fdde:9e1a:dc85:7374::/64" ] when :ipv4_multicast, :ipv4_multi, :ipv4_multiemission obj = [ "224.0.0.0/4" ] when :ipv6_multicast, :ipv6_multi, :ipv6_multiemission obj = [ "ff00::/8", "ff02::1:ff00:0/104" ] when :ipv4_example, :ipv4_reserved obj = [ "192.0.2.0/24", "128.0.0.0/16", "191.255.0.0/16", "192.0.0.0/24", "198.18.0.0/15", "223.255.255.0/24", "240.0.0.0/4" ] when :all, :any, :anyone, :world, :internet, :net, :everything, :everyone, :everybody, :anybody r_args = [ :ipv4_all, :ipv6_all ] when :broadcast, :brd r_args = [ :ipv4_broadcast, :ipv6_broadcast ] when :local, :localhost, :localdomain, :loopback, :lo r_args = [ :ipv4_local, :ipv6_local ] when :auto, :automatic, :linklocal r_args = [ :ipv4_auto, :ipv6_auto ] when :private, :intra, :intranet, :internal r_args = [ :ipv4_private, :ipv6_private ] when :multicast, :multi, :multiemission r_args = [ :ipv4_multicast, :ipv6_multicast ] when :reserved, :example r_args = [ :ipv4_example ] when :strange, :unusual, :nonpublic, :unpublic r_args = [ :local, :auto, :private, :reserved, :multicast ] else raise ArgumentError, "Provided symbol is unknown: #{obj.to_s}" end unless r_args.nil? r_args.push :include_origins if include_origins return to_cidrs(*r_args) end # strange types here if obj.is_a?(Array) return obj.map do |addr| r = NetAddr::CIDR.create(addr) r.tag[:Originator] = addr if include_origins r end end end # URI or something that responds to host method - fetch string obj = obj.host if obj.respond_to?(:host) # objects of external classes case obj.class.name.to_sym when :IPAddr # IPAddr - fetch IP/mask string obj = obj.native.inspect.split[1].chomp('>')[5..-1] when :IPAddrList # IPAddrList - pass array to parse return include_origins ? to_cidrs(obj.to_a, :include_origins) : to_cidrs(obj.to_a) end # string or similar - immediate generation if obj.respond_to?(:to_s) hostmask = "" obj = obj.to_s # URI if obj =~ /^[^:]+:\/\/(.*)/ obj = $1.split('/').first # IP in URI if obj =~ /^\[([^\]]+)\]/ obj = $1 else obj = obj.split(':').first end # host(s) and a mask elsif obj =~ /^([^\/]+)(\/((\d{1,2}$)|(\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b$)))/ obj = $1 hostmask = $2 end begin ipa = obj.split('%').first.to_s r = NetAddr::CIDR.create(ipa + hostmask) rescue NetAddr::ValidationError begin addresses = Resolv::getaddresses(obj) rescue NoMethodError # unhandled error raise Resolv::ResolvError, "not connected or network error" end addresses.map! do |addr| begin r = NetAddr::CIDR.create(addr.split('%').first + hostmask) r.tag[:Originator] = ori_obj r rescue ArgumentError nil end end addresses.flatten! addresses.compact! return addresses end r.tag[:Originator] = ori_obj return [r] end # should never happend r = obj.is_a?(NetAddr::CIDR) ? obj.dup : NetAddr::CIDR.create(obj.to_s) r.tag[:Originator] = ori_obj return [r] end |
.valid_acl?(obj) ⇒ Boolean
This method returns true
if the given object can be used to initialize ACL. Otherwise it returns false
.
286 287 288 289 290 291 292 293 |
# File 'lib/ipaccess/patches/generic.rb', line 286 def IPAccess.valid_acl?(obj) if obj.is_a?(Symbol) return true if (obj == :global || obj == :private) elsif obj.is_a?(IPAccess::Set) return true end return false end |