smart_proxy_dhcp_bluecat 0.1.0 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
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