1 | import { XMLParser } from "fast-xml-parser" |
2 |
|
3 | |
4 | * Push to device group |
5 | * Push the committed Panorama configuration to the firewalls of a device group (PAN-OS XML API, type=commit&action=all, commit-all shared-policy). Commit on Panorama first. Set devices (firewall serial numbers) to push to only those members; set exclude_template to leave out template changes. Returns the job id to follow with Get job status. |
6 | */ |
7 | export async function main( |
8 | auth: RT.PaloAltoPanos, |
9 | device_group: string, |
10 | description: string | undefined, |
11 | devices: string[] | undefined, |
12 | exclude_template: boolean | undefined |
13 | ) { |
14 | let entry = `<entry name="${xmlEscape(device_group)}"` |
15 | if (devices?.length) { |
16 | entry += `><devices>${devices.map((d) => `<entry name="${xmlEscape(d)}"/>`).join("")}</devices></entry>` |
17 | } else { |
18 | entry += "/>" |
19 | } |
20 | let cmd = `<commit-all><shared-policy><device-group>${entry}</device-group>` |
21 | if (description) cmd += `<description>${xmlEscape(description)}</description>` |
22 | if (exclude_template) cmd += "<include-template>no</include-template>" |
23 | cmd += "</shared-policy></commit-all>" |
24 |
|
25 | const res = await xmlApi(auth, { type: "commit", action: "all", cmd }) |
26 | return { |
27 | job_id: res.result?.job != null ? String(res.result.job) : null, |
28 | message: |
29 | res.result?.msg?.line ?? |
30 | res.result?.msg ?? |
31 | res.msg?.line ?? |
32 | res.msg ?? |
33 | null, |
34 | } |
35 | } |
36 |
|
37 | function xmlEscape(s: string) { |
38 | return s |
39 | .replace(/&/g, "&") |
40 | .replace(/</g, "<") |
41 | .replace(/>/g, ">") |
42 | .replace(/"/g, """) |
43 | .replace(/'/g, "'") |
44 | } |
45 |
|
46 | async function xmlApi(auth: RT.PaloAltoPanos, params: { [key: string]: string }) { |
47 | const url = new URL("/api/", auth.base_url) |
48 | for (const [key, value] of Object.entries(params)) { |
49 | url.searchParams.set(key, value) |
50 | } |
51 | const response = await fetch(url, { |
52 | method: "POST", |
53 | headers: { "X-PAN-KEY": auth.api_key }, |
54 | }) |
55 | const text = await response.text() |
56 | if (!response.ok) { |
57 | throw new Error(`${response.status} ${text}`) |
58 | } |
59 | const parsed = new XMLParser({ |
60 | ignoreAttributes: false, |
61 | attributeNamePrefix: "@", |
62 | parseTagValue: false, |
63 | }).parse(text) |
64 | if (parsed.response?.["@status"] !== "success") { |
65 | throw new Error(`${response.status} ${text}`) |
66 | } |
67 | return parsed.response |
68 | } |
69 |
|