smart_proxy_ipam 0.0.20 → 0.1.2
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 +4 -4
- data/README.md +31 -26
- data/bundler.d/ipam.rb +0 -1
- data/lib/smart_proxy_ipam.rb +2 -0
- data/lib/smart_proxy_ipam/api_resource.rb +54 -0
- data/lib/smart_proxy_ipam/dependency_injection.rb +9 -0
- data/lib/smart_proxy_ipam/ip_cache.rb +137 -0
- data/lib/smart_proxy_ipam/ipam.rb +11 -6
- data/lib/smart_proxy_ipam/ipam_api.rb +390 -0
- data/lib/smart_proxy_ipam/ipam_helper.rb +110 -0
- data/lib/smart_proxy_ipam/ipam_http_config.ru +2 -3
- data/lib/smart_proxy_ipam/ipam_validator.rb +48 -0
- data/lib/smart_proxy_ipam/netbox/netbox_client.rb +198 -0
- data/lib/smart_proxy_ipam/netbox/netbox_plugin.rb +17 -0
- data/lib/smart_proxy_ipam/phpipam/phpipam_client.rb +120 -277
- data/lib/smart_proxy_ipam/phpipam/phpipam_plugin.rb +17 -0
- data/lib/smart_proxy_ipam/version.rb +1 -2
- data/settings.d/externalipam.yml.example +1 -6
- data/settings.d/externalipam_netbox.yml.example +3 -0
- data/settings.d/externalipam_phpipam.yml.example +4 -0
- metadata +19 -11
- data/lib/smart_proxy_ipam/ipam_main.rb +0 -11
- data/lib/smart_proxy_ipam/phpipam/phpipam_api.rb +0 -377
- data/lib/smart_proxy_ipam/phpipam/phpipam_helper.rb +0 -62
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9b450f64adc45001ed1f2b969a0291bbb1237fd4077411aca7fe28c54a373cc9
|
4
|
+
data.tar.gz: f38780552bb70c0669a2e34758cda24a97f14ad31d147d13d0420f3eda01247b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 932a9e52aa6c6a507a472e52a9fa3bd6e97af23bda10302ac5913ab93557bc69661c49500ad33d3fff5d375b2037cd2cf5e04fa58d0f559ad6473f5940da726c
|
7
|
+
data.tar.gz: 292349dbefc9d9abcb4d9b3920e0475d5ed90caf7d460123b389d2dbd7a76a1417721fca1c5a3d03855fd774c78813fcc914bbef0d7e6c9ffa1a095a704b0d45
|
data/README.md
CHANGED
@@ -2,11 +2,9 @@
|
|
2
2
|
|
3
3
|
Foreman Smart Proxy plugin for IPAM integration with various IPAM providers.
|
4
4
|
|
5
|
-
Currently supported Providers:
|
6
|
-
1. [phpIPAM](https://phpipam.net/).
|
7
|
-
|
8
|
-
Provides a basic Dashboard for viewing phpIPAM sections, subnets. Also supports obtaining of the next available IPv4 address for a given subnet(via [IPAM Smart Proxy Plugin](https://github.com/grizzthedj/smart_proxy_ipam)).
|
9
|
-
|
5
|
+
Currently supported Providers:
|
6
|
+
1. [phpIPAM](https://phpipam.net/).
|
7
|
+
2. [NetBox](https://github.com/netbox-community/netbox).
|
10
8
|
|
11
9
|
## Installation
|
12
10
|
|
@@ -15,14 +13,23 @@ for how to install Foreman plugins
|
|
15
13
|
|
16
14
|
## Usage
|
17
15
|
|
18
|
-
Once plugin is installed, you can use
|
16
|
+
Once plugin is installed, you can use an External IPAM to get the next available IP address in subnets.
|
17
|
+
|
18
|
+
1. Create a subnet in Foreman of IPAM type "External IPAM". Click on the `Proxy` tab and associate the subnet with a Smart Proxy that has the `externalipam` feature enabled. _NOTE: This subnet must actually exist in External IPAM. There is no integration with subnet creation at this time._
|
19
|
+
2. Create a host in Foreman. When adding/editing interfaces, select the above created subnet, and the next available IP in the selected subnet will be pulled from phpIPAM, and displayed in the IPv4/IPv6 address field.
|
20
|
+
|
21
|
+
### phpIPAM
|
22
|
+
1. Create a User and API Key in phpIPAM, and ensure they are both named exactly the same.
|
23
|
+
2. The "App Security" setting for your API key should be "User token"
|
24
|
+
3. Add the url and User name and password to the configuration at `/etc/foreman-proxy/settings.d/externalipam_phpipam.yml`
|
19
25
|
|
20
|
-
|
21
|
-
|
26
|
+
### NetBox
|
27
|
+
1. Obtain an API token via a user profile in Netbox.
|
28
|
+
2. Add the token and the url to your NetBox instance to the configuration in `/etc/foreman-proxy/settings.d/externalipam_netbox.yml`
|
22
29
|
|
23
30
|
## Local development
|
24
31
|
|
25
|
-
1. Clone the Foreman repo
|
32
|
+
1. Clone the Foreman repo
|
26
33
|
```
|
27
34
|
git clone https://github.com/theforeman/foreman.git
|
28
35
|
```
|
@@ -30,48 +37,46 @@ git clone https://github.com/theforeman/foreman.git
|
|
30
37
|
```
|
31
38
|
git clone https://github.com/theforeman/smart-proxy
|
32
39
|
```
|
33
|
-
3. Fork
|
40
|
+
3. Fork the Smart Proxy IPAM plugin repo, then clone
|
34
41
|
|
35
|
-
foreman_ipam repo: https://github.com/grizzthedj/foreman_ipam
|
36
42
|
smart_proxy_ipam repo: https://github.com/grizzthedj/smart_proxy_ipam
|
37
43
|
|
38
44
|
```
|
39
|
-
git clone https://github.com/<GITHUB_USER>/foreman_ipam
|
40
45
|
git clone https://github.com/<GITHUB_USER>/smart_proxy_ipam
|
41
46
|
```
|
42
|
-
4.
|
43
|
-
```
|
44
|
-
gem 'foreman_ipam', :path => '/path/to/foreman_ipam'
|
45
|
-
```
|
46
|
-
5. From Foreman root directory run
|
47
|
+
4. From Foreman root directory run
|
47
48
|
```
|
48
49
|
bundle install
|
49
50
|
bundle exec rails db:migrate
|
50
51
|
bundle exec rails db:seed # This adds 'External IPAM' feature to Features table
|
51
52
|
bundle exec foreman start
|
52
53
|
```
|
53
|
-
|
54
|
+
5. Add the smart_proxy_ipam plugin to `Gemfile.local.rb` in Smart Proxy bundler.d directory
|
54
55
|
```
|
55
56
|
gem 'smart_proxy_ipam', :path => '/path/to/smart_proxy_ipam'
|
56
57
|
```
|
57
|
-
|
58
|
-
|
58
|
+
6. Copy `config/settings.d/externalipam.yml.example` to `config/settings.d/externalipam.yml`, and set `enabled` to true, and `use_provider` to `externalipam_phpipam` or `externalipam_netbox`.
|
59
|
+
7. Copy `config/settings.d/externalipam_phpipam.yml.example` to `config/settings.d/externalipam_phpipam.yml` and replace values with your phpIPAM URL and credentials.
|
60
|
+
8. Copy `config/settings.d/externalipam_netbox.yml.example` to `config/settings.d/externalipam_netbox.yml` and replace values with your Netbox URL and API token.
|
61
|
+
9. From Smart Proxy root directory run ...
|
59
62
|
```
|
60
63
|
bundle install
|
61
64
|
bundle exec smart-proxy start
|
62
65
|
```
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
66
|
+
10. Navigate to Foreman UI at http://localhost:5000
|
67
|
+
11. Add a Local Smart Proxy in the Foreman UI(Infrastructure => Smart Proxies)
|
68
|
+
12. Ensure that the `External IPAM` feature is present on the proxy(http://localhost:8000/features)
|
69
|
+
13. Create a Subnet(IPv4 or IPv6), and associate the subnet with the `External IPAM` proxy. Subnet must exist in phpIPAM.
|
70
|
+
14. Create a Host, and select an External IPAM Subnet to obtain the next available IP from phpIPAM
|
71
|
+
NOTE: For IPv6 subnets only, if the subnet has no addresses reserved(i.e. empty), the first address returned is actually the network address(e.g. `fd13:6d20:29dc:cf27::`), which is not a valid IP. This is a bug within phpIPAM itself
|
72
|
+
|
68
73
|
## Contributing
|
69
74
|
|
70
75
|
Fork and send a Pull Request. Thanks!
|
71
76
|
|
72
77
|
## Copyright
|
73
78
|
|
74
|
-
Copyright (c) *
|
79
|
+
Copyright (c) *2020* *Christopher Smith*
|
75
80
|
|
76
81
|
This program is free software: you can redistribute it and/or modify
|
77
82
|
it under the terms of the GNU General Public License as published by
|
data/bundler.d/ipam.rb
CHANGED
data/lib/smart_proxy_ipam.rb
CHANGED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'json'
|
3
|
+
require 'net/http'
|
4
|
+
require 'uri'
|
5
|
+
require 'smart_proxy_ipam/ipam_helper'
|
6
|
+
|
7
|
+
module Proxy::Ipam
|
8
|
+
# Class to handle authentication and HTTP transactions with External IPAM providers
|
9
|
+
class ApiResource
|
10
|
+
include ::Proxy::Log
|
11
|
+
include Proxy::Ipam::IpamHelper
|
12
|
+
|
13
|
+
def initialize(params = {})
|
14
|
+
@api_base = params[:api_base]
|
15
|
+
@token = params[:token]
|
16
|
+
@auth_header = params[:auth_header] || 'Authorization'
|
17
|
+
end
|
18
|
+
|
19
|
+
def get(path)
|
20
|
+
uri = URI(@api_base + path)
|
21
|
+
request = Net::HTTP::Get.new(uri)
|
22
|
+
request[@auth_header] = @token
|
23
|
+
request['Accept'] = 'application/json'
|
24
|
+
|
25
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
26
|
+
http.request(request)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def delete(path)
|
31
|
+
uri = URI(@api_base + path)
|
32
|
+
request = Net::HTTP::Delete.new(uri)
|
33
|
+
request[@auth_header] = @token
|
34
|
+
request['Accept'] = 'application/json'
|
35
|
+
|
36
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
37
|
+
http.request(request)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def post(path, body = nil)
|
42
|
+
uri = URI(@api_base + path)
|
43
|
+
request = Net::HTTP::Post.new(uri)
|
44
|
+
request.body = body
|
45
|
+
request[@auth_header] = @token
|
46
|
+
request['Accept'] = 'application/json'
|
47
|
+
request['Content-Type'] = 'application/json'
|
48
|
+
|
49
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
50
|
+
http.request(request)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'json'
|
3
|
+
require 'monitor'
|
4
|
+
require 'concurrent'
|
5
|
+
require 'time'
|
6
|
+
require 'smart_proxy_ipam/ipam_helper'
|
7
|
+
require 'singleton'
|
8
|
+
|
9
|
+
module Proxy::Ipam
|
10
|
+
# Class for managing temp in-memory cache to prevent same IP's being suggested in race conditions
|
11
|
+
class IpCache
|
12
|
+
include Singleton
|
13
|
+
include Proxy::Log
|
14
|
+
include Proxy::Ipam::IpamHelper
|
15
|
+
|
16
|
+
DEFAULT_CLEANUP_INTERVAL = 60
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@m = Monitor.new
|
20
|
+
init_cache
|
21
|
+
start_cleanup_task
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_provider(provider)
|
25
|
+
@provider = provider
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_provider
|
29
|
+
@provider
|
30
|
+
end
|
31
|
+
|
32
|
+
def set_group(group, value)
|
33
|
+
@ip_cache[group.to_sym] = value
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_group(group)
|
37
|
+
@ip_cache[group.to_sym]
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_cidr(group, cidr)
|
41
|
+
@ip_cache[group.to_sym][cidr.to_sym]
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_ip(group_name, cidr, mac)
|
45
|
+
@ip_cache[group_name.to_sym][cidr.to_sym][mac.to_sym][:ip]
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_cleanup_interval
|
49
|
+
DEFAULT_CLEANUP_INTERVAL
|
50
|
+
end
|
51
|
+
|
52
|
+
def ip_exists(ip, cidr, group_name)
|
53
|
+
cidr_key = @ip_cache[group_name.to_sym][cidr.to_sym]&.to_s
|
54
|
+
cidr_key.include?(ip.to_s)
|
55
|
+
end
|
56
|
+
|
57
|
+
def add(ip, mac, cidr, group_name)
|
58
|
+
logger.debug("Adding IP '#{ip}' to cache for subnet '#{cidr}' in group '#{group_name}' for IPAM provider #{@provider.to_s}")
|
59
|
+
@m.synchronize do
|
60
|
+
mac_addr = mac.nil? || mac.empty? ? SecureRandom.uuid : mac
|
61
|
+
group_hash = @ip_cache[group_name.to_sym]
|
62
|
+
|
63
|
+
group_hash.each do |key, values|
|
64
|
+
if values.keys.include? mac_addr.to_sym
|
65
|
+
@ip_cache[group_name.to_sym][key].delete(mac_addr.to_sym)
|
66
|
+
end
|
67
|
+
@ip_cache[group_name.to_sym].delete(key) if @ip_cache[group_name.to_sym][key].nil? || @ip_cache[group_name.to_sym][key].empty?
|
68
|
+
end
|
69
|
+
|
70
|
+
if group_hash.key?(cidr.to_sym)
|
71
|
+
@ip_cache[group_name.to_sym][cidr.to_sym][mac_addr.to_sym] = {ip: ip.to_s, timestamp: Time.now.to_s}
|
72
|
+
else
|
73
|
+
@ip_cache = @ip_cache.merge({group_name.to_sym => {cidr.to_sym => {mac_addr.to_sym => {ip: ip.to_s, timestamp: Time.now.to_s}}}})
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def start_cleanup_task
|
81
|
+
logger.info("Starting ip cache maintenance for IPAM provider #{@provider.to_s}, used by /next_ip.")
|
82
|
+
@timer_task = Concurrent::TimerTask.new(execution_interval: DEFAULT_CLEANUP_INTERVAL) { init_cache }
|
83
|
+
@timer_task.execute
|
84
|
+
end
|
85
|
+
|
86
|
+
# @ip_cache structure
|
87
|
+
#
|
88
|
+
# Groups of subnets are cached under the External IPAM Group name. For example,
|
89
|
+
# "IPAM Group Name" would be the section name in phpIPAM. All IP's cached for subnets
|
90
|
+
# that do not have an External IPAM group specified, they are cached under the "" key. IP's
|
91
|
+
# are cached using one of two possible keys:
|
92
|
+
# 1). Mac Address
|
93
|
+
# 2). UUID (Used when Mac Address not specified)
|
94
|
+
#
|
95
|
+
# {
|
96
|
+
# "": {
|
97
|
+
# "100.55.55.0/24":{
|
98
|
+
# "00:0a:95:9d:68:10": {"ip": "100.55.55.1", "timestamp": "2019-09-17 12:03:43 -D400"},
|
99
|
+
# "906d8bdc-dcc0-4b59-92cb-665935e21662": {"ip": "100.55.55.2", "timestamp": "2019-09-17 11:43:22 -D400"}
|
100
|
+
# },
|
101
|
+
# },
|
102
|
+
# "IPAM Group Name": {
|
103
|
+
# "123.11.33.0/24":{
|
104
|
+
# "00:0a:95:9d:68:33": {"ip": "123.11.33.1", "timestamp": "2019-09-17 12:04:43 -0400"},
|
105
|
+
# "00:0a:95:9d:68:34": {"ip": "123.11.33.2", "timestamp": "2019-09-17 12:05:48 -0400"},
|
106
|
+
# "00:0a:95:9d:68:35": {"ip": "123.11.33.3", "timestamp:: "2019-09-17 12:06:50 -0400"}
|
107
|
+
# }
|
108
|
+
# },
|
109
|
+
# "Another IPAM Group": {
|
110
|
+
# "185.45.39.0/24":{
|
111
|
+
# "00:0a:95:9d:68:55": {"ip": "185.45.39.1", "timestamp": "2019-09-17 12:04:43 -0400"},
|
112
|
+
# "00:0a:95:9d:68:56": {"ip": "185.45.39.2", "timestamp": "2019-09-17 12:05:48 -0400"}
|
113
|
+
# }
|
114
|
+
# }
|
115
|
+
# }
|
116
|
+
def init_cache
|
117
|
+
@m.synchronize do
|
118
|
+
if @ip_cache && !@ip_cache.empty?
|
119
|
+
logger.debug("Processing ip cache for IPAM provider #{@provider.to_s}")
|
120
|
+
@ip_cache.each do |group, subnets|
|
121
|
+
subnets.each do |cidr, macs|
|
122
|
+
macs.each do |mac, ip|
|
123
|
+
if Time.now - Time.parse(ip[:timestamp]) > DEFAULT_CLEANUP_INTERVAL
|
124
|
+
@ip_cache[group][cidr].delete(mac)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
@ip_cache[group].delete(cidr) if @ip_cache[group][cidr].nil? || @ip_cache[group][cidr].empty?
|
128
|
+
end
|
129
|
+
end
|
130
|
+
else
|
131
|
+
logger.debug("Clearing ip cache for IPAM provider #{@provider.to_s}")
|
132
|
+
@ip_cache = {'': {}}
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -1,11 +1,16 @@
|
|
1
|
-
|
2
1
|
module Proxy::Ipam
|
3
|
-
class NotFound < RuntimeError; end
|
4
|
-
|
5
2
|
class Plugin < ::Proxy::Plugin
|
6
|
-
plugin
|
3
|
+
plugin :externalipam, Proxy::Ipam::VERSION
|
4
|
+
|
5
|
+
uses_provider
|
6
|
+
default_settings use_provider: 'externalipam_phpipam'
|
7
|
+
|
8
|
+
load_classes(proc do
|
9
|
+
require 'smart_proxy_ipam/dependency_injection'
|
10
|
+
require 'smart_proxy_ipam/ipam_api'
|
11
|
+
end)
|
7
12
|
|
8
|
-
http_rackup_path File.expand_path('ipam_http_config.ru',
|
9
|
-
https_rackup_path File.expand_path('ipam_http_config.ru',
|
13
|
+
http_rackup_path File.expand_path('ipam_http_config.ru', __dir__)
|
14
|
+
https_rackup_path File.expand_path('ipam_http_config.ru', __dir__)
|
10
15
|
end
|
11
16
|
end
|
@@ -0,0 +1,390 @@
|
|
1
|
+
require 'proxy/validations'
|
2
|
+
require 'smart_proxy_ipam/ipam'
|
3
|
+
require 'smart_proxy_ipam/ipam_helper'
|
4
|
+
require 'smart_proxy_ipam/ipam_validator'
|
5
|
+
require 'smart_proxy_ipam/dependency_injection'
|
6
|
+
|
7
|
+
module Proxy::Ipam
|
8
|
+
# Generic API for External IPAM interactions
|
9
|
+
class Api < ::Sinatra::Base
|
10
|
+
extend Proxy::Ipam::DependencyInjection
|
11
|
+
|
12
|
+
include ::Proxy::Log
|
13
|
+
helpers ::Proxy::Helpers
|
14
|
+
include Proxy::Ipam::IpamHelper
|
15
|
+
include Proxy::Ipam::IpamValidator
|
16
|
+
|
17
|
+
inject_attr :externalipam_client, :client
|
18
|
+
|
19
|
+
# Gets the next available IP address based on a given External IPAM subnet
|
20
|
+
#
|
21
|
+
# Inputs: 1. address: Network address of the subnet(e.g. 100.55.55.0)
|
22
|
+
# 2. prefix: Network prefix(e.g. 24)
|
23
|
+
# 3. group(optional): The External IPAM group
|
24
|
+
#
|
25
|
+
# Returns:
|
26
|
+
# Response if success:
|
27
|
+
# ======================
|
28
|
+
# Http Code: 200
|
29
|
+
# JSON Response:
|
30
|
+
# "100.55.55.3"
|
31
|
+
#
|
32
|
+
# Response if missing parameter(e.g. 'mac')
|
33
|
+
# ======================
|
34
|
+
# Http Code: 400
|
35
|
+
# JSON Response:
|
36
|
+
# {"error": ["A 'mac' address must be provided(e.g. 00:0a:95:9d:68:10)"]}
|
37
|
+
#
|
38
|
+
# Response if no free ip's available
|
39
|
+
# ======================
|
40
|
+
# Http Code: 404
|
41
|
+
# JSON Response:
|
42
|
+
# {"error": "There are no free IP's in subnet 100.55.55.0/24"}
|
43
|
+
get '/subnet/:address/:prefix/next_ip' do
|
44
|
+
content_type :json
|
45
|
+
|
46
|
+
begin
|
47
|
+
validate_required_params!([:address, :prefix, :mac], params)
|
48
|
+
|
49
|
+
mac = validate_mac!(params[:mac])
|
50
|
+
cidr = validate_cidr!(params[:address], params[:prefix])
|
51
|
+
group_name = get_request_group(params)
|
52
|
+
|
53
|
+
next_ip = provider.get_next_ip(mac, cidr, group_name)
|
54
|
+
halt 404, { error: ERRORS[:no_free_ips] }.to_json if next_ip.nil?
|
55
|
+
next_ip.to_json
|
56
|
+
rescue Proxy::Validations::Error => e
|
57
|
+
logger.warn(e.message)
|
58
|
+
halt 400, { error: e.to_s }.to_json
|
59
|
+
rescue RuntimeError => e
|
60
|
+
logger.warn(e.message)
|
61
|
+
halt 500, { error: e.message }.to_json
|
62
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET
|
63
|
+
logger.warn(ERRORS[:no_connection])
|
64
|
+
halt 500, { error: ERRORS[:no_connection] }.to_json
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Gets the subnet from External IPAM
|
69
|
+
#
|
70
|
+
# Inputs: 1. address: Network address of the subnet
|
71
|
+
# 2. prefix: Network prefix(e.g. 24)
|
72
|
+
# 3. group(optional): The name of the External IPAM group
|
73
|
+
#
|
74
|
+
# Returns:
|
75
|
+
# Response if subnet exists:
|
76
|
+
# ===========================
|
77
|
+
# Http Code: 200
|
78
|
+
# JSON Response:
|
79
|
+
# {"data": {
|
80
|
+
# "id": "33",
|
81
|
+
# "subnet": "10.20.30.0",
|
82
|
+
# "description": "Subnet description",
|
83
|
+
# "mask": "29"}
|
84
|
+
# }
|
85
|
+
#
|
86
|
+
# Response if subnet does not exist:
|
87
|
+
# ===========================
|
88
|
+
# Http Code: 404
|
89
|
+
# JSON Response:
|
90
|
+
# {"error": "No subnets found"}
|
91
|
+
get '/subnet/:address/:prefix' do
|
92
|
+
content_type :json
|
93
|
+
|
94
|
+
begin
|
95
|
+
validate_required_params!([:address, :prefix], params)
|
96
|
+
|
97
|
+
cidr = validate_cidr!(params[:address], params[:prefix])
|
98
|
+
group_name = get_request_group(params)
|
99
|
+
subnet = provider.get_ipam_subnet(cidr, group_name)
|
100
|
+
|
101
|
+
halt 404, { error: ERRORS[:no_subnet] }.to_json if subnet.nil?
|
102
|
+
subnet.to_json
|
103
|
+
rescue Proxy::Validations::Error => e
|
104
|
+
logger.warn(e.message)
|
105
|
+
halt 400, { error: e.to_s }.to_json
|
106
|
+
rescue RuntimeError => e
|
107
|
+
logger.warn(e.message)
|
108
|
+
halt 500, { error: e.message }.to_json
|
109
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET
|
110
|
+
logger.warn(ERRORS[:no_connection])
|
111
|
+
halt 500, { error: ERRORS[:no_connection] }.to_json
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Get a list of groups from External IPAM
|
116
|
+
#
|
117
|
+
# Returns:
|
118
|
+
# Response if success:
|
119
|
+
# ===========================
|
120
|
+
# Http Code: 200
|
121
|
+
# JSON Response:
|
122
|
+
# {"data": [
|
123
|
+
# {"id": "1", "name": "Group 1", "description": "This is group 1"},
|
124
|
+
# {"id": "2", "name": "Group 2", "description": "This is group 2"}
|
125
|
+
# ]}
|
126
|
+
#
|
127
|
+
# Response if no groups exist:
|
128
|
+
# ===========================
|
129
|
+
# Http Code: 404
|
130
|
+
# JSON Response:
|
131
|
+
# {"error": "Groups are not supported"}
|
132
|
+
#
|
133
|
+
# Response if groups are not supported:
|
134
|
+
# ===========================
|
135
|
+
# Http Code: 500
|
136
|
+
# JSON Response:
|
137
|
+
# {"error": "Groups are not supported"}
|
138
|
+
get '/groups' do
|
139
|
+
content_type :json
|
140
|
+
|
141
|
+
begin
|
142
|
+
halt 500, { error: ERRORS[:groups_not_supported] }.to_json unless provider.groups_supported?
|
143
|
+
groups = provider.get_ipam_groups
|
144
|
+
groups.to_json
|
145
|
+
rescue Proxy::Validations::Error => e
|
146
|
+
logger.warn(e.message)
|
147
|
+
halt 400, { error: e.to_s }.to_json
|
148
|
+
rescue RuntimeError => e
|
149
|
+
logger.warn(e.message)
|
150
|
+
halt 500, { error: e.message }.to_json
|
151
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET
|
152
|
+
logger.warn(ERRORS[:no_connection])
|
153
|
+
halt 500, { error: ERRORS[:no_connection] }.to_json
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Get a group from External IPAM
|
158
|
+
#
|
159
|
+
# Inputs: 1. group: The name of the External IPAM group
|
160
|
+
#
|
161
|
+
# Returns:
|
162
|
+
# Response if success:
|
163
|
+
# ===========================
|
164
|
+
# Http Code: 200
|
165
|
+
# JSON Response:
|
166
|
+
# {"data": {"id": "1", "name": "Group 1", "description": "This is group 1"}}
|
167
|
+
#
|
168
|
+
# Response if group not found:
|
169
|
+
# ===========================
|
170
|
+
# Http Code: 404
|
171
|
+
# JSON Response:
|
172
|
+
# {"error": "Group not Found"}
|
173
|
+
#
|
174
|
+
# Response if groups are not supported:
|
175
|
+
# ===========================
|
176
|
+
# Http Code: 500
|
177
|
+
# JSON Response:
|
178
|
+
# {"error": "Groups are not supported"}
|
179
|
+
get '/groups/:group' do
|
180
|
+
content_type :json
|
181
|
+
|
182
|
+
begin
|
183
|
+
validate_required_params!([:group], params)
|
184
|
+
|
185
|
+
group_name = get_request_group(params)
|
186
|
+
group = provider.get_ipam_group(group_name)
|
187
|
+
|
188
|
+
halt 404, { error: ERRORS[:no_group] }.to_json if group.nil?
|
189
|
+
group.to_json
|
190
|
+
rescue Proxy::Validations::Error => e
|
191
|
+
logger.warn(e.message)
|
192
|
+
halt 400, { error: e.to_s }.to_json
|
193
|
+
rescue RuntimeError => e
|
194
|
+
logger.warn(e.message)
|
195
|
+
halt 500, { error: e.message }.to_json
|
196
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET
|
197
|
+
logger.warn(ERRORS[:no_connection])
|
198
|
+
halt 500, { error: ERRORS[:no_connection] }.to_json
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# Get a list of subnets for a given External IPAM group
|
203
|
+
#
|
204
|
+
# Input: 1. group: The name of the External IPAM group
|
205
|
+
#
|
206
|
+
# Returns:
|
207
|
+
# Response if success:
|
208
|
+
# ===========================
|
209
|
+
# Http Code: 200
|
210
|
+
# JSON Response:
|
211
|
+
# {"data":[
|
212
|
+
# {"subnet":"10.20.30.0","mask":"29","description":"This is a subnet"},
|
213
|
+
# {"subnet":"40.50.60.0","mask":"29","description":"This is another subnet"}
|
214
|
+
# ]}
|
215
|
+
#
|
216
|
+
# Response if no subnets exist in group:
|
217
|
+
# ===========================
|
218
|
+
# Http Code: 404
|
219
|
+
# JSON Response:
|
220
|
+
# {"error": "No subnets found in External IPAM group"}
|
221
|
+
#
|
222
|
+
# Response if groups are not supported:
|
223
|
+
# ===========================
|
224
|
+
# Http Code: 500
|
225
|
+
# JSON Response:
|
226
|
+
# {"error": "Groups are not supported"}
|
227
|
+
get '/groups/:group/subnets' do
|
228
|
+
content_type :json
|
229
|
+
|
230
|
+
begin
|
231
|
+
validate_required_params!([:group], params)
|
232
|
+
|
233
|
+
group_name = get_request_group(params)
|
234
|
+
subnets = provider.get_ipam_subnets(group_name)
|
235
|
+
|
236
|
+
halt 404, { error: ERRORS[:no_subnets_in_group] }.to_json if subnets.nil?
|
237
|
+
subnets.to_json
|
238
|
+
rescue Proxy::Validations::Error => e
|
239
|
+
logger.warn(e.message)
|
240
|
+
halt 400, { error: e.to_s }.to_json
|
241
|
+
rescue RuntimeError => e
|
242
|
+
logger.warn(e.message)
|
243
|
+
halt 500, { error: e.message }.to_json
|
244
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET
|
245
|
+
logger.warn(ERRORS[:no_connection])
|
246
|
+
halt 500, { error: ERRORS[:no_connection] }.to_json
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# Checks whether an IP address has already been taken in External IPAM
|
251
|
+
#
|
252
|
+
# Inputs: 1. address: The network address of the IPv4 or IPv6 subnet.
|
253
|
+
# 2. prefix: The subnet prefix(e.g. 24)
|
254
|
+
# 3. ip: IP address to be queried
|
255
|
+
# 4. group(optional): The name of the External IPAM Group, containing the subnet to check
|
256
|
+
#
|
257
|
+
# Returns:
|
258
|
+
# Response if exists:
|
259
|
+
# ===========================
|
260
|
+
# Http Code: 200
|
261
|
+
# Response: true
|
262
|
+
#
|
263
|
+
# Response if not exists:
|
264
|
+
# ===========================
|
265
|
+
# Http Code: 200
|
266
|
+
# JSON Response: false
|
267
|
+
get '/subnet/:address/:prefix/:ip' do
|
268
|
+
content_type :json
|
269
|
+
|
270
|
+
begin
|
271
|
+
validate_required_params!([:address, :prefix, :ip], params)
|
272
|
+
|
273
|
+
ip = validate_ip!(params[:ip])
|
274
|
+
cidr = validate_cidr!(params[:address], params[:prefix])
|
275
|
+
group_name = get_request_group(params)
|
276
|
+
subnet = provider.get_ipam_subnet(cidr, group_name)
|
277
|
+
|
278
|
+
halt 404, { error: ERRORS[:no_subnet] }.to_json if subnet.nil?
|
279
|
+
validate_ip_in_cidr!(ip, cidr)
|
280
|
+
ip_exists = provider.ip_exists?(ip, subnet[:id], group_name)
|
281
|
+
halt 200, ip_exists.to_json
|
282
|
+
rescue Proxy::Validations::Error => e
|
283
|
+
logger.warn(e.message)
|
284
|
+
halt 400, { error: e.to_s }.to_json
|
285
|
+
rescue RuntimeError => e
|
286
|
+
logger.warn(e.message)
|
287
|
+
halt 500, { error: e.message }.to_json
|
288
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET
|
289
|
+
logger.warn(ERRORS[:no_connection])
|
290
|
+
halt 500, { error: ERRORS[:no_connection] }.to_json
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# Adds an IP address to the specified subnet for the specified IPAM provider
|
295
|
+
#
|
296
|
+
# Params: 1. address: The network address of the IPv4 or IPv6 subnet
|
297
|
+
# 2. prefix: The subnet prefix(e.g. 24)
|
298
|
+
# 3. ip: IP address to be added
|
299
|
+
# 4. group(optional): The name of the External IPAM Group, containing the subnet to add ip to
|
300
|
+
#
|
301
|
+
# Returns:
|
302
|
+
# Response if added successfully:
|
303
|
+
# ===========================
|
304
|
+
# Http Code: 201
|
305
|
+
# Response: Empty
|
306
|
+
#
|
307
|
+
# Response if not added successfully:
|
308
|
+
# ===========================
|
309
|
+
# Http Code: 500
|
310
|
+
# JSON Response:
|
311
|
+
# {"error": "Unable to add IP to External IPAM"}
|
312
|
+
post '/subnet/:address/:prefix/:ip' do
|
313
|
+
content_type :json
|
314
|
+
|
315
|
+
begin
|
316
|
+
validate_required_params!([:address, :ip, :prefix], params)
|
317
|
+
|
318
|
+
ip = validate_ip!(params[:ip])
|
319
|
+
cidr = validate_cidr!(params[:address], params[:prefix])
|
320
|
+
group_name = get_request_group(params)
|
321
|
+
subnet = provider.get_ipam_subnet(cidr, group_name)
|
322
|
+
|
323
|
+
halt 404, { error: ERRORS[:no_subnet] }.to_json if subnet.nil?
|
324
|
+
add_ip_params = { cidr: cidr, subnet_id: subnet[:id], group_name: group_name }
|
325
|
+
validate_ip_in_cidr!(ip, cidr)
|
326
|
+
|
327
|
+
ip_added = provider.add_ip_to_subnet(ip, add_ip_params) # Returns nil on success
|
328
|
+
halt 500, ip_added.to_json unless ip_added.nil?
|
329
|
+
status 201
|
330
|
+
rescue Proxy::Validations::Error => e
|
331
|
+
logger.warn(e.message)
|
332
|
+
halt 400, { error: e.to_s }.to_json
|
333
|
+
rescue RuntimeError => e
|
334
|
+
logger.warn(e.message)
|
335
|
+
halt 500, { error: e.message }.to_json
|
336
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET
|
337
|
+
logger.warn(ERRORS[:no_connection])
|
338
|
+
halt 500, { error: ERRORS[:no_connection] }.to_json
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
# Deletes IP address from a given subnet
|
343
|
+
#
|
344
|
+
# Params: 1. address: The network address of the IPv4 or IPv6 subnet
|
345
|
+
# 2. prefix: The subnet prefix(e.g. 24)
|
346
|
+
# 3. ip: IP address to be deleted
|
347
|
+
# 4. group(optional): The name of the External IPAM Group, containing the subnet to delete ip from
|
348
|
+
#
|
349
|
+
# Returns:
|
350
|
+
# Response if deleted successfully:
|
351
|
+
# ===========================
|
352
|
+
# Http Code: 200
|
353
|
+
# Response: Empty
|
354
|
+
#
|
355
|
+
# Response if not added successfully:
|
356
|
+
# ===========================
|
357
|
+
# Http Code: 500
|
358
|
+
# JSON Response:
|
359
|
+
# {"error": "Unable to delete IP from External IPAM"}
|
360
|
+
delete '/subnet/:address/:prefix/:ip' do
|
361
|
+
content_type :json
|
362
|
+
|
363
|
+
begin
|
364
|
+
validate_required_params!([:address, :ip, :prefix], params)
|
365
|
+
|
366
|
+
ip = validate_ip!(params[:ip])
|
367
|
+
cidr = validate_cidr!(params[:address], params[:prefix])
|
368
|
+
group_name = get_request_group(params)
|
369
|
+
subnet = provider.get_ipam_subnet(cidr, group_name)
|
370
|
+
|
371
|
+
halt 404, { error: ERRORS[:no_subnet] }.to_json if subnet.nil?
|
372
|
+
del_ip_params = { cidr: cidr, subnet_id: subnet[:id], group_name: group_name }
|
373
|
+
validate_ip_in_cidr!(ip, cidr)
|
374
|
+
|
375
|
+
ip_deleted = provider.delete_ip_from_subnet(ip, del_ip_params) # Returns nil on success
|
376
|
+
halt 500, ip_deleted.to_json unless ip_deleted.nil?
|
377
|
+
halt 204
|
378
|
+
rescue Proxy::Validations::Error => e
|
379
|
+
logger.warn(e.message)
|
380
|
+
halt 400, { error: e.to_s }.to_json
|
381
|
+
rescue RuntimeError => e
|
382
|
+
logger.warn(e.message)
|
383
|
+
halt 500, { error: e.message }.to_json
|
384
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET
|
385
|
+
logger.warn(ERRORS[:no_connection])
|
386
|
+
halt 500, { error: ERRORS[:no_connection] }.to_json
|
387
|
+
end
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|