/* global __appdir */ const path = require('path') const ExternalBackends = require(path.join(__appdir, 'lib', 'external-backends')) const Infoblox = require('infoblox') class InfobloxBackend extends ExternalBackends { // ############################################################################ // ######################## needed functions ################################# /* * Returns the credential structure / fields, defined in the backends. */ getCredentials () { return [ { type: 'text', id: 1, name: 'API url', icon: 'link' }, { type: 'text', id: 2, name: 'API version', icon: 'info' }, { type: 'text', id: 3, name: 'Username', icon: 'person_outline' }, { type: 'password', id: 4, name: 'Password', icon: 'vpn_key', show: false } ] } /* * Checks the connection of a given backend. * infoblox: If we can login the connection to the server and also the login is successfull. * * return: { success: , status: '', error: '' } */ async checkConnection (credentials) { var c = this.mapCredentials(credentials) var ipam = new Infoblox({ ip: c.url, apiVersion: c.version }) // Add custom timeout to the infoblox login return Promise.race([ new Promise(async (resolve, reject) => { try { const result = await ipam.login(c.username, c.password) if (!result) resolve({ error: 'LOGIN_FAILED', message: 'Login failed' }) else resolve(result) } catch (e) { resolve({ error: 'LOGIN_FAILED', message: e }) } }), new Promise((resolve, reject) => { let wait = setTimeout(() => { clearTimeout(wait) resolve({ error: 'LOGIN_FAILED', message: 'Timeout' }) }, 15000) })]) } /* * Gets the client via IP, because only fixed pcs can be found via mac. But we also want so support leased pc's? * */ async getClient (credentials, client) { var c = this.mapCredentials(credentials) var ipam = new Infoblox({ ip: c.url, apiVersion: c.version }) return ipam.login(c.username, c.password).then(response => { if (response) { return ipam.getHost('10.21.9.228').then(response => { return JSON.parse(response) }) } else { return { success: false, error: 'Login failed' } } }) } async checkDomain (credentials) { const c = this.mapCredentials(credentials) const ipam = new Infoblox({ ip: c.url, apiVersion: c.version }) const login = await ipam.login(c.username, c.password) if (!login) return { error: 'LOGIN_FAILED' } const domainList = await ipam.getDomain() return domainList } async checkIp (credentials, ipv4) { const c = this.mapCredentials(credentials) // Login const ipam = new Infoblox({ ip: c.url, apiVersion: c.version }) const login = await ipam.login(c.username, c.password) if (!login) return { leased: false } // Get the host and check the leased state let host = JSON.parse(await ipam.getHost(ipv4))[0] if (!host) return { error: 'HOST_NOT_FOUND' } if (host.lease_state && host.lease_state === 'ACTIVE') { const dhcpNetwork = JSON.parse(await ipam.getNetworkFromIp(ipv4)) // If leased return the next 20 free ips of the subnet. API method is limited to 20 ... // const nextIps = await ipam.getNext(dhcpNetwork[0]._ref, 20) // Instead of only getting the next 20 ips, GET THEM ALL let nextIps = JSON.parse(await ipam.getIpsFromSubnet(dhcpNetwork[0].network)) nextIps = nextIps.filter(x => x.status === 'UNUSED').map(y => y.ip_address) return { leased: true, nextIps: nextIps } } let response = { leased: false, id: host._ref } if (host.names.length >= 1) response.name = host.names[0].split('.')[0] return response } async setIp (credentials, ipv4, domain, mac, name, setNextIp = false) { const c = this.mapCredentials(credentials) const ipam = new Infoblox({ ip: c.url, apiVersion: c.version }) const login = await ipam.login(c.username, c.password) if (!login) return { error: 'LOGIN_FAILED' } // If setNextIp is true, use the next available ip from the subnet if (setNextIp) { try { const network = JSON.parse(await ipam.getNetworkFromIp(ipv4)) ipv4 = 'func:nextavailableip:' + network[0].network } catch (e) { console.log(e) return { error: 'ERROR_INFOBLOX', msg: 'No network found. Missing permissions?' } } } // If the domain is not set, take the first available one. if (!domain) domain = (await ipam.getDomain())[0] // Set fixed ip if the name is not set (Automatic registration) let path = '' let data = {} if (!name) { path = 'fixedaddress?_return_fields%2B=ipv4addr&_return_as_object=1' data = { 'ipv4addr': ipv4, 'mac': mac } } else { path = 'record:host?_return_fields%2B=ipv4addrs&_return_as_object=1' data = { 'name': name + '.' + domain, 'ipv4addrs': [ { 'ipv4addr': ipv4, 'mac': mac } ] } } const createHost = await ipam.create(path, data) // Return error if there is one if (createHost.Error) { return { error: 'ERROR_INFOBLOX', msg: createHost.text } } else { if (!name) return { ip: createHost.result.ipv4addr } else return { host: createHost.result.ipv4addrs[0].host, ip: createHost.result.ipv4addrs[0].ipv4addr, id: createHost.result._ref, domain: domain } } } isDhcp () { return true } async updateClient (credentials, client) { const c = this.mapCredentials(credentials) const ipam = new Infoblox({ ip: c.url, apiVersion: c.version }) const login = await ipam.login(c.username, c.password) if (!login) return { error: 'LOGIN_FAILED' } let data = {} let result = [] for (let index in client.networks) { const network = client.networks[index] if (network.hostname && network.domain) { data.name = network.hostname + '.' + network.domain } let ipv4addr = {} if (network.ip) ipv4addr.ipv4addr = network.ip if (network.mac) ipv4addr.mac = network.mac if (network.ip || network.mac) { data.ipv4addrs = [ipv4addr] } const oldId = client.id const newId = await ipam.update(oldId, data) // If the id changed (domain / name / ip) the mapping has to be updated if (oldId !== newId) result.push({ oldId: oldId, newId: newId }) } return { mappings: result } } async deleteObjects (credentials, objectIds) { const c = this.mapCredentials(credentials) const ipam = new Infoblox({ ip: c.url, apiVersion: c.version }) const login = await ipam.login(c.username, c.password) if (!login) return { error: 'LOGIN_FAILED' } let errors = [] for (let id of objectIds) { const response = await ipam.delete(id) const parsed = JSON.parse(response) if (parsed.Error) { errors.push({ error: parsed.Code, message: parsed.text, id: id }) } } return errors } async getObjects (credentials) { const c = this.mapCredentials(credentials) const ipam = new Infoblox({ ip: c.url, apiVersion: c.version }) const login = await ipam.login(c.username, c.password) if (!login) return { error: 'LOGIN_FAILED' } let result = [] const domains = await ipam.getDomain() for (let index in domains) { const domain = domains[index] // Max result of infoblox is 1000, so we need to use the paging system const response = JSON.parse(await ipam.list('record:host?zone=' + domain + '&_paging=1&_return_as_object=1&_max_results=1000')) let records = response.result let nextPageId = response.next_page_id while (nextPageId) { let nextpage = JSON.parse(await ipam.list('record:host?zone=' + domain + '&_paging=1&_return_as_object=1&_max_results=1000&_page_id=' + nextPageId)) records = records.concat(nextpage.result) nextPageId = nextpage.next_page_id } for (let i in records) { const record = records[i] result.push({ id: record._ref, mac: record.ipv4addrs[0].mac, ip: record.ipv4addrs[0].ipv4addr, domain: domain }) } } return result } // ############################################################################ // ####################### helper/optional functions ######################### // Helper function, to map the array of credential objects into a single js object. mapCredentials (credentials) { const c = JSON.parse(credentials) var mapped = { url: c.find(x => x.id === 1).value, version: c.find(x => x.id === 2).value, username: c.find(x => x.id === 3).value, password: c.find(x => x.id === 4).value } return mapped } // #### TEST FUNCTIONS ### async test (credentials) { const c = this.mapCredentials(credentials) const ipam = new Infoblox({ ip: c.url, apiVersion: c.version }) const login = await ipam.login(c.username, c.password) let result = {} if (!login) return { error: 'LOGIN_FAILED' } // Get network from ip result['getNetworkFromIp'] = JSON.parse(await ipam.getNetworkFromIp('10.21.9.78')) // Get host result['getHost.78'] = JSON.parse(await ipam.getHost('10.21.9.78')) // result["getHost.219"] = JSON.parse(await ipam.getHost('10.21.9.219')) result['getHost.12'] = JSON.parse(await ipam.getHost('10.21.9.12')) result['getHost.43'] = JSON.parse(await ipam.getHost('10.21.11.43')) // Get ips from subnet result['getIpsFromSubnet'] = JSON.parse(await ipam.getIpsFromSubnet('10.21.9.0/24')) result['getIpsFromSubnet'] = result['getIpsFromSubnet'].filter(x => x.status === 'UNUSED').map(y => y.ip_address) // List records // result["list"] = JSON.parse(await ipam.list('record:host')) // Get Domain result['getDomain'] = await ipam.getDomain() // Get next ip result['getNextIp.1'] = await ipam.getNext(result['getNetworkFromIp'][0]._ref, 1) // Get next 20 ips result['getNextIp.20'] = await ipam.getNext(result['getNetworkFromIp'][0]._ref, 20) // Get all unsued ips // result["getUnusedIps"] = await ipam.getNext(result["getNetworkFromIp"][0]._ref, 20) // Create Host // result['createHost'] = await ipam.create('record:host?_return_fields%2B=ipv4addrs&_return_as_object=1', {'name': 'wapiTest2.lp.privat', 'ipv4addrs': [{'ipv4addr': '10.21.9.218', 'mac': 'aa:bb:cc:11:22:21'}]}) /* result['create'] = await ipam.create('record:host?_return_fields%2B=ipv4addrs&_return_as_object=1', { 'name': 'test.lp.privat', 'ipv4addrs': [ { //'ipv4addr': 'func:nextavailableip:' + result['getNetworkFromIp'][0].network, 'ipv4addr': '10.21.9.206', 'mac': 'aa:bb:cc:11:22:21' } ] }) */ // Delete stuff // result["getHost.173"] = JSON.parse(await ipam.getHost('10.21.9.173')) // result["deleteHost.173"] = JSON.parse(await ipam.delete(result["getHost.173"][0]._ref)) // result["deleteHost.206"] = JSON.parse(await ipam.delete('record:host/ZG5zLmhvc3QkLl9kZWZhdWx0LnByaXZhdC5scC50ZXN0:test.lp.privat/default')) // result['allrecords'] = JSON.parse(await ipam.list('record:host?zone=lp.privat')) /* result['updateDomain'] = await ipam.update('record:host/ZG5zLmhvc3QkLl9kZWZhdWx0LmRlLnVuaS1mcmVpYnVyZy5hZHMucHVibGljLnplZnQ5MDQz', { 'name': 'zeft9043.public.ads.uni-freiburg.de' }) */ return result } } module.exports = InfobloxBackend