ifms-pti/server/src/lib/paloalto.js

209 lines
5.4 KiB
JavaScript
Raw Normal View History

2020-12-10 23:56:05 +00:00
// Ref.: https://docs.paloaltonetworks.com/pan-os/9-1/pan-os-panorama-api/pan-os-xml-api-request-types/apply-user-id-mapping-and-populate-dynamic-address-groups-api.html
2020-12-02 15:20:27 +00:00
import axios from 'axios'
import https from 'https'
2021-01-14 19:12:37 +00:00
import { isIPv4 } from 'net'
import qs from 'qs'
2021-01-14 20:22:15 +00:00
import { subMinutes } from 'date-fns'
2020-12-18 18:33:34 +00:00
import { logError, logSuccess } from './logger'
2021-01-14 17:04:41 +00:00
import { AES, enc } from 'crypto-js'
2021-01-14 19:12:37 +00:00
import ip from 'ip'
2021-11-03 15:59:23 +00:00
import { performance } from 'perf_hooks'
2021-01-14 19:12:37 +00:00
import prisma from '../prisma'
2020-12-02 15:20:27 +00:00
2022-02-07 18:25:56 +00:00
const MAP_TIMEOUT_IN_MINUTES = process.env.MAPPING_TIMEOUT || '360' // 6 horas
2021-01-14 19:12:37 +00:00
const CIDR_RE = /^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))?$/
2022-02-08 12:57:36 +00:00
const REQUEST_TIMEOUT_IN_MS = 20000
2020-12-17 12:57:38 +00:00
const httpsAgent = new https.Agent({
2020-12-02 15:20:27 +00:00
rejectUnauthorized: false
})
2021-01-15 19:33:09 +00:00
async function getDevicesWithUser() {
2020-12-17 12:57:38 +00:00
const now = new Date()
2022-02-07 18:25:56 +00:00
const timeoutThreshold = subMinutes(now, MAP_TIMEOUT_IN_MINUTES)
2020-12-17 12:57:38 +00:00
2021-01-14 14:07:29 +00:00
const wifiDevices = await prisma.wifiDevice.findMany({
where: {
userId: { not: null },
status: 'ONLINE',
lastSeen: { gt: timeoutThreshold }
2021-01-14 14:07:29 +00:00
},
select: {
ip: true,
2021-01-19 16:46:52 +00:00
user: { select: { sAMAccountName: true, displayName: true } }
2021-01-14 14:07:29 +00:00
}
})
return wifiDevices
}
function createCommand(devices) {
const entries = devices.reduce(
(entries, device) =>
entries +
2022-02-07 18:25:56 +00:00
`<entry name="ifms\\${device.user.sAMAccountName}" ip="${device.ip}" timeout="${MAP_TIMEOUT_IN_MINUTES}"/>\n`,
2021-01-14 14:07:29 +00:00
''
)
return `
<uid-message>
<version>1.0</version>
<type>update</type>
<payload>
<login>
${entries}
</login>
</payload>
</uid-message>`
}
2020-12-02 15:20:27 +00:00
2021-01-19 16:32:39 +00:00
const isWorking = []
2022-02-07 18:25:56 +00:00
function clearWorkingState() {
isWorking.length = 0
}
setInterval(clearWorkingState, 3600000)
2021-01-14 14:07:29 +00:00
async function updateUserIdMappings() {
2021-01-15 19:33:09 +00:00
const allDevices = await getDevicesWithUser()
2020-12-17 17:04:38 +00:00
const pAHosts = await prisma.pAHost.findMany()
2020-12-02 15:20:27 +00:00
const jobs = pAHosts.map(async pAHost => {
2022-02-07 18:25:56 +00:00
if (isWorking.includes(pAHost.id))
return `Última execução para ${pAHost.description} ainda em andamento`
2021-01-19 16:32:39 +00:00
2021-11-03 15:59:23 +00:00
const startTime = performance.now()
const net = ip.cidrSubnet(pAHost.cidr)
2021-04-06 12:56:33 +00:00
const devices = allDevices.filter(
({ ip }) => isIPv4(ip) && net.contains(ip)
)
try {
2022-02-07 18:25:56 +00:00
if (devices.length == 0)
return `Nenhum dispositivo encontrado para ${pAHost.description}`
2020-12-18 18:33:34 +00:00
2021-01-19 20:44:53 +00:00
isWorking.push(pAHost.id)
const cmd = createCommand(devices)
await axios({
url: `https://${pAHost.cidr.split('/')[0]}/api/`,
method: 'POST',
params: { type: 'user-id', key: decryptKey(pAHost.encryptedKey) },
data: qs.stringify({ cmd }),
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
2022-02-08 12:57:36 +00:00
timeout: REQUEST_TIMEOUT_IN_MS,
httpsAgent
})
2021-11-03 15:59:23 +00:00
const endTime = performance.now()
logSuccess({
2021-01-19 16:32:39 +00:00
tags: ['paloalto', 'user-id'],
2022-06-14 13:26:01 +00:00
message: `Foram mapeados ${devices.length} user-ids em ${
pAHost.description || pAHost.cidr
} em ${((endTime - startTime) / 1000).toFixed(2)}s.`,
data: devices
})
return devices.length
} catch (e) {
logError({
tags: ['paloalto', 'user-id'],
2022-06-14 13:26:01 +00:00
message: `Erro atualizando user-id mappings em ${
pAHost.description || pAHost.cidr
}: ${e.message}`
})
2022-04-27 18:32:43 +00:00
//console.log(e) // Do not add e to log DB for security reasons...
2021-10-27 16:41:22 +00:00
return 'Não foi possível atualizar. Veja o log do servidor'
2021-01-19 16:32:39 +00:00
} finally {
const index = isWorking.indexOf(pAHost.id)
if (index > -1) isWorking.splice(index, 1)
}
})
return Promise.allSettled(jobs)
2020-12-02 15:20:27 +00:00
}
2021-01-14 19:12:37 +00:00
async function getUserKey({ ipAddr, user, password }) {
try {
const result = await axios({
url: `https://${ipAddr}/api/`,
method: 'POST',
params: { type: 'keygen', user, password },
httpsAgent
})
return result.data.split('<key>')[1].split('</key>')[0]
} catch (e) {
throw new Error(e.message)
}
}
function encryptKey(key) {
return AES.encrypt(key, process.env.CRYPT_SECRET).toString()
}
2021-01-14 17:04:41 +00:00
2021-01-14 19:12:37 +00:00
function decryptKey(encryptedKey) {
return AES.decrypt(encryptedKey, process.env.CRYPT_SECRET).toString(enc.Utf8)
2021-01-14 17:04:41 +00:00
}
2021-01-18 20:03:08 +00:00
async function addHost({ cidr, user, password, description, note, owner }) {
2021-01-14 19:51:40 +00:00
if (!CIDR_RE.test(cidr)) throw new Error('Este não é um CIDR válido')
2021-01-14 19:12:37 +00:00
2021-01-14 19:51:40 +00:00
const ipAddr = cidr.split('/')[0]
2021-01-14 19:12:37 +00:00
2021-01-14 19:51:40 +00:00
if (!isIPv4(ipAddr)) throw new Error('Este não é um IPv4 válido')
const net = ip.cidrSubnet(cidr)
2021-01-14 19:12:37 +00:00
2021-01-14 19:51:40 +00:00
if (net.subnetMaskLength > 32 || net.networkAddress == '0.0.0.0')
throw new Error('Esta não é uma combinação de IP/máscara IPv4 válida')
2021-01-14 19:12:37 +00:00
2021-01-14 19:51:40 +00:00
const pAHosts = await prisma.pAHost.findMany()
2021-01-14 19:12:37 +00:00
2021-01-14 19:51:40 +00:00
try {
2021-01-14 19:12:37 +00:00
const key = await getUserKey({ ipAddr, user, password })
const encryptedKey = encryptKey(key)
2021-01-14 19:51:40 +00:00
const id = pAHosts.find(pAHost => pAHost.cidr.split('/')[0] == ipAddr)?.id
2021-01-14 19:12:37 +00:00
const pAHost = {
cidr,
encryptedKey,
user,
description,
2021-01-18 20:03:08 +00:00
note,
owner: {
connect: {
sAMAccountName: owner.sAMAccountName
}
}
2021-01-14 19:12:37 +00:00
}
2021-01-14 19:51:40 +00:00
const host = id
? await prisma.pAHost.update({ where: { id }, data: pAHost })
: await prisma.pAHost.create({ data: pAHost })
2021-01-14 17:04:41 +00:00
return {
...host,
2021-01-14 19:12:37 +00:00
key: `${key.slice(0, 5)}`
2021-01-14 17:04:41 +00:00
}
} catch (e) {
logError({
2021-01-14 19:12:37 +00:00
message: `Não foi possível adicionar o host ${cidr}. ${e?.message}`,
2021-01-14 17:04:41 +00:00
tags: ['paloalto']
})
throw new Error(e.message)
}
}
2021-01-14 23:20:01 +00:00
export { updateUserIdMappings, addHost, decryptKey }