smart_proxy_dhcp_bluecat 0.1.0 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -1,47 +1,47 @@
1
- # SmartProxyDhcpBlueCat
2
-
3
- This plugin adds a new DHCP provider for managing records with BlueCat Address Manager.
4
- The Provider manages dhcp reservations and A&PTR records.
5
-
6
- ## Installation
7
-
8
- See [How_to_Install_a_Smart-Proxy_Plugin](http://projects.theforeman.org/projects/foreman/wiki/How_to_Install_a_Smart-Proxy_Plugin)
9
- for how to install Smart Proxy plugins
10
-
11
- This plugin is compatible with Smart Proxy 1.16 or higher.
12
-
13
- When installing using "gem", make sure to install the bundle file:
14
-
15
- echo "gem 'smart_proxy_dhcp_bluecat', :git => 'https://github.com/theforeman/smart_proxy_dhcp_bluecat'" > /usr/share/foreman-proxy/bundler.d/dhcp_bluecat.rb
16
-
17
- ## Configuration
18
-
19
- To enable this DHCP provider, edit `/etc/foreman-proxy/settings.d/dhcp.yml` and set:
20
-
21
- :use_provider: dhcp_bluecat
22
- :subnets: subnets you want to use (optional)
23
-
24
- Configuration options for this plugin are in `/etc/foreman-proxy/settings.d/dhcp_bluecat.yml` and include:
25
-
26
- :scheme: connection mode to the Bluecat address manager
27
- :verify: validate ssl connection
28
- :host: FQDN or IP of the Bluecat address manager
29
- :parent_block: parent_block Id that holds your subnets
30
- :view_name: Bluecat DNS view name
31
- :config_id: Bluecat configuration id
32
- :config_name: Bluecat configuration name
33
- :server_id: id of your dhcp server
34
- :username: API Username
35
- :password: API Password
36
-
37
- ## Limitations
38
- IPv6 Records are currently not implemented
39
- Adresses with expired DHCP Leases are not handed out as free IPs by Bluecat
40
-
41
- ## Contributing
42
-
43
- Fork and send a Pull Request. Thanks!
44
-
45
- ## Copyright
46
-
47
- Copyright (c) 2018 Sixt GmbH & Co. Autovermietung KG
1
+ # SmartProxyDhcpBlueCat
2
+
3
+ This plugin adds a new DHCP provider for managing records with BlueCat Address Manager.
4
+ The Provider manages dhcp reservations and A&PTR records.
5
+
6
+ ## Installation
7
+
8
+ See [How_to_Install_a_Smart-Proxy_Plugin](http://projects.theforeman.org/projects/foreman/wiki/How_to_Install_a_Smart-Proxy_Plugin)
9
+ for how to install Smart Proxy plugins
10
+
11
+ This plugin is compatible with Smart Proxy 1.16 or higher.
12
+
13
+ When installing using "gem", make sure to install the bundle file:
14
+
15
+ echo "gem 'smart_proxy_dhcp_bluecat', :git => 'https://github.com/theforeman/smart_proxy_dhcp_bluecat'" > /usr/share/foreman-proxy/bundler.d/dhcp_bluecat.rb
16
+
17
+ ## Configuration
18
+
19
+ To enable this DHCP provider, edit `/etc/foreman-proxy/settings.d/dhcp.yml` and set:
20
+
21
+ :use_provider: dhcp_bluecat
22
+ :subnets: subnets you want to use (optional)
23
+
24
+ Configuration options for this plugin are in `/etc/foreman-proxy/settings.d/dhcp_bluecat.yml` and include:
25
+
26
+ :scheme: connection mode to the Bluecat address manager
27
+ :verify: validate ssl connection
28
+ :host: FQDN or IP of the Bluecat address manager
29
+ :parent_block: parent_block Id that holds your subnets
30
+ :view_name: Bluecat DNS view name
31
+ :config_id: Bluecat configuration id
32
+ :config_name: Bluecat configuration name
33
+ :server_id: id of your dhcp server
34
+ :username: API Username
35
+ :password: API Password
36
+
37
+ ## Limitations
38
+ IPv6 Records are currently not implemented
39
+ Adresses with expired DHCP Leases are not handed out as free IPs by Bluecat
40
+
41
+ ## Contributing
42
+
43
+ Fork and send a Pull Request. Thanks!
44
+
45
+ ## Copyright
46
+
47
+ Copyright (c) 2018 Sixt GmbH & Co. Autovermietung KG
@@ -1 +1 @@
1
- gem 'smart_proxy_dhcp_bluecat'
1
+ gem 'smart_proxy_dhcp_bluecat'
@@ -1,35 +1,35 @@
1
- ---
2
- #
3
- # Configuration file for 'dhcp_bluecat' dhcp provider
4
- #
5
-
6
- #connection mode to the address manager
7
- #https or http
8
- :scheme: "https"
9
-
10
- # validate ssl connection
11
- # true or false
12
- :verify: true
13
-
14
- #fqdn or ip of your bluecat address manager
15
- :host: "192.168.0.2"
16
-
17
- # id of the parent_block that holds the subnets that you want to use
18
- :parent_block: 00000
19
-
20
- # name of your dns view
21
- :view_name: "internal"
22
-
23
- # id of your Bluecat configuration
24
- :config_id: 000000
25
-
26
- # Name of your Bluecat configuration
27
- :config_name: "default"
28
-
29
- # id of the server that holds your dhcp
30
- :server_id: 000000
31
-
32
-
33
- # credentials of your api user
34
- :username: "username"
35
- :password: "password"
1
+ ---
2
+ #
3
+ # Configuration file for 'dhcp_bluecat' dhcp provider
4
+ #
5
+
6
+ # connection mode to the address manager
7
+ # https or http
8
+ :scheme: "https"
9
+
10
+ # validate ssl connection
11
+ # true or false
12
+ :verify: true
13
+
14
+ # fqdn or ip of your bluecat address manager
15
+ :host: "192.168.0.2"
16
+
17
+ # id of the parent_block that holds the subnets that you want to use
18
+ :parent_block: 00000
19
+
20
+ # name of your dns view
21
+ :view_name: "internal"
22
+
23
+ # id of your Bluecat configuration
24
+ :config_id: 000000
25
+
26
+ # Name of your Bluecat configuration
27
+ :config_name: "default"
28
+
29
+ # id of the server that holds your dhcp
30
+ :server_id: 000000
31
+
32
+
33
+ # credentials of your api user
34
+ :username: "username"
35
+ :password: "password"
@@ -1,11 +1,11 @@
1
- module Proxy
2
- module DHCP
3
- module BlueCat; end
4
- end
5
- end
6
-
7
- require 'smart_proxy_dhcp_bluecat/plugin_configuration'
8
- require 'smart_proxy_dhcp_bluecat/module_loader'
9
- require 'smart_proxy_dhcp_bluecat/settings_validator'
10
- require 'smart_proxy_dhcp_bluecat/dhcp_bluecat_version'
11
- require 'smart_proxy_dhcp_bluecat/dhcp_bluecat_plugin'
1
+ module Proxy
2
+ module DHCP
3
+ module BlueCat; end
4
+ end
5
+ end
6
+
7
+ require 'smart_proxy_dhcp_bluecat/plugin_configuration'
8
+ require 'smart_proxy_dhcp_bluecat/module_loader'
9
+ require 'smart_proxy_dhcp_bluecat/settings_validator'
10
+ require 'smart_proxy_dhcp_bluecat/dhcp_bluecat_version'
11
+ require 'smart_proxy_dhcp_bluecat/dhcp_bluecat_plugin'
@@ -1,345 +1,386 @@
1
- require 'httparty'
2
- require 'ipaddress'
3
- require 'json'
4
-
5
- class BlueCat::Client
6
- include ::Proxy::Log
7
-
8
- @@token = ''
9
-
10
- attr_reader :scheme, :verify, :host, :parent_block, :view_name, :config_name, :config_id, :server_id, :username, :password
11
-
12
- def initialize(scheme, verify, host, parent_block, view_name, config_name, config_id, server_id, username, password)
13
- @scheme = scheme
14
- @verify = verify
15
- @host = host
16
- @parent_block = parent_block
17
- @view_name = view_name
18
- @config_id = config_id
19
- @config_name = config_name
20
- @server_id = server_id
21
- @username = username
22
- @password = password
23
- end
24
-
25
- def rest_login
26
- # login to bam, parse the session token
27
- logger.debug('BAM Login ' + @scheme + ' ' + @host + ' ')
28
- response = HTTParty.get(format('%s://%s/Services/REST/v1/login?username=%s&password=%s', @scheme, @host, @username, @password),
29
- headers: { 'Content-Type' => 'text/plain' },
30
- verify => @verify)
31
- if response.code != 200
32
- logger.error('BAM Login Failed. HTTP' + response.code.to_s + ' ' + response.body.to_s)
33
- end
34
- body = response.body.to_s
35
- token = body.match(/BAMAuthToken:\s+(\S+)/).captures
36
-
37
- logger.debug('BAM Login Body ' + response.body)
38
- logger.debug('BAM Login Token ' + token[0].to_s)
39
- @@token = token[0].to_s
40
- end
41
-
42
- def rest_logout
43
- # logout from bam,
44
- logger.debug('BAM Logout ')
45
- response = HTTParty.get(format('%s://%s/Services/REST/v1/logout', @scheme, @host),
46
- headers: { 'Authorization' => 'BAMAuthToken: ' + @@token, 'Content-Type' => 'application/json' },
47
- verify: @verify)
48
- if response.code != 200
49
- logger.error('BAM Logout Failed. HTTP' + response.code.to_s + ' ' + response.body.to_s)
50
- end
51
- end
52
-
53
- def rest_get(endpoint, querystring)
54
- # wrapper function to for rest get requests
55
- logger.debug('BAM GET ' + endpoint + '?' + querystring)
56
-
57
- response = HTTParty.get(format('%s://%s/Services/REST/v1/%s?%s', @scheme, @host, endpoint, querystring),
58
- headers: { 'Authorization' => 'BAMAuthToken: ' + @@token, 'Content-Type' => 'application/json' },
59
- verify: @verify)
60
- # Session propably expired, refresh it and do the request again
61
- if response.code == 401
62
- rest_login
63
- response = HTTParty.get(format('%s://%s/Services/REST/v1/%s?%s', @scheme, @host, endpoint, querystring),
64
- headers: { 'Authorization' => 'BAMAuthToken: ' + @@token, 'Content-Type' => 'application/json' },
65
- verify: @verify)
66
- end
67
-
68
- if response.code != 200
69
- logger.error('BAM GET Failed. HTTP' + response.code.to_s + ' ' + response.body.to_s)
70
- return nil
71
- end
72
-
73
- response.body
74
- end
75
-
76
- def rest_post(endpoint, querystring)
77
- # wrapper function to for rest post requests
78
- logger.debug('BAM POST ' + endpoint + '?' + querystring)
79
- response = HTTParty.post(format('%s://%s/Services/REST/v1/%s?%s', @scheme, @host, endpoint, querystring),
80
- headers: { 'Authorization' => 'BAMAuthToken: ' + @@token, 'Content-Type' => 'application/json' },
81
- verify: @verify
82
- )
83
- # Session propably expired, refresh it and do the request again
84
- if response.code == 401
85
- rest_login
86
- response = HTTParty.post(format('%s://%s/Services/REST/v1/%s?%s', @scheme, @host, endpoint, querystring),
87
- headers: { 'Authorization' => 'BAMAuthToken: ' + @@token, 'Content-Type' => 'application/json' },
88
- verify: @verify)
89
- end
90
- if response.code != 200
91
- logger.error('BAM POST Failed. HTTP' + response.code.to_s + ' ' + response.body.to_s)
92
- return nil
93
- else
94
- return response.body
95
- end
96
- end
97
-
98
- def rest_put(endpoint, querystring)
99
- # wrapper function to for rest put requests
100
- logger.debug('BAM PUT ' + endpoint + '?' + querystring)
101
- response = HTTParty.put(format('%s://%s/Services/REST/v1/%s?%s', @scheme, @host, endpoint, querystring),
102
- headers: { 'Authorization' => 'BAMAuthToken: ' + @@token, 'Content-Type' => 'application/json' },
103
- verify: @verify)
104
- # Session propably expired, refresh it and do the request again
105
- if response.code == 401
106
- rest_login
107
- response = HTTParty.put(format('%s://%s/Services/REST/v1/%s?%s', @scheme, @host, endpoint, querystring),
108
- headers: { 'Authorization' => 'BAMAuthToken: ' + @@token, 'Content-Type' => 'application/json' },
109
- verify: @verify)
110
- end
111
- if response.code != 200
112
- logger.error('BAM PUT Failed. HTTP' + response.code.to_s + ' ' + response.body.to_s)
113
- return nil
114
- else
115
- return response.body
116
- end
117
- end
118
-
119
- def rest_delete(endpoint, querystring)
120
- # wrapper function to for rest delete requests
121
- logger.debug('BAM DELETE ' + endpoint + '?' + querystring)
122
- response = HTTParty.delete(format('%s://%s/Services/REST/v1/%s?%s', @scheme, @host, endpoint, querystring),
123
- headers: { 'Authorization' => 'BAMAuthToken: ' + @@token, 'Content-Type' => 'application/json' },
124
- verify: @verify)
125
-
126
- # Session propably expired, refresh it and do the request again
127
- if response.code == 401
128
- rest_login
129
- response = HTTParty.delete(format('%s://%s/Services/REST/v1/%s?%s', @scheme, @host, endpoint, querystring),
130
- headers: { 'Authorization' => 'BAMAuthToken: ' + @@token, 'Content-Type' => 'application/json' },
131
- verify: @verify)
132
- end
133
- if response.code != 200
134
- logger.error('BAM DELETE Failed. HTTP' + response.code.to_s + ' ' + response.body.to_s)
135
- return nil
136
- else
137
- return response.body
138
- end
139
- end
140
-
141
- def get_addressid_by_ip(ip)
142
- # helper function to get the object id of a ip by an ip address
143
- json = rest_get('getIP4Address', 'containerId=' + @config_id.to_s + '&address=' + ip)
144
- result = JSON.parse(json)
145
- return nil if result.empty?
146
- result['id'].to_s
147
- end
148
-
149
- def get_networkid_by_ip(ip)
150
- # helper function to get the object id of a subnet by an ip address
151
- logger.debug('BAM get_networkid_by_ip ' + ip)
152
- querystring = 'containerId=' + @config_id.to_s + '&type=IP4Network' + '&address=' + ip.to_s
153
- json = rest_get('getIPRangedByIP', querystring)
154
- result = JSON.parse(json)
155
- return nil if result.empty?
156
- result['id'].to_s
157
- end
158
-
159
- def get_network_by_ip(ip)
160
- # helper function to get the whole subnet informarions by an ip address
161
- logger.debug('BAM get_network_by_ip ' + ip)
162
- querystring = 'containerId=' + @config_id.to_s + '&type=IP4Network' + '&address=' + ip.to_s
163
- json = rest_get('getIPRangedByIP', querystring)
164
- result = JSON.parse(json)
165
- properties = parse_properties(result['properties'])
166
- properties['CIDR'].to_s
167
- end
168
-
169
- def parse_properties(properties)
170
- # helper function to parse the properties scheme of bluecat into a hash
171
- # => properies: a string that contains properties for the object in attribute=value format, with each separated by a | (pipe) character.
172
- # For example, a host record object may have a properties field such as ttl=123|comments=my comment|.
173
- properties = properties.split('|')
174
- h = {}
175
- properties.each do |property|
176
- h[property.split('=').first.to_s] = property.split('=').last.to_s
177
- end
178
- h
179
- end
180
-
181
- # public
182
- def add_host(options)
183
- # wrapper function to add the dhcp reservation and dns records
184
-
185
- # add the ip and hostname and mac as static
186
- rest_post('addDeviceInstance', 'configName=' + @config_name +
187
- '&ipAddressMode=PASS_VALUE' \
188
- '&ipEntity=' + options['ip'] +
189
- '&viewName=' + @view_name +
190
- '&zoneName=' + options['hostname'].split('.', 2).last +
191
- '&deviceName=' + options['hostname'] +
192
- '&recordName=' + options['hostname'] +
193
- '&macAddressMode=PASS_VALUE' \
194
- '&macEntity=' + options['mac'] +
195
- '&options=AllowDuplicateHosts=true%7C')
196
-
197
- address_id = get_addressid_by_ip(options['ip'])
198
-
199
- # update the state of the ip from static to dhcp reserved
200
- rest_put('changeStateIP4Address', 'addressId=' + address_id +
201
- '&targetState=MAKE_DHCP_RESERVED' \
202
- '&macAddress=' + options['mac'])
203
- # deploy the config
204
- rest_post('deployServerConfig', 'serverId=' + @server_id.to_s + '&properties=services=DHCP')
205
- # lets wait a little bit for the complete dhcp deploy
206
- sleep 3
207
- rest_post('deployServerConfig', 'serverId=' + @server_id.to_s + '&properties=services=DNS')
208
- nil
209
- end
210
-
211
- # public
212
- def remove_host(ip)
213
- # wrapper function to remove a dhcp reservation and dns records
214
- # deploy the config, without a clean config the removal fails sometimes
215
- rest_post('deployServerConfig', 'serverId=' + @server_id.to_s + '&properties=services=DHCP,DNS')
216
- # remove the ip and depending records
217
- rest_delete('deleteDeviceInstance', 'configName=' + @config_name + '&identifier=' + ip)
218
- # deploy the config again
219
- rest_post('deployServerConfig', 'serverId=' + @server_id.to_s + '&properties=services=DHCP,DNS')
220
- end
221
-
222
- # public
223
- def get_next_ip(netadress, start_ip, _end_ip)
224
- # fetches the next free address in a subnet
225
- networkid = get_networkid_by_ip(netadress)
226
-
227
- start_ip = IPAddress.parse(netadress).first if start_ip.to_s.empty?
228
-
229
- properties = 'offset=' + start_ip.to_s + '%7CexcludeDHCPRange=false'
230
- result = rest_get('getNextIP4Address', 'parentId=' + networkid.to_s + '&properties=' + properties)
231
- return if result.empty?
232
- result.tr('"', '')
233
- end
234
-
235
- # public
236
- def get_subnets
237
- # fetches all subnets under the parent_block
238
- json = rest_get('getEntities', 'parentId=' + @parent_block.to_s + '&type=IP4Network&start=0&count=10000')
239
- results = JSON.parse(json)
240
- subnets = []
241
- subnets = results.map do |result|
242
- properties = parse_properties(result['properties'])
243
- net = IPAddress.parse(properties['CIDR'])
244
- opts = { routers: [properties['gateway']] }
245
- ::Proxy::DHCP::Subnet.new(net.address, net.netmask, opts)
246
- end
247
- subnets.compact
248
- end
249
-
250
- # public
251
- def find_mysubnet(subnet_address)
252
- # fetches a subnet by its network address
253
- net = IPAddress.parse(get_network_by_ip(subnet_address))
254
- subnet = ::Proxy::DHCP::Subnet.new(net.address, net.netmask)
255
- subnet
256
- end
257
-
258
- # public
259
- def get_hosts(network_address)
260
- # fetches all dhcp reservations in a subnet
261
- netid = get_networkid_by_ip(network_address)
262
- net = IPAddress.parse(get_network_by_ip(network_address))
263
- subnet = ::Proxy::DHCP::Subnet.new(net.address, net.netmask)
264
-
265
- json = rest_get('getNetworkLinkedProperties', 'networkId=' + netid.to_s)
266
- results = JSON.parse(json)
267
-
268
- hosts = []
269
- results.each do |result|
270
- properties = parse_properties(result['properties'])
271
-
272
- # Static Addresses and Gateway are not needed here
273
- # if properties.length() >= 4
274
- # if properties["state"] == "Gateway" or properties["state"] == "Static"
275
- # address = properties[0].split("=").last()
276
- # macAddress = "00:00:00:00:00:00"
277
- # hosttag = properties[3].split("=").last().split(":")
278
- # name = hosttag[1] + "." + hosttag[3]
279
- # opts = {:hostname => name}
280
- # hosts.push(Proxy::DHCP::Reservation.new(name, address, macAddress, subnet, opts))
281
- # end
282
- # end
283
- next unless properties.length >= 5
284
- next unless properties['state'] == 'DHCP Reserved'
285
- hosttag = properties['host'].split(':')
286
- name = hosttag[1] + '.' + hosttag[3]
287
- opts = { hostname: name }
288
- hosts.push(Proxy::DHCP::Reservation.new(name, properties['address'], properties['macAddress'].tr('-', ':'), subnet, opts))
289
- end
290
- hosts.compact
291
- end
292
-
293
- # public
294
- def get_hosts_by_ip(ip)
295
- # fetches a host by its ip
296
- hosts = []
297
- net = IPAddress.parse(get_network_by_ip(ip))
298
- subnet = ::Proxy::DHCP::Subnet.new(net.address, net.netmask)
299
- ipid = get_addressid_by_ip(ip)
300
- return nil if ipid.to_s == '0'
301
- json = rest_get('getLinkedEntities', 'entityId=' + ipid + '&type=HostRecord&start=0&count=2')
302
- results = JSON.parse(json)
303
-
304
- if results.empty?
305
- # no host record on ip, fetch mac only
306
- json2 = rest_get('getIP4Address', 'containerId=' + @config_id.to_s + '&address=' + ip)
307
- result2 = JSON.parse(json2)
308
- properties2 = parse_properties(result2['properties'])
309
- mac_address = properties2['macAddress'].tr('-', ':')
310
- hosts.push(Proxy::DHCP::Reservation.new("", ip, mac_address, subnet, {}))
311
- else
312
- # host record on ip, return more infos
313
- results.each do |result|
314
- properties = parse_properties(result['properties'])
315
- opts = { hostname: properties['absoluteName'] }
316
-
317
- next unless properties['reverseRecord'].to_s == 'true'.to_s
318
- json2 = rest_get('getEntityById', 'id=' + ipid)
319
- result2 = JSON.parse(json2)
320
- properties2 = parse_properties(result2['properties'])
321
- mac_address = properties2['macAddress'].tr('-', ':')
322
- unless mac_address.empty?
323
- hosts.push(Proxy::DHCP::Reservation.new(properties['absoluteName'], ip, mac_address, subnet, opts))
324
- end
325
- end
326
- end
327
- hosts.compact
328
- end
329
-
330
- # public
331
- def get_host_by_mac(mac)
332
- # fetches all dhcp reservations by a mac
333
- json = rest_get('getMACAddress', 'configurationId=' + @config_id.to_s + '&macAddress=' + mac.to_s)
334
- result = JSON.parse(json)
335
- macid = result['id'].to_s
336
- return if macid == '0'
337
- json2 = rest_get('getLinkedEntities', 'entityId=' + macid + '&type=IP4Address&start=0&count=1')
338
- result2 = JSON.parse(json2)
339
- return if result2.empty?
340
- properties = parse_properties(result2[0]['properties'])
341
- host = get_hosts_by_ip(properties['address'])
342
- return if host.nil?
343
- host[0]
344
- end
345
- end
1
+ require 'httparty'
2
+ require 'ipaddress'
3
+ require 'json'
4
+
5
+ module Proxy
6
+ module DHCP
7
+ module BlueCat
8
+ ##
9
+ # This Class handles all commuincation to the bluecat address manager
10
+ class BlueCatAPI
11
+ include ::Proxy::Log
12
+
13
+ # connection mode to the address manager. http or https
14
+ attr_reader :scheme
15
+
16
+ # validate ssl connection. true or false
17
+ attr_reader :verify
18
+
19
+ # fqdn or ip of your bluecat address manager
20
+ attr_reader :host
21
+
22
+ # id of the parent_block that holds the subnets that you want to use
23
+ attr_reader :parent_block
24
+
25
+ # name of your dns view
26
+ attr_reader :view_name
27
+
28
+ # Name of your Bluecat configuration
29
+ attr_reader :config_name
30
+
31
+ # id of your Bluecat configuration
32
+ attr_reader :config_id
33
+
34
+ # id of the server that holds your dhcp
35
+ attr_reader :server_id
36
+
37
+ # credentials of your api user
38
+ attr_reader :username
39
+
40
+ # credentials of your api user
41
+ attr_reader :password
42
+
43
+ class << self
44
+ # contains the bluecat api token
45
+ attr_accessor :token
46
+ end
47
+
48
+ def initialize(scheme, verify, host, parent_block, view_name, config_name, config_id, server_id, username, password)
49
+ @scheme = scheme
50
+ @verify = verify
51
+ @host = host
52
+ @parent_block = parent_block
53
+ @view_name = view_name
54
+ @config_name = config_name
55
+ @config_id = config_id
56
+ @server_id = server_id
57
+ @username = username
58
+ @password = password
59
+ end
60
+
61
+ # login to bam, parse the session token
62
+ def rest_login
63
+ logger.debug('BAM Login ' + @scheme + ' ' + @host + ' ')
64
+ response = HTTParty.get(format('%s://%s/Services/REST/v1/login?username=%s&password=%s', @scheme, @host, @username, @password),
65
+ headers: { 'Content-Type' => 'text/plain' },
66
+ verify => @verify)
67
+ if response.code != 200
68
+ logger.error('BAM Login Failed. HTTP' + response.code.to_s + ' ' + response.body.to_s)
69
+ end
70
+ body = response.body.to_s
71
+ token = body.match(/BAMAuthToken:\s+(\S+)/).captures
72
+
73
+ logger.debug('BAM Login Body ' + response.body)
74
+ logger.debug('BAM Login Token ' + token[0].to_s)
75
+ self.class.token = token[0].to_s
76
+ end
77
+
78
+ # logout from bam
79
+ def rest_logout
80
+ logger.debug('BAM Logout ')
81
+ response = HTTParty.get(format('%s://%s/Services/REST/v1/logout', @scheme, @host),
82
+ headers: { 'Authorization' => 'BAMAuthToken: ' + self.class.token, 'Content-Type' => 'application/json' },
83
+ verify: @verify)
84
+ logger.error('BAM Logout Failed. HTTP' + response.code.to_s + ' ' + response.body.to_s) if response.code != 200
85
+ end
86
+
87
+ # wrapper function to for rest get requests
88
+ def rest_get(endpoint, querystring)
89
+ rest_login if self.class.token.nil?
90
+
91
+ logger.debug('BAM GET ' + endpoint + '?' + querystring)
92
+
93
+ response = HTTParty.get(format('%s://%s/Services/REST/v1/%s?%s', @scheme, @host, endpoint, querystring),
94
+ headers: { 'Authorization' => 'BAMAuthToken: ' + self.class.token, 'Content-Type' => 'application/json' },
95
+ verify: @verify)
96
+ # Session propably expired, refresh it and do the request again
97
+ if response.code == 401
98
+ rest_login
99
+ response = HTTParty.get(format('%s://%s/Services/REST/v1/%s?%s', @scheme, @host, endpoint, querystring),
100
+ headers: { 'Authorization' => 'BAMAuthToken: ' + self.class.token, 'Content-Type' => 'application/json' },
101
+ verify: @verify)
102
+ end
103
+
104
+ return response.body if response.code == 200
105
+ logger.error('BAM GET Failed. HTTP' + response.code.to_s + ' ' + response.body.to_s)
106
+ end
107
+
108
+ # wrapper function to for rest post requests
109
+ def rest_post(endpoint, querystring)
110
+ logger.debug('BAM POST ' + endpoint + '?' + querystring)
111
+ response = HTTParty.post(format('%s://%s/Services/REST/v1/%s?%s', @scheme, @host, endpoint, querystring),
112
+ headers: { 'Authorization' => 'BAMAuthToken: ' + self.class.token, 'Content-Type' => 'application/json' },
113
+ verify: @verify
114
+ )
115
+ # Session propably expired, refresh it and do the request again
116
+ if response.code == 401
117
+ rest_login
118
+ response = HTTParty.post(format('%s://%s/Services/REST/v1/%s?%s', @scheme, @host, endpoint, querystring),
119
+ headers: { 'Authorization' => 'BAMAuthToken: ' + self.class.token, 'Content-Type' => 'application/json' },
120
+ verify: @verify)
121
+ end
122
+ return response.body if response.code == 200
123
+ logger.error('BAM POST Failed. HTTP' + response.code.to_s + ' ' + response.body.to_s)
124
+ end
125
+
126
+ # wrapper function to for rest put requests
127
+ def rest_put(endpoint, querystring)
128
+ logger.debug('BAM PUT ' + endpoint + '?' + querystring)
129
+ response = HTTParty.put(format('%s://%s/Services/REST/v1/%s?%s', @scheme, @host, endpoint, querystring),
130
+ headers: { 'Authorization' => 'BAMAuthToken: ' + self.class.token, 'Content-Type' => 'application/json' },
131
+ verify: @verify)
132
+ # Session propably expired, refresh it and do the request again
133
+ if response.code == 401
134
+ rest_login
135
+ response = HTTParty.put(format('%s://%s/Services/REST/v1/%s?%s', @scheme, @host, endpoint, querystring),
136
+ headers: { 'Authorization' => 'BAMAuthToken: ' + self.class.token, 'Content-Type' => 'application/json' },
137
+ verify: @verify)
138
+ end
139
+ return response.body if response.code == 200
140
+ logger.error('BAM PUT Failed. HTTP' + response.code.to_s + ' ' + response.body.to_s)
141
+ end
142
+
143
+ # wrapper function to for rest delete requests
144
+ def rest_delete(endpoint, querystring)
145
+ logger.debug('BAM DELETE ' + endpoint + '?' + querystring)
146
+ response = HTTParty.delete(format('%s://%s/Services/REST/v1/%s?%s', @scheme, @host, endpoint, querystring),
147
+ headers: { 'Authorization' => 'BAMAuthToken: ' + self.class.token, 'Content-Type' => 'application/json' },
148
+ verify: @verify)
149
+
150
+ # Session propably expired, refresh it and do the request again
151
+ if response.code == 401
152
+ rest_login
153
+ response = HTTParty.delete(format('%s://%s/Services/REST/v1/%s?%s', @scheme, @host, endpoint, querystring),
154
+ headers: { 'Authorization' => 'BAMAuthToken: ' + self.class.token, 'Content-Type' => 'application/json' },
155
+ verify: @verify)
156
+ end
157
+ return response.body if response.code == 200
158
+ logger.error('BAM DELETE Failed. HTTP' + response.code.to_s + ' ' + response.body.to_s)
159
+ end
160
+
161
+ # helper function to get the object id of a ip by an ip address
162
+ def get_addressid_by_ip(ip)
163
+ json = rest_get('getIP4Address', 'containerId=' + @config_id.to_s + '&address=' + ip)
164
+ result = JSON.parse(json)
165
+ return nil if result.empty?
166
+ result['id'].to_s
167
+ end
168
+
169
+ # helper function to get the object id of a subnet by an ip address
170
+ def get_networkid_by_ip(ip)
171
+ logger.debug('BAM get_networkid_by_ip ' + ip)
172
+ querystring = 'containerId=' + @config_id.to_s + '&type=IP4Network' + '&address=' + ip.to_s
173
+ json = rest_get('getIPRangedByIP', querystring)
174
+ result = JSON.parse(json)
175
+ return nil if result.empty?
176
+ result['id'].to_s
177
+ end
178
+
179
+ # helper function to get the whole subnet informarions by an ip address
180
+ def get_network_by_ip(ip)
181
+ logger.debug('BAM get_network_by_ip ' + ip)
182
+ querystring = 'containerId=' + @config_id.to_s + '&type=IP4Network' + '&address=' + ip.to_s
183
+ json = rest_get('getIPRangedByIP', querystring)
184
+ result = JSON.parse(json)
185
+ properties = parse_properties(result['properties'])
186
+ properties['CIDR'].to_s
187
+ end
188
+
189
+ # helper function to parse the properties scheme of bluecat into a hash
190
+ #
191
+ # properies: a string that contains properties for the object in attribute=value format, with each separated by a | (pipe) character.
192
+ # For example, a host record object may have a properties field such as ttl=123|comments=my comment|.
193
+ def parse_properties(properties)
194
+ properties = properties.split('|')
195
+ h = {}
196
+ properties.each do |property|
197
+ h[property.split('=').first.to_s] = property.split('=').last.to_s
198
+ end
199
+ h
200
+ end
201
+
202
+ # public
203
+ # wrapper function to add the dhcp reservation and dns records
204
+ def add_host(options)
205
+ # add the ip and hostname and mac as static
206
+ rest_post('addDeviceInstance', 'configName=' + @config_name +
207
+ '&ipAddressMode=PASS_VALUE' \
208
+ '&ipEntity=' + options['ip'] +
209
+ '&viewName=' + @view_name +
210
+ '&zoneName=' + options['hostname'].split('.', 2).last +
211
+ '&deviceName=' + options['hostname'] +
212
+ '&recordName=' + options['hostname'] +
213
+ '&macAddressMode=PASS_VALUE' \
214
+ '&macEntity=' + options['mac'] +
215
+ '&options=AllowDuplicateHosts=true%7C')
216
+
217
+ address_id = get_addressid_by_ip(options['ip'])
218
+
219
+ # update the state of the ip from static to dhcp reserved
220
+ rest_put('changeStateIP4Address', 'addressId=' + address_id +
221
+ '&targetState=MAKE_DHCP_RESERVED' \
222
+ '&macAddress=' + options['mac'])
223
+
224
+ unless options['nextServer'].nil? or options['filename'].nil?
225
+ rest_post('addDHCPClientDeploymentOption', 'entityId=' + address_id.to_s + '&name=tftp-server-name' + "&value=" + options['nextServer'].to_s)
226
+ rest_post('addDHCPClientDeploymentOption', 'entityId=' + address_id.to_s + '&name=boot-file-name' + "&value=" + options['filename'].to_s)
227
+ end
228
+
229
+ # deploy the config
230
+ rest_post('deployServerConfig', 'serverId=' + @server_id.to_s + '&properties=services=DHCP')
231
+ # lets wait a little bit for the complete dhcp deploy
232
+ sleep 3
233
+ rest_post('deployServerConfig', 'serverId=' + @server_id.to_s + '&properties=services=DNS')
234
+ nil
235
+ end
236
+
237
+ # public
238
+ # wrapper function to remove a ip record and depending dns records
239
+ def remove_host(ip)
240
+ ipid = get_addressid_by_ip(ip)
241
+ json = rest_get('getLinkedEntities', 'entityId=' + ipid.to_s + '&type=HostRecord&start=0&count=2')
242
+ results = JSON.parse(json)
243
+
244
+ hosts = results.map do |result|
245
+ rest_delete('delete', 'objectId=' + result['id'].to_s)
246
+ end
247
+ rest_delete('delete', 'objectId=' + ipid.to_s)
248
+
249
+ rest_post('deployServerConfig', 'serverId=' + @server_id.to_s + '&properties=services=DHCP,DNS')
250
+ end
251
+
252
+ # public
253
+ # fetches the next free address in a subnet
254
+ # +end_ip not implemented+
255
+ def next_ip(netadress, start_ip, end_ip)
256
+ networkid = get_networkid_by_ip(netadress)
257
+
258
+ start_ip = IPAddress.parse(netadress).first if start_ip.to_s.empty?
259
+
260
+ properties = 'offset=' + start_ip.to_s + '%7CexcludeDHCPRange=false'
261
+ result = rest_get('getNextIP4Address', 'parentId=' + networkid.to_s + '&properties=' + properties)
262
+ return if result.empty?
263
+ result.tr('"', '')
264
+ end
265
+
266
+ # public
267
+ # fetches all subnets under the parent_block
268
+ def subnets
269
+ json = rest_get('getEntities', 'parentId=' + @parent_block.to_s + '&type=IP4Network&start=0&count=10000')
270
+ results = JSON.parse(json)
271
+ subnets = results.map do |result|
272
+ properties = parse_properties(result['properties'])
273
+ net = IPAddress.parse(properties['CIDR'])
274
+ opts = { routers: [properties['gateway']] }
275
+
276
+ if properties['gateway'].nil?
277
+ logger.error("subnet issue: " + properties['CIDR'] + " skipped, due missing gateway in bluecat")
278
+ next
279
+ end
280
+
281
+ ::Proxy::DHCP::Subnet.new(net.address, net.netmask, opts)
282
+ end
283
+ subnets.compact
284
+ end
285
+
286
+ # public
287
+ # fetches a subnet by its network address
288
+ def find_mysubnet(subnet_address)
289
+ net = IPAddress.parse(get_network_by_ip(subnet_address))
290
+ subnet = ::Proxy::DHCP::Subnet.new(net.address, net.netmask)
291
+ subnet
292
+ end
293
+
294
+ # public
295
+ # fetches all dhcp reservations in a subnet
296
+ def hosts(network_address)
297
+ netid = get_networkid_by_ip(network_address)
298
+ net = IPAddress.parse(get_network_by_ip(network_address))
299
+ subnet = ::Proxy::DHCP::Subnet.new(net.address, net.netmask)
300
+
301
+ json = rest_get('getNetworkLinkedProperties', 'networkId=' + netid.to_s)
302
+ results = JSON.parse(json)
303
+
304
+ hosts = results.map do |result|
305
+ properties = parse_properties(result['properties'])
306
+
307
+ ## Static Addresses and Gateway are not needed here
308
+ ## But lets keep the logic to identify them
309
+ # if properties.length() >= 4
310
+ # if properties["state"] == "Gateway" or properties["state"] == "Static"
311
+ # address = properties[0].split("=").last()
312
+ # macAddress = "00:00:00:00:00:00"
313
+ # hosttag = properties[3].split("=").last().split(":")
314
+ # name = hosttag[1] + "." + hosttag[3]
315
+ # opts = {:hostname => name}
316
+ # ::Proxy::DHCP::Reservation.new(name, address, macAddress, subnet, opts)
317
+ # end
318
+ # end
319
+ next unless properties.length >= 5
320
+ next unless properties['state'] == 'DHCP Reserved'
321
+ hosttag = properties['host'].split(':')
322
+ name = hosttag[1] + '.' + hosttag[3]
323
+ opts = { hostname: name }
324
+ ::Proxy::DHCP::Reservation.new(name, properties['address'], properties['macAddress'].tr('-', ':'), subnet, opts)
325
+ end
326
+ hosts.compact
327
+ end
328
+
329
+ # public
330
+ # fetches a host by its ip
331
+ def hosts_by_ip(ip)
332
+ hosts = []
333
+ net = IPAddress.parse(get_network_by_ip(ip))
334
+ subnet = ::Proxy::DHCP::Subnet.new(net.address, net.netmask)
335
+ ipid = get_addressid_by_ip(ip)
336
+ return nil if ipid.to_s == '0'
337
+ json = rest_get('getLinkedEntities', 'entityId=' + ipid + '&type=HostRecord&start=0&count=2')
338
+ results = JSON.parse(json)
339
+
340
+ if results.empty? || (results == "Link request is not supported")
341
+ # no host record on ip, fetch mac only
342
+ json2 = rest_get('getIP4Address', 'containerId=' + @config_id.to_s + '&address=' + ip)
343
+ result2 = JSON.parse(json2)
344
+ properties2 = parse_properties(result2['properties'])
345
+ unless properties2['macAddress'].nil?
346
+ mac_address = properties2['macAddress'].tr('-', ':')
347
+ hosts.push(Proxy::DHCP::Reservation.new("", ip, mac_address, subnet, {}))
348
+ end
349
+ else
350
+ # host record on ip, return more infos
351
+ results.each do |result|
352
+ properties = parse_properties(result['properties'])
353
+ opts = { hostname: properties['absoluteName'] }
354
+
355
+ next unless properties['reverseRecord'].to_s == 'true'.to_s
356
+ json2 = rest_get('getEntityById', 'id=' + ipid)
357
+ result2 = JSON.parse(json2)
358
+ properties2 = parse_properties(result2['properties'])
359
+ unless properties2['macAddress'].nil?
360
+ mac_address = properties2['macAddress'].tr('-', ':')
361
+ hosts.push(Proxy::DHCP::Reservation.new(properties['absoluteName'], ip, mac_address, subnet, opts))
362
+ end
363
+ end
364
+ end
365
+ hosts.compact
366
+ end
367
+
368
+ # public
369
+ # fetches all dhcp reservations by a mac
370
+ def host_by_mac(mac)
371
+ json = rest_get('getMACAddress', 'configurationId=' + @config_id.to_s + '&macAddress=' + mac.to_s)
372
+ result = JSON.parse(json)
373
+ macid = result['id'].to_s
374
+ return if macid == '0'
375
+ json2 = rest_get('getLinkedEntities', 'entityId=' + macid + '&type=IP4Address&start=0&count=1')
376
+ result2 = JSON.parse(json2)
377
+ return if result2.empty?
378
+ properties = parse_properties(result2[0]['properties'])
379
+ host = hosts_by_ip(properties['address'])
380
+ return if host.nil?
381
+ host[0]
382
+ end
383
+ end
384
+ end
385
+ end
386
+ end