1 | |
2 |
|
3 | async function connect(auth: RT.F5Bigip): Promise<{ |
4 | base: string |
5 | headers: { [key: string]: string } |
6 | release: () => Promise<void> |
7 | }> { |
8 | const base = auth.base_url.replace(/\/+$/, "") |
9 | if (auth.auth_method === "basic") { |
10 | return { |
11 | base, |
12 | headers: { |
13 | Authorization: `Basic ${btoa(`${auth.username}:${auth.password}`)}`, |
14 | }, |
15 | release: async () => {}, |
16 | } |
17 | } |
18 | const login = await fetch(`${base}/mgmt/shared/authn/login`, { |
19 | method: "POST", |
20 | headers: { "Content-Type": "application/json", Accept: "application/json" }, |
21 | body: JSON.stringify({ |
22 | username: auth.username, |
23 | password: auth.password, |
24 | loginProviderName: auth.login_provider_name || "tmos", |
25 | }), |
26 | }) |
27 | if (!login.ok) { |
28 | throw new Error(`${login.status} ${await login.text()}`) |
29 | } |
30 | const token: string = (await login.json()).token.token |
31 | return { |
32 | base, |
33 | headers: { "X-F5-Auth-Token": token }, |
34 | release: async () => { |
35 | await fetch( |
36 | `${base}/mgmt/shared/authz/tokens/${encodeURIComponent(token)}`, |
37 | { |
38 | method: "DELETE", |
39 | headers: { "X-F5-Auth-Token": token }, |
40 | } |
41 | ) |
42 | }, |
43 | } |
44 | } |
45 |
|
46 | |
47 | * List active sessions |
48 | * List active APM access sessions (session id, logon user, client IP) from /mgmt/tm/apm/access-info. BIG-IP returns every session in one stats document; filtering by user or client IP and the limit/offset window are applied by the script. |
49 | */ |
50 | export async function main( |
51 | auth: RT.F5Bigip, |
52 | logon_user: string | undefined, |
53 | client_ip: string | undefined, |
54 | limit: number | undefined, |
55 | offset: number | undefined |
56 | ) { |
57 | const f5 = await connect(auth) |
58 | try { |
59 | const response = await fetch(`${f5.base}/mgmt/tm/apm/access-info`, { |
60 | headers: { ...f5.headers, Accept: "application/json" }, |
61 | }) |
62 |
|
63 | if (!response.ok) { |
64 | throw new Error(`${response.status} ${await response.text()}`) |
65 | } |
66 |
|
67 | |
68 | const stats = await response.json() |
69 | const all = Object.entries(stats.entries ?? {}).map( |
70 | ([link, value]: [string, any]) => { |
71 | const fields = value?.nestedStats?.entries ?? {} |
72 | return { |
73 | session_id: new URL(link).pathname.split("/").pop() as string, |
74 | logon_user: fields.logonUser?.description ?? null, |
75 | client_ip: fields.clientIp?.description ?? null, |
76 | } |
77 | } |
78 | ) |
79 | |
80 | const sessions = all.filter( |
81 | (s) => |
82 | (s.logon_user || s.client_ip) && |
83 | (!logon_user || s.logon_user === logon_user) && |
84 | (!client_ip || s.client_ip === client_ip) |
85 | ) |
86 |
|
87 | const start = offset || 0 |
88 | const end = start + (limit || 100) |
89 | return { |
90 | total: sessions.length, |
91 | sessions: sessions.slice(start, end), |
92 | next_offset: end < sessions.length ? end : null, |
93 | } |
94 | } finally { |
95 | await f5.release() |
96 | } |
97 | } |
98 |
|