Ruby IPAddr: find address mask - ruby

I have an application where I process a lot of IP addresses (analysing Checkpoint firewall rule sets). At one point I want to check if a particular address object is a /32 or a 'network'.
Currently I am doing it like this:
next unless ip.inspect.match(/\/255\.255\.255\.255/)
it works but seems a bit inefficient but I can't see any method that extracts the mask from the address object.

Some parts of the Ruby core library are sometimes just sketched in, and IPAddr appears to be one of those that is, unfortunately, a little bit incomplete.
Not to worry. You can fix this with a simple monkey-patch:
class IPAddr
def cidr_mask
case (#family)
when Socket::AF_INET
32 - Math.log2((1<<32) - #mask_addr).to_i
when Socket::AF_INET6
128 - Math.log2((1<<128) - #mask_addr).to_i
else
raise AddressFamilyError, "unsupported address family"
end
end
end
That should handle IPv4 and IPv6 addresses:
IPAddr.new('151.101.65.69').cidr_mask
# => 32
IPAddr.new('151.101.65.69/26').cidr_mask
# => 26
IPAddr.new('151.101.65.69/255.255.255.0').cidr_mask
# => 24
IPAddr.new('2607:f8b0:4006:800::200e').cidr_mask
# => 128
IPAddr.new('2607:f8b0:4006:800::200e/100').cidr_mask
# => 100
It's not necessarily the best solution here, but it works.

I'm aware that this is a 3 year old question, but this was the first result on Google for me when I searched it, so I want to provide a new answer.
I was playing around in console today and noticed the the prefix method on the IPAddr object returns cidr mask as an integer.
So, for example:
ip = IPAddr.new("192.168.1.0/24")
ip.prefix
# => 24
It also turns out that type coercion gives you the integer representation of the address and mask, so you could potentially do the math on the output of to_i, to_json, as_json or instance_values.
An example with the network address:
ip.to_i
# => 3232235776
ip.to_i.to_s(2)
# => "11000000101010000000000100000000"
And one with the netmask:
ip.as_json
# => {"family"=>2, "addr"=>3232235776, "mask_addr"=>4294967040}
ip.as_json["mask_addr"].to_s(2)
# => 11111111111111111111111100000000
ip.as_json["mask_addr"].to_s(2).count("1")
# => 24

Related

Ruby enumeration has unexpected second pass

This is making me crazy. I'm trying to loop through email messages using ".each do |one_of|…end", but inserting one innocuous statement breaks the enumeration.
Here is an almost-stand-alone snippet. The authentication info is set up immediately preceding, as is the "require net/imap"
imap = Net::IMAP.new(IMAP_server)
imap.authenticate('LOGIN', IMAP_login, IMAP_pass)
imap.select(IMAP_folder)
[1,2,3,5].each do |mnum|
#message_ids = imap.search(['SUBJECT', 'NETGEAR R7000 Log']).each do |mnum|
puts(mnum)
msg = imap.fetch(mnum, 'BODY[TEXT]')
msg[0].values[1]['BODY[TEXT]'].each_line do |full_entry|
line = String.new(full_entry)
recType = line[/^\[.*\] /]
# recType = recType.tr('[]','')
puts recType
end
puts
puts
end
It was working (and not) when I was looping through message ID numbers, but I replaced that statement with a literal array to eliminate a potential problem source.
Running it with the ".tr" statement commented out produces expected results, lines like:
1
[DHCP IP: (192.168.1.9)]
[DoS attack: FIN Scan]
[DHCP IP: (192.168.1.8)]
[Admin login]
[Admin login failure]
[Admin login]
[DHCP IP: (192.168.1.7)]
[DHCP IP: (192.168.1.5)]
[Time synchronized with NTP server]
[Internet connected]
2
[LAN access from remote]
[LAN access from remote]
[LAN access from remote]
[UPnP set event: Public_UPNP_C3]
[UPnP set event: Public_UPNP_C3]
[UPnP set event: Public_UPNP_C3]
…
Now un-comment the ".tr" line, to remove the brackets, and it goes all the way through the first iteration, but then:
1
DHCP IP: (192.168.1.9)
DoS attack: FIN Scan
DHCP IP: (192.168.1.8)
Admin login
Admin login failure
Admin login
DHCP IP: (192.168.1.7)
DHCP IP: (192.168.1.5)
Time synchronized with NTP server
Internet connected
Traceback (most recent call last):
4: from /Users/jan/bin/MassageNetgear.rb:39:in `<main>'
3: from /Users/jan/bin/MassageNetgear.rb:39:in `each'
2: from /Users/jan/bin/MassageNetgear.rb:43:in `block in <main>'
1: from /Users/jan/bin/MassageNetgear.rb:43:in `each_line'
/Users/jan/bin/MassageNetgear.rb:45:in `block (2 levels) in <main>': undefined method `tr' for nil:NilClass (NoMethodError)
So, somehow, putting this one statement in is causing the second iteration of mnum to fail. It doesn't even print "2" for the second iteration. Line 45 in the traceback is the newly inserted statement, but it doesn't even get that far! Adding another puts as the first statement in the inner loop does nothing.
I've done this before to concatenate a bunch of emails containing router log messages, but I then modified that code, and I somehow broke it! I've cut and re-typed the problem statement, thinking there may have been invisibles in there, but I'm using BBEdit, which shows such things.
Please, point out where I'm doing something stupid!
Thanks!
nil prints as an empty string, so it's possible there is actually a last line which isn't matching the regexp and which is printing as an empty string. In that case, tr would fail since nil actually doesn't have a tr method.
You could use a conditional assignment, which will not execute the right-hand side if recType is falsy.
recType = line[/^\[.*\] /]
recType &&= recType.tr('[]','')
puts recType
Or (if you want to actually treat it as an empty string), you can simply unconditionally call to_s on it.
recType = line[/^\[.*\] /].to_s
recType = recType.tr('[]','')
puts recType

Parsing Casandra.yaml in Ruby/Inspec to get seeds value

How do I parse the following yaml from Cassandra.yaml in Ruby (InSpec) profile to get the seeds value. I would to get all the 3 Ipaddress in one sting or the 3 IP addresses in 3 strings.
seed_provider:
- class_name: org.apache.cassandra.locator.SimpleSeedProvider
parameters:
# seeds is actually a comma-delimited list of addresses.
# Ex: "<ip1>,<ip2>,<ip3>"
- seeds: "10.0.0.1, 10.0.0.2, 10.0.0.3"
Maybe there are better ways, but this would work:
require 'yaml'
config = YAML.load_file("/path/cassandra.yml")[0]
config.dig("parameters").first['seeds']
# => "10.0.0.1, 10.0.0.2, 10.0.0.3"
You could try the file resource or yaml resource in InSpec.

Why does puppet think my custom fact is a string?

I am trying to create a custom fact I can use as the value for a class parameter in a hiera yaml file.
I am using the openstack/puppet-keystone module and I want to use fernet-keys.
According to the comments in the module I can use this parameter.
# [*fernet_keys*]
# (Optional) Hash of Keystone fernet keys
# If you enable this parameter, make sure enable_fernet_setup is set to True.
# Example of valid value:
# fernet_keys:
# /etc/keystone/fernet-keys/0:
# content: c_aJfy6At9y-toNS9SF1NQMTSkSzQ-OBYeYulTqKsWU=
# /etc/keystone/fernet-keys/1:
# content: zx0hNG7CStxFz5KXZRsf7sE4lju0dLYvXdGDIKGcd7k=
# Puppet will create a file per key in $fernet_key_repository.
# Note: defaults to false so keystone-manage fernet_setup will be executed.
# Otherwise Puppet will manage keys with File resource.
# Defaults to false
So wrote this custom fact ...
[root#puppetmaster modules]# cat keystone_fernet/lib/facter/fernet_keys.rb
Facter.add(:fernet_keys) do
setcode do
fernet_keys = {}
puts ( 'Debug keyrepo is /etc/keystone/fernet-keys' )
Dir.glob('/etc/keystone/fernet-keys/*').each do |fernet_file|
data = File.read(fernet_file)
if data
content = {}
puts ( "Debug Key file #{fernet_file} contains #{data}" )
fernet_keys[fernet_file] = { 'content' => data }
end
end
fernet_keys
end
end
Then in my keystone.yaml file I have this line:
keystone::fernet_keys: '%{::fernet_keys}'
But when I run puppet agent -t on my node I get this error:
Error: Could not retrieve catalog from remote server: Error 500 on SERVER: Server Error: Evaluation Error: Error while evaluating a Function Call, "{\"/etc/keystone/fernet-keys/1\"=>{\"content\"=>\"xxxxxxxxxxxxxxxxxxxx=\"}, \"/etc/keystone/fernet-keys/0\"=>{\"content\"=>\"xxxxxxxxxxxxxxxxxxxx=\"}}" is not a Hash. It looks to be a String at /etc/puppetlabs/code/environments/production/modules/keystone/manifests/init.pp:1144:7 on node mgmt-01
I had assumed that I had formatted the hash correctly because facter -p fernet_keys output this on the agent:
{
/etc/keystone/fernet-keys/1 => {
content => "xxxxxxxxxxxxxxxxxxxx="
},
/etc/keystone/fernet-keys/0 => {
content => "xxxxxxxxxxxxxxxxxxxx="
}
}
The code in the keystone module looks like this (with line numbers)
1142
1143 if $fernet_keys {
1144 validate_hash($fernet_keys)
1145 create_resources('file', $fernet_keys, {
1146 'owner' => $keystone_user,
1147 'group' => $keystone_group,
1148 'subscribe' => 'Anchor[keystone::install::end]',
1149 }
1150 )
1151 } else {
Puppet does not necessarily think your fact value is a string -- it might do, if the client is set to stringify facts, but that's actually beside the point. The bottom line is that Hiera interpolation tokens don't work the way you think. Specifically:
Hiera can interpolate values of any of Puppet’s data types, but the
value will be converted to a string.
(Emphasis added.)

cucumber ActiveRecord::ConnectionNotEstablished

The exception I am getting is "ActiveRecord::ConnectionNotEstablished: No connection pool for ActiveRecord::Base". I am really on the deep end of the pool (no pun intended) with this one. I really don't understand the connection and connection pool handling, even as much as I have studied this problem. I'm assuming this might be scope related inside of Cucumber, but I do not know. Any and all assistance is appreciated.
Here are the details:
The exception occurs when I perform a count from a Then clause:
WorkTable.where('? is not null',col['COLUMN_NAME']).count
It does not occur if I send the sql directly through the connection:
WorkTable.connection.select_all(st.encode('utf-8')).first['nulls']
My scenario reads as follows:
Scenario: CompanyMaster test for null value
Given table dbo.base_table in stage
Then these columns are expected to be not null
| COLUMN_NAME | nulls |
| id | 0 |
| company_name | 0 |
I establish my class in my env.rb:
class WorkTable < ActiveRecord::Base
end
ActiveRecord::Base.configurations = YAML.load_file(yaml) # yaml is database.yml file name
I establish my connection in a Given clause:
Given(/^table (\w+)\.?([\w_]+) in (\w+)(?: as (\w+))?$/) do |schema,name,env,id|
#sc_name = schema_file_name(schema,name)
WorkTable.logger.info title_line("* Active table(#{#sc_name}) *")
case id
# ActiveRecord::Base.configurations[env]
...
else
WorkTable.table_name = #sc_name
WorkTable.establish_connection(env.to_sym)
# ary = get_tables(WorkTable,schema:schema)
# expect( ary.any?{|s| s.casecmp(name)==0 } ).to eq(true)
end
end
I execute my test in a Then clause:
Then(/^these columns are expected to be not null$/) do |columns|
# expected is an instance of Cucumber::Ast::Table
WorkTable.logger.info title_line('Columns cannot be null')
results = []
columns.hashes.each {|col|
results << {
'COLUMN_NAME' => col['COLUMN_NAME'],
'nulls' => WorkTable.where('? is not null',col['COLUMN_NAME']).count.to_s
}
}
columns.diff!(results,surplus_row: false)
end
It is the WorkTable.where that throws the "ActiveRecord::ConnectionNotEstablished: No connection pool for ActiveRecord::Base". Again, if I use the WorkTable.connection method, I do not get it. Also, it executes fine if I copy all the function code to single ruby script.
I see the following when I "pp WorkTable.connection":
#<ActiveRecord::ConnectionAdapters::SQLServerAdapter version: 4.2.2, mode: dblib, azure: false>
And I see the following when I "pp WorkTable.connection_pool":
#<ActiveRecord::ConnectionAdapters::ConnectionPool:0x42f5238
#automatic_reconnect=true,
#available=
#<ActiveRecord::ConnectionAdapters::ConnectionPool::Queue:0x42f4f20
#cond=
#<MonitorMixin::ConditionVariable:0x42f4ed8
#cond=
#<ConditionVariable:0x42f4de8
#waiters=[],
#waiters_mutex=#<Mutex:0x42f4d58>>,
#monitor=
#<ActiveRecord::ConnectionAdapters::ConnectionPool:0x42f5238 ...>>,
#lock=#<ActiveRecord::ConnectionAdapters::ConnectionPool:0x42f5238 ...>,
#num_waiting=0,
#queue=[]>,
#checkout_timeout=5,
#connections=
[#<ActiveRecord::ConnectionAdapters::SQLServerAdapter version: 4.2.2, mode: dblib, azure: false>],
#mon_count=0,
#mon_mutex=#<Mutex:0x42f51c0>,
#mon_owner=nil,
#reaper=
#<ActiveRecord::ConnectionAdapters::ConnectionPool::Reaper:0x42f51a8
#frequency=nil,
#pool=#<ActiveRecord::ConnectionAdapters::ConnectionPool:0x42f5238 ...>>,
#reserved_connections=
#<ThreadSafe::Cache:0x42f4fc8
#backend=
{16931712=>
#<ActiveRecord::ConnectionAdapters::SQLServerAdapter version: 4.2.2, mode: dblib, azure: false>},
#default_proc=nil>,
#size=5,
#spec=
#<ActiveRecord::ConnectionAdapters::ConnectionSpecification:0x42f55c8
#adapter_method="sqlserver_connection",
#config=
{:host=>"server_name",
:database=>"mssb_stg",
:encoding=>"utf-8",
:adapter=>"sqlserver",
:timeout=>5000}>>
Ruby 1.9.3, activerecord (4.2.0), activerecord-sqlserver-adapter (4.2.2), and cucumber (1.3.18). And sql server 2014 [this has been a bugger for me].
Thank you for you time and consideration.
dvn
== Additional detail ==
Ignore the sql-server reference. I get the same exception when I reconfigure to work with SqLite. So it is not related to db platform.
Check your env.rb, conf in supports, it seems you are making connections in steps, ideally you should do it in before_scenario or before feature file rather than per steps.
It could be possible after steps your connection is not working properly.

meaning of failure_reset_period option in win32-service gem for Ruby

Creating new service with win32-service, what's the meaning of the failure_reset_period ?
I'll appreciate some words on other options too (failure_reboot_message, failure_command, failure_actions, failure_delay) and examples.
Thank you in advance.
an example of use:
Service.new(
:service_name => SERVICE_NAME,
:display_name => SERVICE_DISPLAYNAME,
:start_type => Service::AUTO_START,
:error_control => Service::ERROR_NORMAL,
:service_type => Service::WIN32_OWN_PROCESS,
:description => 'This service does blah blah..',
:binary_path_name => path,
:failure_reset_period => 86400, # period (in seconds) with no failures after which the failure count should be reset to 0
:failure_actions => [ Service::ACTION_RESTART ], # action to take
:failure_delay => 60000 # delay before action in milliseconds
)
failure_reset_period resets to 0 the failure count on the service after specified time, what is useful since you can configure different actions for the first, second and other failures of the service.
meaning of those options is described here, for failure_reset_period:
the number of days that must pass before the service fail count is
reset
What if I need to set second and third failure options having different failure_delay?

Resources