smart_proxy_dhcp_infoblox 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b37b9aafe28027c77579ba06a180b28a8d64caa5
4
- data.tar.gz: 55bee325d7c2b0447824270c7ad244f2c40370ed
3
+ metadata.gz: 1f93a00fc3b3226cd13507406635a76d39fdf5bf
4
+ data.tar.gz: 2af103b766051daba129f8a05918f704692f7362
5
5
  SHA512:
6
- metadata.gz: a44fdb8a55966706ca342a5820013196e822883ae2c667f90bdb44974063529028334aea75a8e7c6f97b6d56e0c5fe27a99d2017fe4191f207cd9aa3b32b01a1
7
- data.tar.gz: 22c4d7aa26b2047cae38ae0a9509bc715cc20cc5acabdb87d78b5e6aca3bd022f4d2d40f33de354fc8fd6a2c19531313799da90174a814507dea1abbf4d20cb6
6
+ metadata.gz: 629785c6c921e5a356af8ab70703bc13d9db487dc3b4cdc2fb24ab1ff320d620e67b6762ff557a400cc1a77b4a73e7d7817c07591160e6d3de4011cf65abef48
7
+ data.tar.gz: 442344c94cb0cf17e7e8915139149be7f50c545d771e570dcc7ae921c8ae98649a2f207c72e2c91c66bfdfc655fe73ab8b1b3a9ff1191c7ed2ecf470a4118fb7
data/README.md CHANGED
@@ -1,32 +1,37 @@
1
1
  # SmartProxyDhcpInfoblox
2
2
 
3
- *Introduction here*
4
-
5
- This plugin adds a new DHCP provider for managing records with Infoblox Servers
3
+ This plugin adds a new DHCP provider for managing records with infoblox servers
6
4
 
7
5
  ## Installation
8
6
 
9
7
  See [How_to_Install_a_Smart-Proxy_Plugin](http://projects.theforeman.org/projects/foreman/wiki/How_to_Install_a_Smart-Proxy_Plugin)
10
8
  for how to install Smart Proxy plugins
11
9
 
12
- This plugin is compatible with Smart Proxy 1.10 or higher.
10
+ This plugin is compatible with Smart Proxy 1.11 or higher.
11
+
12
+ When installing using "gem", make sure to install the bundle file:
13
+
14
+ echo "gem 'smart_proxy_dhcp_infoblox'" > /usr/share/foreman-proxy/bundler.d/dhcp_infoblox.rb
13
15
 
14
16
  ## Configuration
15
17
 
16
18
  To enable this DHCP provider, edit `/etc/foreman-proxy/settings.d/dhcp.yml` and set:
17
19
 
18
20
  :use_provider: dhcp_infoblox
21
+ :server: IP of infoblox server
22
+ :subnets: subnets you want to use (optional unless you set infoblox_subnets to false)
19
23
 
20
24
  Configuration options for this plugin are in `/etc/foreman-proxy/settings.d/dhcp_infoblox.yml` and include:
21
25
 
22
- * infoblox_user: API Username
23
- * infoblox_pw: API Password
24
- * infoblox_host: IP/URL to infoblox Server
26
+ * username: API Username
27
+ * passowrd: API Password
28
+ * record_type: host / fixedaddress (see different record types chapter)
29
+ * use_range: use infoblox ranges (true) or infoblox networks (false) to find the next free ip in your infoblox
25
30
 
26
- //// TEMP ////
27
- Add this to the Smart Proxy's bundler.d/Gemfile.local.rb configuration:
31
+ ## Different record types
32
+ The main difference between host and fixedaddress is that a host record already includes the dns records. It's an infoblox object that includes dhcp/a record/ptr records. If you use the host objects there is no need to use a dns smart proxy. Everything gets handled inside the dhcp smart proxy. This does however limit functionality. You can't delete conflicting records or you can't change dns names using foreman gui. Beware when editing host objects manually in infoblox, once you delete a host in foreman all associated host objects get deleted.
28
33
 
29
- gem 'smart_proxy_dhcp_infoblox', :path => '/path/tosmart_proxy_dhcp_infoblox'
34
+ If you chose to use fixedaddress you'll need to use the infoblox dns smart proxy (https://github.com/theforeman/smart_proxy_dns_infoblox) if you want to manage dns records.
30
35
 
31
36
  ## Contributing
32
37
 
@@ -0,0 +1,17 @@
1
+ ---
2
+ #
3
+ # Configuration file for 'dhcp_infoblox' dhcp provider
4
+ #
5
+ # use :server setting in dhcp.yml if you are managing a dhcp server which is not localhost
6
+ # use :subnets setting in dhcp.yml if you want to restrict subnets available to smart-proxy
7
+ #
8
+ :username: "infoblox"
9
+ :password: "infoblox"
10
+ #
11
+ # Record type to manage: can be "host" or "fixed_address"
12
+ #
13
+ :record_type: 'host'
14
+ #
15
+ # Use pre-definded ranges in networks to find available IP's
16
+ #
17
+ :use_ranges: false
@@ -1,4 +1,10 @@
1
- require 'dhcp_common/dhcp_common'
2
- require 'smart_proxy_dhcp_infoblox/dhcp_infoblox_plugin'
1
+ module Proxy
2
+ module DHCP
3
+ module Infoblox; end
4
+ end
5
+ end
3
6
 
4
- module Proxy::DHCP::Infoblox; end
7
+ require 'smart_proxy_dhcp_infoblox/plugin_configuration'
8
+ require 'smart_proxy_dhcp_infoblox/record_type_validator'
9
+ require 'smart_proxy_dhcp_infoblox/dhcp_infoblox_version'
10
+ require 'smart_proxy_dhcp_infoblox/dhcp_infoblox_plugin'
@@ -0,0 +1,75 @@
1
+ require 'resolv'
2
+ require 'smart_proxy_dhcp_infoblox/ip_address_arithmetic'
3
+ require "proxy/validations"
4
+
5
+ module ::Proxy::DHCP::Infoblox
6
+ class CommonCRUD
7
+ include Proxy::Validations
8
+ include IpAddressArithmetic
9
+
10
+ attr_reader :connection
11
+
12
+ def initialize(connection)
13
+ @connection = connection
14
+ end
15
+
16
+ def all_leases(network_address)
17
+ [] # infoblox doesn't support leases
18
+ end
19
+
20
+ def find_record(subnet_address, an_address)
21
+ return find_record_by_ip(subnet_address, an_address) if Resolv::IPv4::Regex =~ an_address
22
+ find_record_by_mac(subnet_address, an_address)
23
+ end
24
+
25
+ def add_record(options)
26
+ validate_ip(options[:ip])
27
+ validate_mac(options[:mac])
28
+ raise(Proxy::DHCP::Error, "Must provide hostname") unless options[:hostname]
29
+
30
+ build_host(options).post
31
+ # TODO: DELETE ME needed for testing on infoblox ipam express
32
+ #host.configure_for_dns = false
33
+ rescue Infoblox::Error => e
34
+ raise e unless e.message.include?("IB.Data.Conflict") # not a conflict
35
+
36
+ begin
37
+ existing_name, existing_host = find_host_and_name_by_ip(options[:ip])
38
+ rescue Exception
39
+ raise e
40
+ end
41
+ raise e if existing_host.nil? # something weird going on, re-raise the original exception
42
+
43
+ if options[:mac] != existing_host.mac || options[:hostname] != existing_name
44
+ raise Proxy::DHCP::Collision, "Record #{options[:ip]} conflicts with an existing record."
45
+ end
46
+ raise Proxy::DHCP::AlreadyExists, "Record #{options[:ip]} already exists."
47
+ end
48
+
49
+ def del_record(_, record)
50
+ raise InvalidRecord, "#{record} is static - unable to delete" unless record.deleteable?
51
+ found = find_host('ipv4addr' => record.ip)
52
+ return if found.nil?
53
+ found.delete
54
+ end
55
+
56
+ def build_reservation(name, host, full_subnet_address)
57
+ return nil if host.nil?
58
+ return nil if name.nil? || name.empty?
59
+ return nil if (host.respond_to?(:configure_for_dhcp) && !host.configure_for_dhcp)
60
+ return nil if host.mac.nil? || host.mac.empty?
61
+
62
+ opts = { :hostname => name }
63
+ opts[:mac] = host.mac
64
+ opts[:ip] = host.ipv4addr
65
+ opts[:deleteable] = true
66
+ # TODO: nextserver, use_nextserver, bootfile, and use_bootfile attrs exist but are not available in the Fixedaddress model
67
+ # Might be useful to extend the model to include these
68
+ opts[:nextServer] = host.nextserver if (host.respond_to?(:use_nextserver) && host.use_nextserver)
69
+ opts[:filename] = host.bootfile if (host.respond_to?(:use_bootfile) && host.use_bootfile)
70
+ opts[:subnet] = ::Proxy::DHCP::Subnet.new(full_subnet_address.split('/').first, cidr_to_ip_mask(cidr_to_i(full_subnet_address.split('/').last)))
71
+
72
+ Proxy::DHCP::Reservation.new(opts)
73
+ end
74
+ end
75
+ end
@@ -1,256 +1,70 @@
1
1
  require 'dhcp_common/server'
2
- require 'infoblox'
3
- require 'ipaddr'
2
+ require 'smart_proxy_dhcp_infoblox/ip_address_arithmetic'
4
3
 
5
4
  module Proxy::DHCP::Infoblox
6
5
  class Provider < ::Proxy::DHCP::Server
7
6
  include Proxy::Log
8
7
  include Proxy::Util
8
+ include IpAddressArithmetic
9
9
 
10
- attr_reader :infoblox_user, :infoblox_pw, :server
10
+ attr_reader :connection, :crud, :restart_grid, :unused_ips
11
11
 
12
- def initialize
13
- # TODO: Verify input
14
- server = Proxy::DhcpPlugin.settings.server
15
- infoblox_user = Proxy::DHCP::Infoblox::Plugin.settings.infoblox_user
16
- infoblox_pw = Proxy::DHCP::Infoblox::Plugin.settings.infoblox_pw
17
- @record_type = Proxy::DHCP::Infoblox::Plugin.settings.record_type
18
- @range = Proxy::DHCP::Infoblox::Plugin.settings.range
19
- wapi_version = Proxy::DHCP::Infoblox::Plugin.settings.wapi_version
20
- ::Infoblox.wapi_version = "#{wapi_version}"
21
- @connection = ::Infoblox::Connection.new(username: infoblox_user, password: infoblox_pw, host: server)
22
- logger.debug "Loaded infoblox provider with #{@record_type} record_type and #{wapi_version} wapi_version"
12
+ def initialize(connection, crud, restart_grid, unused_ips, managed_subnets)
13
+ @connection = connection
14
+ @crud = crud
15
+ @restart_grid = restart_grid
16
+ @unused_ips = unused_ips
17
+ super('infoblox', managed_subnets, nil)
23
18
  end
24
19
 
25
- def initialize_for_testing(params)
26
- @name = params[:name] || @name
27
- @service = params[:service] || service
28
- @dhcp_server = params[:dhcp_server] || @dhcp_server
29
- @username = params[:username] || @username
30
- @password = params[:password] || @password
31
- @record_type = params[:record_type] || @record_type
32
- @wapi_version = params[:wapi_version] || @wapi_version
33
- self
34
- end
35
-
36
- def load_subnets
37
- logger.debug 'load_subnets'
38
- ::Infoblox::Network.all(@connection).each do |obj|
39
- if match = obj.network.split('/')
40
- tmp = IPAddr.new(obj.network)
41
- netmask = IPAddr.new(tmp.instance_variable_get("@mask_addr"), Socket::AF_INET).to_s
42
- next unless managed_subnet? "#{match[0]}/#{netmask}"
43
- options = {}
44
- service.add_subnets(Proxy::DHCP::Subnet.new(match[0], netmask, options))
45
- end
46
- end
47
- end
48
-
49
- def find_subnet(network_address)
50
- # returns Proxy::DHCP::Subnet that has network_address or nil if none was found
51
- # network = ::Infoblox::Ipv4address.find(connection, "ip_address" => network_address).first.network
52
- super
53
- end
20
+ def find_subnet(address);::Proxy::DHCP::Subnet.new(address, '255.255.255.0'); end
21
+ def load_subnets; end
22
+ def load_subnet_data(_); end
54
23
 
55
- def load_subnet_data(subnet)
56
- # intentionally do nothing
57
- end
58
-
59
- def load_infoblox_subnet_data(subnet)
60
- # Load network from infoblox, iterate over ips to gather additional settings
61
- logger.debug 'load_infoblox_subnet_data'
62
- if @record_type == 'host'
63
- # max results are currently set to work in my setup, one could calculate that setting by looking at netmask :)
64
- network = ::Infoblox::Ipv4address.find(@connection, 'network' => "#{subnet.network}/#{subnet.cidr}", 'status' => 'USED', 'usage' => 'DHCP', '_max_results' => 2**(32-subnet.cidr))
65
- # Find out which hosts are in use
66
- network.each do |host|
67
- # next if certain values are not set
68
- next if host.names.empty? || host.mac_address.empty? || host.ip_address.empty?
69
- hostdhcp = ::Infoblox::HostIpv4addr.find(@connection, 'ipv4addr' => host.ip_address).first
70
- next unless hostdhcp.configure_for_dhcp
71
- opts = { :hostname => host.names.first }
72
- opts[:mac] = host.mac_address
73
- opts[:ip] = host.ip_address
74
- # broadcast and network entrys are not deleteable
75
- opts[:deleteable] = true unless (host.types & %w(BROADCAST NETWORK)).any?
76
- opts[:nextServer] = hostdhcp.nextserver unless hostdhcp.use_nextserver
77
- opts[:filename] = hostdhcp.bootfile unless hostdhcp.use_bootfile
78
- service.add_host(subnet.network, Proxy::DHCP::Reservation.new(opts.merge(:subnet => subnet)))
79
- end
80
- elsif @record_type == 'fixed_address'
81
- network = ::Infoblox::Fixedaddress.find(@connection, 'network' => "#{subnet.network}/#{subnet.cidr}", '_max_results' => 2**(32-subnet.cidr))
82
- network.each do |host|
83
- logger.debug "Processing host: #{host.name} #{host.mac} #{host.ipv4addr}"
84
- next if host.name == nil || host.mac == nil || host.ipv4addr == nil
85
- opts = { :hostname => host.name }
86
- opts[:mac] = host.mac
87
- opts[:ip] = host.ipv4addr
88
- service.add_host(subnet.network, Proxy::DHCP::Reservation.new(opts.merge(:subnet => subnet)))
89
- end
90
- end
24
+ def subnets
25
+ ::Infoblox::Network.all(connection).map do |network|
26
+ address, prefix_length = network.network.split("/")
27
+ netmask = cidr_to_ip_mask(prefix_length.to_i)
28
+ managed_subnet?("#{address}/#{netmask}") ? Proxy::DHCP::Subnet.new(address, netmask, {}) : nil
29
+ end.compact
91
30
  end
92
31
 
93
32
  def all_hosts(network_address)
94
- # returns all reservations in a subnet with network_address
95
- logger.debug "infoblox.all_hosts #{network_address}"
96
- load_infoblox_subnet_data(find_subnet(network_address))
97
- super
33
+ crud.all_hosts(full_network_address(network_address))
98
34
  end
99
35
 
100
- def unused_ip(network_address, mac_address, from_ip_address, to_ip_address)
101
- # returns first available ip address in a subnet with network_address, for a host with mac_address, in the range of ip addresses: from_ip_address, to_ip_address
102
- # Deliberatly ignoring everything but first argument
103
- logger.debug "Infoblox unused_ip Network_address: #{network_address} #{mac_address}, #{from_ip_address}, #{to_ip_address}"
104
- #next_available_ip can take a number to return (1), and an array of ips to exclude. So, we need to:
105
- #build a list of all ips in the network (all_addresses)
106
- #build a list of all ips in between from_ip_address and to_ip_address (include_addresses)
107
- #remove all of the from_ip_address and to_ip_address from the all_address (exclude_addresses)
108
- #and call next_available_ip with exclude_addresses passed
109
- all_addresses = Array.new
110
- net=IPAddr.new("#{network_address.network}/#{network_address.cidr}")
111
- net.to_range.each do |ip|
112
- all_addresses.push(ip.to_s)
113
- end
114
- range_start=IPAddr.new(from_ip_address)
115
- range_stop=IPAddr.new(to_ip_address)
116
- included_addresses=Array.new
117
- (range_start..range_stop).each do |ip|
118
- included_addresses.push(ip.to_s)
119
- end
120
- excluded_addresses=all_addresses-included_addresses
121
- #excluded_addresses is now an array of ips containing all the ips from the network not between from, and to_ip_address
122
-
123
- if @range
124
- ::Infoblox::Range.find(@connection, network: "#{network_address.network}/#{network_address.cidr}").first.next_available_ip(1,excluded_addresses)
125
- else
126
- ::Infoblox::Network.find(@connection, network: "#{network_address.network}/#{network_address.cidr}").first.next_available_ip(1,excluded_addresses)
127
- end
128
- # Idea for randomisation in case of concurrent installs:
129
- #::Infoblox::Network.find(@connection, network: "#{network_address.network}/#{network_address.cidr}").first.next_available_ip(15).sample
36
+ def all_leases(network_address)
37
+ crud.all_leases(full_network_address(network_address))
130
38
  end
131
39
 
132
40
  def find_record(subnet_address, an_address)
133
- logger.debug 'find_record'
134
- # record can be either ip or mac, true = mac --> lookup ip
135
- if an_address.is_a?(String) && valid_mac?(an_address)
136
- if @record_type == 'host'
137
- hostdhcp = ::Infoblox::HostIpv4addr.find(@connection, 'mac' => an_address)
138
- elsif @record_type == 'fixed_address'
139
- hostdhcp = ::Infoblox::Fixedaddress.find(@connection,'mac' => an_address)
140
- end
141
- return nil if hostdhcp.empty?
142
- ipv4address = hostdhcp.first.ipv4addr
143
- elsif an_address.is_a?(String)
144
- validate_ip(an_address)
145
- ipv4address = an_address
146
- end
147
-
148
- if @record_type == 'host'
149
- host = ::Infoblox::Host.find(@connection, 'ipv4addr' => ipv4address)
150
- return nil if host.empty? || host.first.name.empty?
151
- hostdhcp = ::Infoblox::HostIpv4addr.find(@connection, 'ipv4addr' => ipv4address).first
152
- return nil unless hostdhcp.configure_for_dhcp
153
- return nil if hostdhcp.mac.empty? || hostdhcp.ipv4addr.empty?
154
- opts = { :hostname => host.first.name }
155
- opts[:mac] = hostdhcp.mac
156
- opts[:ip] = hostdhcp.ipv4addr
157
- opts[:deleteable] = true
158
- opts[:nextServer] = hostdhcp.nextserver if hostdhcp.use_nextserver
159
- opts[:filename] = hostdhcp.bootfile if hostdhcp.use_bootfile
160
- elsif @record_type == 'fixed_address'
161
- logger.debug "find_record for #{an_address}"
162
- fixed_address = ::Infoblox::Fixedaddress.find(@connection, 'ipv4addr' => ipv4address)
163
- #logger.debug "#{fixed_address.inspect}"
164
- return nil if fixed_address == []
165
- #return nil if fixed_address.emtpy? || fixed_address.first.name.empty?
166
- #return nil if fixed_address.emtpy?
167
- opts = { :hostname => fixed_address.first.name }
168
- opts[:deleteable] = true
169
- opts[:mac] = fixed_address.first.mac
170
- opts[:ip] = fixed_address.first.ipv4addr
171
- end
172
- # Subnet should only be one, not checking that yet
173
- subnet = subnets.find { |s| s.include? ipv4address }
174
- Proxy::DHCP::Record.new(opts.merge(:subnet => subnet))
41
+ crud.find_record(full_network_address(subnet_address), an_address)
175
42
  end
176
43
 
177
- def create_infoblox_host_record(record)
178
- logger.debug 'create_infoblox_host_record'
179
- host = ::Infoblox::Host.new(:connection => @connection)
180
- host.name = record.name
181
- host.add_ipv4addr(record.ip)
182
- host.post
44
+ def add_record(options)
45
+ crud.add_record(options)
46
+ logger.debug("Added DHCP reservation for #{options[:ip]}/#{options[:mac]}")
47
+ restart_grid.try_restart
183
48
  end
184
49
 
185
- def create_infoblox_fixed_address(record)
186
- logger.debug 'create_infoblox_fixed_address'
187
- fixed_address = ::Infoblox::Fixedaddress.new(:connection => @connection)
188
- fixed_address.name = record.name
189
- fixed_address.ipv4addr = record.ip
190
- fixed_address.mac = record.mac
191
- fixed_address.post
50
+ def del_record(subnet, record)
51
+ crud.del_record(full_network_address(subnet.network), record)
52
+ logger.debug("Removed DHCP reservation for #{record.ip} => #{record}")
53
+ restart_grid.try_restart
192
54
  end
193
55
 
194
- def add_record options={}
195
- logger.debug 'Add Record'
196
- record = super
197
- #Since we support 2 types of records, do the right thing with each one.
198
- if @record_type == 'host'
199
- host = ::Infoblox::Host.find(@connection, 'ipv4addr' => record.ip)
200
- # If empty create:
201
- if host.empty?
202
- create_infoblox_host_record(record)
203
- end
204
- host = ::Infoblox::Host.find(@connection, 'ipv4addr' => record.ip).first
205
- options = record.options
206
- # Overwrite values without checking
207
- # Select correct ipv4addr object from ipv4addrs array
208
- hostip = host.ipv4addrs.find { |ip| ip.ipv4addr == record.ip }
209
- logger.debug "Add Record - record.name: #{record.name}, hostip.host #{hostip.host}, record.mac #{record.mac}, record.ip #{record.ip}"
210
- logger.debug "Add Record - options[:nextServer] #{options[:nextServer]}, options[:filename] #{options[:filename]}, hostip.ipv4addr: #{hostip.ipv4addr} "
211
- raise InvalidRecord, "#{record} Hostname mismatch" unless hostip.host == record.name
212
- hostip.mac = record.mac
213
- hostip.configure_for_dhcp = true
214
- hostip.nextserver = options[:nextServer]
215
- hostip.use_nextserver = true
216
- hostip.bootfile = options[:filename]
217
- hostip.use_bootfile = true
218
- ## Test if Host Entry has correct IP
219
- raise InvalidRecord, "#{record} IP mismatch" unless hostip.ipv4addr == record.ip
220
- # Send object
221
- host.put
222
- record
223
- elsif @record_type == 'fixed_address'
224
- create_infoblox_fixed_address(record)
225
- record
226
- end
56
+ def unused_ip(subnet, _, from_ip_address, to_ip_address)
57
+ unused_ips.unused_ip(subnet.network, from_ip_address, to_ip_address)
227
58
  end
228
59
 
229
- def del_record subnet, record
230
- logger.debug 'Infoblox del_record'
231
- validate_subnet subnet
232
- validate_record record
233
- # TODO: Refactor this into the base class
234
- raise InvalidRecord, "#{record} is static - unable to delete" unless record.deleteable?
235
- if @record_type == 'host'
236
- # "Deleting" a record here means just disabling dhcp
237
- host = ::Infoblox::Host.find(@connection, 'ipv4addr' => record.ip)
238
- unless host.empty?
239
- # if not empty, first element is what we want to edit
240
- host = host.first
241
- # Select correct ipv4addr object from ipv4addrs array
242
- hostip = host.ipv4addrs.find { |ip| ip.ipv4addr == record.ip }
243
- hostip.configure_for_dhcp = false
244
- # Send object
245
- host.put
246
- end
247
- elsif @record_type == 'fixed_address'
248
- #Delete the fixed address record.
249
- fixed_address = ::Infoblox::Fixedaddress.find(@connection, 'ipv4addr' => record.ip).first
250
- fixed_address.delete
251
- end
60
+ def find_network(network_address)
61
+ network = ::Infoblox::Network.find(connection, 'network~' => network_address, '_max_results' => 1).first
62
+ raise "Subnet #{network_address} not found" if network.nil?
63
+ network
64
+ end
252
65
 
253
- logger.debug "Disabled DHCP on #{record}"
66
+ def full_network_address(network_address)
67
+ find_network(network_address).network
254
68
  end
255
69
  end
256
70
  end