smart_proxy_ipam 0.0.16 → 0.0.17

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2aabee347a7edd212a629d75d24d812cbe772d16cc7626ee7691d550c28091d8
4
- data.tar.gz: a516d9a3925ededc68b1a1738c9eb6d406e09e8769e042201fb28a86ba8071f0
3
+ metadata.gz: d0624875b34dd0cd44fcaa5446e22b5a9f4c2d9ee6426445ba8605fd80ee9df6
4
+ data.tar.gz: 9cecc6b2070e5fd02d1f42a7aa849b79a92d05dc18a660f2f5baf45af97bf368
5
5
  SHA512:
6
- metadata.gz: 4956e02e3637c56c9274a36488723d1a49099362d238b57bbc41a9549c39ea75ecc0af1f4a88738cf3d0636c1c154b9b7ab1a146b98a11cf898700f062bbe4cc
7
- data.tar.gz: d339ecc86c1d1f23e770fac2bd83d1f4245333a99b893c6cce847c47af29d89017e6c8f634f62c3f0f97cce5298f569dad7c28bedc55662d787a81fc5242327a
6
+ metadata.gz: 10ec0acd80412ff6cd523f99701cc1f57a61d67c6cf2e30b4c986c4cd61553443310ab32aa8f5116d8cbce8f5245c46c2a422f719f27d25cc5297550f5c465cf
7
+ data.tar.gz: c9324940694102cfccbf3088091f2a2fb6a978261d3ab2bb3d65fba88c7cd6d678dfc67a3b24f785035db4edaebd0b12729c4bb2df54d4c811bb7a58a11a0091
@@ -31,10 +31,15 @@ module Proxy::Phpipam
31
31
 
32
32
  mac = params[:mac]
33
33
  cidr = params[:address] + '/' + params[:prefix]
34
+ section_name = params[:group]
35
+
34
36
  phpipam_client = PhpipamClient.new
35
- subnet = JSON.parse(phpipam_client.get_subnet(cidr))
37
+ subnet = JSON.parse(phpipam_client.get_subnet(cidr, section_name))
38
+
36
39
  return {:code => subnet['code'], :error => subnet['message']}.to_json if no_subnets_found(subnet)
37
- ipaddr = phpipam_client.get_next_ip(subnet['data'][0]['id'], mac, cidr)
40
+
41
+ ipaddr = phpipam_client.get_next_ip(subnet['data']['id'], mac, cidr, section_name)
42
+
38
43
  return {:code => subnet['code'], :error => ipaddr['message']}.to_json if no_free_ip_found(JSON.parse(ipaddr))
39
44
 
40
45
  ipaddr
@@ -95,7 +100,7 @@ module Proxy::Phpipam
95
100
  # ]
96
101
  # Response if :error =>
97
102
  # {"error":"Unable to connect to phpIPAM server"}
98
- get '/sections' do
103
+ get '/groups' do
99
104
  content_type :json
100
105
 
101
106
  begin
@@ -107,6 +112,37 @@ module Proxy::Phpipam
107
112
  end
108
113
  end
109
114
 
115
+ # Get a single sections from external ipam
116
+ #
117
+ # Input: Section name
118
+ # Returns: A JSON section on success, hash with "error" key otherwise
119
+ # Examples
120
+ # Response if success:
121
+ # {"code":200,"success":true,"data":{"id":"80","name":"Development #1","description":null,
122
+ # "masterSection":"0","permissions":"[]","strictMode":"1","subnetOrdering":"default","order":null,
123
+ # "editDate":"2020-02-11 10:57:01","showVLAN":"1","showVRF":"1","showSupernetOnly":"1","DNS":null},"time":0.004}
124
+ # Response if not found:
125
+ # {"code":404,"error":"Not Found"}
126
+ # Response if :error =>
127
+ # {"error":"Unable to connect to phpIPAM server"}
128
+ get '/groups/:group_name' do
129
+ content_type :json
130
+
131
+ begin
132
+ err = validate_required_params(["group_name"], params)
133
+ return err if err.length > 0
134
+
135
+ phpipam_client = PhpipamClient.new
136
+ section = JSON.parse(phpipam_client.get_section(params[:group_name]))
137
+
138
+ return {:code => section['code'], :error => section['message']}.to_json if no_section_found(section)
139
+ section.to_json
140
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET
141
+ logger.debug(errors[:no_connection])
142
+ raise
143
+ end
144
+ end
145
+
110
146
  # Get a list of subnets for given external ipam section/group
111
147
  #
112
148
  # Input: section_name(string). The name of the external ipam section/group
@@ -162,16 +198,15 @@ module Proxy::Phpipam
162
198
  # }
163
199
  # Response if :error =>
164
200
  # {"error":"Unable to connect to External IPAM server"}
165
- get '/sections/:section_name/subnets' do
201
+ get '/groups/:group_name/subnets' do
166
202
  content_type :json
167
203
 
168
204
  begin
169
- err = validate_required_params(["section_name"], params)
205
+ err = validate_required_params(["group_name"], params)
170
206
  return err if err.length > 0
171
-
172
- section_name = CGI.unescape(params[:section_name])
207
+
173
208
  phpipam_client = PhpipamClient.new
174
- section = JSON.parse(phpipam_client.get_section(section_name))
209
+ section = JSON.parse(phpipam_client.get_section(params[:group_name]))
175
210
 
176
211
  return {:code => section['code'], :error => section['message']}.to_json if no_section_found(section)
177
212
 
@@ -204,12 +239,13 @@ module Proxy::Phpipam
204
239
 
205
240
  ip = params[:ip]
206
241
  cidr = params[:address] + '/' + params[:prefix]
242
+ section_name = params[:group]
207
243
  phpipam_client = PhpipamClient.new
208
- subnet = JSON.parse(phpipam_client.get_subnet(cidr))
244
+ subnet = JSON.parse(phpipam_client.get_subnet(cidr, section_name))
209
245
 
210
246
  return {:code => subnet['code'], :error => subnet['message']}.to_json if no_subnets_found(subnet)
211
247
 
212
- phpipam_client.ip_exists(ip, subnet['data'][0]['id'])
248
+ phpipam_client.ip_exists(ip, subnet['data']['id'])
213
249
  rescue Errno::ECONNREFUSED, Errno::ECONNRESET
214
250
  logger.debug(errors[:no_connection])
215
251
  raise
@@ -239,12 +275,14 @@ module Proxy::Phpipam
239
275
 
240
276
  ip = params[:ip]
241
277
  cidr = params[:address] + '/' + params[:prefix]
278
+ section_name = URI.escape(params[:group])
279
+
242
280
  phpipam_client = PhpipamClient.new
243
- subnet = JSON.parse(phpipam_client.get_subnet(cidr))
281
+ subnet = JSON.parse(phpipam_client.get_subnet(cidr, section_name))
244
282
 
245
283
  return {:code => subnet['code'], :error => subnet['message']}.to_json if no_subnets_found(subnet)
246
284
 
247
- phpipam_client.add_ip_to_subnet(ip, subnet['data'][0]['id'], 'Address auto added by Foreman')
285
+ phpipam_client.add_ip_to_subnet(ip, subnet['data']['id'], 'Address auto added by Foreman')
248
286
  rescue Errno::ECONNREFUSED, Errno::ECONNRESET
249
287
  logger.debug(errors[:no_connection])
250
288
  raise
@@ -272,14 +310,46 @@ module Proxy::Phpipam
272
310
 
273
311
  ip = params[:ip]
274
312
  cidr = params[:address] + '/' + params[:prefix]
313
+ section_name = URI.escape(params[:group])
275
314
  phpipam_client = PhpipamClient.new
276
- subnet = JSON.parse(phpipam_client.get_subnet(cidr))
315
+ subnet = JSON.parse(phpipam_client.get_subnet(cidr, section_name))
277
316
 
278
317
  return {:code => subnet['code'], :error => subnet['message']}.to_json if no_subnets_found(subnet)
279
318
 
280
- phpipam_client.delete_ip_from_subnet(ip, subnet['data'][0]['id'])
319
+ phpipam_client.delete_ip_from_subnet(ip, subnet['data']['id'])
281
320
  rescue Errno::ECONNREFUSED, Errno::ECONNRESET
282
- return {:error => errors[:no_connection]}.to_json
321
+ logger.debug(errors[:no_connection])
322
+ raise
323
+ end
324
+ end
325
+
326
+ # Checks whether a subnet exists in a specific section.
327
+ #
328
+ # Params: 1. address: The network address of the IPv4 or IPv6 subnet.
329
+ # 2. prefix: The subnet prefix(e.g. 24)
330
+ # 3. group: The name of the section
331
+ #
332
+ # Returns: JSON object with 'data' field is exists, otherwise field with 'error'
333
+ #
334
+ # Example:
335
+ # Response if exists:
336
+ # {"code":200,"success":true,"data":{"id":"147","subnet":"172.55.55.0","mask":"29","sectionId":"84","description":null,"linked_subnet":null,"firewallAddressObject":null,"vrfId":"0","masterSubnetId":"0","allowRequests":"0","vlanId":"0","showName":"0","device":"0","permissions":"[]","pingSubnet":"0","discoverSubnet":"0","resolveDNS":"0","DNSrecursive":"0","DNSrecords":"0","nameserverId":"0","scanAgent":"0","customer_id":null,"isFolder":"0","isFull":"0","tag":"2","threshold":"0","location":"0","editDate":null,"lastScan":null,"lastDiscovery":null,"calculation":{"Type":"IPv4","IP address":"\/","Network":"172.55.55.0","Broadcast":"172.55.55.7","Subnet bitmask":"29","Subnet netmask":"255.255.255.248","Subnet wildcard":"0.0.0.7","Min host IP":"172.55.55.1","Max host IP":"172.55.55.6","Number of hosts":"6","Subnet Class":false}},"time":0.009}
337
+ # Response if not exists:
338
+ # {"code":404,"error":"No subnet 172.66.66.0/29 found in section :group"}
339
+ get '/group/:group/subnet/:address/:prefix' do
340
+ content_type :json
341
+
342
+ begin
343
+ err = validate_required_params(["address", "prefix", "group"], params)
344
+ return err if err.length > 0
345
+
346
+ cidr = params[:address] + '/' + params[:prefix]
347
+
348
+ phpipam_client = PhpipamClient.new
349
+ phpipam_client.get_subnet_by_section(cidr, params[:group])
350
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET
351
+ logger.debug(errors[:no_connection])
352
+ raise
283
353
  end
284
354
  end
285
355
  end
@@ -4,6 +4,7 @@ require 'net/http'
4
4
  require 'monitor'
5
5
  require 'concurrent'
6
6
  require 'time'
7
+ require 'uri'
7
8
  require 'smart_proxy_ipam/ipam'
8
9
  require 'smart_proxy_ipam/ipam_main'
9
10
  require 'smart_proxy_ipam/phpipam/phpipam_helper'
@@ -14,7 +15,7 @@ module Proxy::Phpipam
14
15
  include PhpipamHelper
15
16
 
16
17
  MAX_RETRIES = 5
17
- DEFAULT_CLEANUP_INTERVAL = 120 # 2 mins
18
+ DEFAULT_CLEANUP_INTERVAL = 60 # 2 mins
18
19
  @@ip_cache = nil
19
20
  @@timer_task = nil
20
21
 
@@ -27,13 +28,35 @@ module Proxy::Phpipam
27
28
  start_cleanup_task if @@timer_task.nil?
28
29
  end
29
30
 
30
- def get_subnet(cidr)
31
+ def get_subnet(cidr, section_name = nil)
32
+ if section_name.nil? || section_name.empty?
33
+ get_subnet_by_cidr(cidr)
34
+ else
35
+ get_subnet_by_section(cidr, section_name)
36
+ end
37
+ end
38
+
39
+ def get_subnet_by_section(cidr, section_name)
40
+ section = JSON.parse(get_section(section_name))
41
+ subnets = JSON.parse(get_subnets(section['data']['id']))
42
+ subnet_id = nil
43
+
44
+ subnets['data'].each do |subnet|
45
+ subnet_cidr = subnet['subnet'] + '/' + subnet['mask']
46
+ subnet_id = subnet['id'] if subnet_cidr == cidr
47
+ end
48
+
49
+ return {:code => 404, :error => "No subnet #{cidr} found in section #{URI.unescape(section_name)}"}.to_json if subnet_id.nil?
50
+
51
+ response = get("subnets/#{subnet_id.to_s}/")
52
+ response.body
53
+ end
54
+
55
+ def get_subnet_by_cidr(cidr)
31
56
  response = get("subnets/cidr/#{cidr.to_s}")
32
57
  json_body = JSON.parse(response.body)
33
-
34
58
  return response.body if no_subnets_found(json_body)
35
-
36
- json_body['data'] = filter_fields(json_body, [:id, :subnet, :description, :mask])
59
+ json_body['data'] = filter_fields(json_body, [:id, :subnet, :description, :mask])[0]
37
60
  response.body = json_body.to_json
38
61
  response.header['Content-Length'] = json_body.to_s.length
39
62
  response.body
@@ -78,15 +101,17 @@ module Proxy::Phpipam
78
101
  response.body
79
102
  end
80
103
 
81
- def get_next_ip(subnet_id, mac, cidr)
104
+ def get_next_ip(subnet_id, mac, cidr, section_name)
82
105
  response = get("subnets/#{subnet_id.to_s}/first_free/")
83
106
  json_body = JSON.parse(response.body)
84
- subnet_hash = @@ip_cache[cidr.to_sym]
107
+ section = section_name.nil? ? "" : section_name
108
+ @@ip_cache[section.to_sym] = {} if @@ip_cache[section.to_sym].nil?
109
+ subnet_hash = @@ip_cache[section.to_sym][cidr.to_sym]
85
110
 
86
111
  return {:code => json_body['code'], :error => json_body['message']}.to_json if json_body['message']
87
112
 
88
113
  if subnet_hash && subnet_hash.key?(mac.to_sym)
89
- json_body['data'] = @@ip_cache[cidr.to_sym][mac.to_sym][:ip]
114
+ json_body['data'] = @@ip_cache[section_name.to_sym][cidr.to_sym][mac.to_sym][:ip]
90
115
  else
91
116
  next_ip = nil
92
117
  new_ip = json_body['data']
@@ -94,9 +119,9 @@ module Proxy::Phpipam
94
119
 
95
120
  if ip_not_in_cache
96
121
  next_ip = new_ip.to_s
97
- add_ip_to_cache(new_ip, mac, cidr)
122
+ add_ip_to_cache(new_ip, mac, cidr, section)
98
123
  else
99
- next_ip = find_new_ip(subnet_id, new_ip, mac, cidr)
124
+ next_ip = find_new_ip(subnet_id, new_ip, mac, cidr, section)
100
125
  end
101
126
 
102
127
  return {:code => 404, :error => "Unable to find another available IP address in subnet #{cidr}"}.to_json if next_ip.nil?
@@ -119,49 +144,68 @@ module Proxy::Phpipam
119
144
  private
120
145
 
121
146
  # @@ip_cache structure
147
+ #
148
+ # Groups of subnets are cached under the External IPAM Group name. For example,
149
+ # "IPAM Group Name" would be the section name in phpIPAM. All IP's cached for subnets
150
+ # that do not have an External IPAM group specified, they are cached under the "" key.
151
+ #
122
152
  # {
123
- # "100.55.55.0/24":{
124
- # "00:0a:95:9d:68:10": {"ip": "100.55.55.1", "timestamp": "2019-09-17 12:03:43 -D400"}
153
+ # "": {
154
+ # "100.55.55.0/24":{
155
+ # "00:0a:95:9d:68:10": {"ip": "100.55.55.1", "timestamp": "2019-09-17 12:03:43 -D400"}
156
+ # },
157
+ # },
158
+ # "IPAM Group Name": {
159
+ # "123.11.33.0/24":{
160
+ # "00:0a:95:9d:68:33": {"ip": "123.11.33.1", "timestamp": "2019-09-17 12:04:43 -0400"},
161
+ # "00:0a:95:9d:68:34": {"ip": "123.11.33.2", "timestamp": "2019-09-17 12:05:48 -0400"},
162
+ # "00:0a:95:9d:68:35": {"ip": "123.11.33.3", "timestamp:: "2019-09-17 12:06:50 -0400"}
163
+ # }
125
164
  # },
126
- # "123.11.33.0/24":{
127
- # "00:0a:95:9d:68:33": {"ip": "123.11.33.1", "timestamp": "2019-09-17 12:04:43 -0400"}
128
- # "00:0a:95:9d:68:34": {"ip": "123.11.33.2", "timestamp": "2019-09-17 12:05:48 -0400"}
129
- # "00:0a:95:9d:68:35": {"ip": "123.11.33.3", "timestamp:: "2019-09-17 12:06:50 -0400"}
165
+ # "Another IPAM Group": {
166
+ # "185.45.39.0/24":{
167
+ # "00:0a:95:9d:68:55": {"ip": "185.45.39.1", "timestamp": "2019-09-17 12:04:43 -0400"},
168
+ # "00:0a:95:9d:68:56": {"ip": "185.45.39.2", "timestamp": "2019-09-17 12:05:48 -0400"}
169
+ # }
130
170
  # }
131
171
  # }
132
172
  def init_cache
133
- logger.debug("Clearing ip cache.")
134
173
  @@m.synchronize do
135
174
  if @@ip_cache and not @@ip_cache.empty?
136
- @@ip_cache.each do |key, values|
137
- values.each do |mac, value|
138
- if Time.now - Time.parse(value[:timestamp]) > DEFAULT_CLEANUP_INTERVAL
139
- @@ip_cache[key].delete(mac)
175
+ logger.debug("Processing ip cache.")
176
+ @@ip_cache.each do |section, subnets|
177
+ subnets.each do |cidr, macs|
178
+ macs.each do |mac, ip|
179
+ if Time.now - Time.parse(ip[:timestamp]) > DEFAULT_CLEANUP_INTERVAL
180
+ @@ip_cache[section][cidr].delete(mac)
181
+ end
140
182
  end
183
+ @@ip_cache[section].delete(cidr) if @@ip_cache[section][cidr].nil? or @@ip_cache[section][cidr].empty?
141
184
  end
142
- @@ip_cache.delete(key) if @@ip_cache[key].nil? or @@ip_cache[key].empty?
143
185
  end
144
186
  else
145
- @@ip_cache = {}
187
+ logger.debug("Clearing ip cache.")
188
+ @@ip_cache = {:"" => {}}
146
189
  end
147
190
  end
148
191
  end
149
192
 
150
- def add_ip_to_cache(ip, mac, cidr)
151
- logger.debug("Adding IP #{ip} to cache for subnet #{cidr}")
193
+ def add_ip_to_cache(ip, mac, cidr, section_name)
194
+ logger.debug("Adding IP #{ip} to cache for subnet #{cidr} in section #{section_name}")
152
195
  @@m.synchronize do
153
196
  # Clear cache data which has the same mac and ip with the new one
154
- @@ip_cache.each do |key, values|
197
+ section_hash = @@ip_cache[section_name.to_sym]
198
+ section_hash.each do |key, values|
155
199
  if values.keys.include? mac.to_sym
156
- @@ip_cache[key].delete(mac.to_sym)
200
+ @@ip_cache[section_name.to_sym][key].delete(mac.to_sym)
157
201
  end
158
- @@ip_cache.delete(key) if @@ip_cache[key].nil? or @@ip_cache[key].empty?
202
+ @@ip_cache[section_name.to_sym].delete(key) if @@ip_cache[section_name.to_sym][key].nil? or @@ip_cache[section_name.to_sym][key].empty?
159
203
  end
160
-
161
- if @@ip_cache.key?(cidr.to_sym)
162
- @@ip_cache[cidr.to_sym][mac.to_sym] = {:ip => ip.to_s, :timestamp => Time.now.to_s}
204
+
205
+ if section_hash.key?(cidr.to_sym)
206
+ @@ip_cache[section_name.to_sym][cidr.to_sym][mac.to_sym] = {:ip => ip.to_s, :timestamp => Time.now.to_s}
163
207
  else
164
- @@ip_cache = @@ip_cache.merge({cidr.to_sym => {mac.to_sym => {:ip => ip.to_s, :timestamp => Time.now.to_s}}})
208
+ @@ip_cache = @@ip_cache.merge({section_name.to_sym => {cidr.to_sym => {mac.to_sym => {:ip => ip.to_s, :timestamp => Time.now.to_s}}}})
165
209
  end
166
210
  end
167
211
  end
@@ -169,7 +213,7 @@ module Proxy::Phpipam
169
213
  # Called when next available IP from external IPAM has been cached by another user/host, but
170
214
  # not actually persisted in external IPAM. Will increment the IP(MAX_RETRIES times), and
171
215
  # see if it is available in external IPAM.
172
- def find_new_ip(subnet_id, ip, mac, cidr)
216
+ def find_new_ip(subnet_id, ip, mac, cidr, section_name)
173
217
  found_ip = nil
174
218
  temp_ip = ip
175
219
  retry_count = 0
@@ -179,9 +223,9 @@ module Proxy::Phpipam
179
223
  verify_ip = JSON.parse(ip_exists(new_ip, subnet_id))
180
224
 
181
225
  # If new IP doesn't exist in IPAM and not in the cache
182
- if ip_not_found_in_ipam(verify_ip) && !ip_exists_in_cache(new_ip, cidr, mac)
226
+ if ip_not_found_in_ipam(verify_ip) && !ip_exists_in_cache(new_ip, cidr, mac, section_name)
183
227
  found_ip = new_ip.to_s
184
- add_ip_to_cache(found_ip, mac, cidr)
228
+ add_ip_to_cache(found_ip, mac, cidr, section_name)
185
229
  break
186
230
  end
187
231
 
@@ -200,8 +244,8 @@ module Proxy::Phpipam
200
244
  IPAddr.new(ip.to_s).succ.to_s
201
245
  end
202
246
 
203
- def ip_exists_in_cache(ip, cidr, mac)
204
- @@ip_cache[cidr.to_sym] && @@ip_cache[cidr.to_sym].to_s.include?(ip.to_s)
247
+ def ip_exists_in_cache(ip, cidr, mac, section_name)
248
+ @@ip_cache[section_name.to_sym][cidr.to_sym] && @@ip_cache[section_name.to_sym][cidr.to_sym].to_s.include?(ip.to_s)
205
249
  end
206
250
 
207
251
  # Checks if given IP is within a subnet. Broadcast address is considered unusable
@@ -10,7 +10,7 @@ module PhpipamHelper
10
10
  end
11
11
 
12
12
  def no_subnets_found(subnet)
13
- !subnet.kind_of?(Array) && subnet['message'] && subnet['message'].downcase == "no subnets found"
13
+ subnet['message'] && subnet['message'].downcase == "no subnets found"
14
14
  end
15
15
 
16
16
  def no_section_found(section)
@@ -1,6 +1,6 @@
1
1
 
2
2
  module Proxy
3
3
  module Ipam
4
- VERSION = '0.0.16'
4
+ VERSION = '0.0.17'
5
5
  end
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_proxy_ipam
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.16
4
+ version: 0.0.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christopher Smith
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-12-20 00:00:00.000000000 Z
11
+ date: 2020-02-17 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Smart proxy plugin for IPAM integration with various IPAM providers
14
14
  email: chrisjsmith001@gmail.com