smart_proxy_ipam 0.0.16 → 0.0.17

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
  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