1 | |
2 |
|
3 | import * as wmill from "windmill-client" |
4 |
|
5 | |
6 | * New Issues |
7 | * Emits Wiz issues created since the last run. The first run records a watermark and returns nothing; subsequent runs return newly created issues (oldest first). |
8 | */ |
9 | export async function main( |
10 | auth: RT.Wiz, |
11 | severity: |
12 | | ("INFORMATIONAL" | "LOW" | "MEDIUM" | "HIGH" | "CRITICAL")[] |
13 | | undefined |
14 | ) { |
15 | const tokenResponse = await fetch( |
16 | auth.auth_url || "https://auth.app.wiz.io/oauth/token", |
17 | { |
18 | method: "POST", |
19 | headers: { "Content-Type": "application/x-www-form-urlencoded" }, |
20 | body: new URLSearchParams({ |
21 | grant_type: "client_credentials", |
22 | audience: auth.audience || "wiz-api", |
23 | client_id: auth.client_id, |
24 | client_secret: auth.client_secret, |
25 | }), |
26 | } |
27 | ) |
28 | if (!tokenResponse.ok) { |
29 | throw new Error(`${tokenResponse.status} ${await tokenResponse.text()}`) |
30 | } |
31 | const { access_token } = (await tokenResponse.json()) as { |
32 | access_token: string |
33 | } |
34 |
|
35 | const lastChecked: string | undefined = await wmill.getState() |
36 |
|
37 | |
38 | if (!lastChecked) { |
39 | await wmill.setState(new Date().toISOString()) |
40 | return [] |
41 | } |
42 |
|
43 | const filterBy: { [key: string]: any } = { createdAt: { after: lastChecked } } |
44 | if (severity && severity.length > 0) filterBy.severity = severity |
45 |
|
46 | const query = ` |
47 | query NewIssues($first: Int, $after: String, $filterBy: IssueFilters) { |
48 | issues: issuesV2(first: $first, after: $after, filterBy: $filterBy) { |
49 | pageInfo { hasNextPage endCursor } |
50 | nodes { |
51 | id |
52 | type |
53 | severity |
54 | status |
55 | createdAt |
56 | sourceRule { |
57 | __typename |
58 | ... on Control { id name } |
59 | ... on CloudConfigurationRule { id name } |
60 | ... on CloudEventRule { id name } |
61 | } |
62 | entitySnapshot { id name type nativeType cloudPlatform region subscriptionName } |
63 | projects { id name } |
64 | } |
65 | } |
66 | }` |
67 |
|
68 | const newIssues: any[] = [] |
69 | let after: string | null = null |
70 | |
71 | for (let page = 0; page < 20; page++) { |
72 | const response = await fetch(auth.api_endpoint, { |
73 | method: "POST", |
74 | headers: { |
75 | Authorization: `Bearer ${access_token}`, |
76 | "Content-Type": "application/json", |
77 | Accept: "application/json", |
78 | }, |
79 | body: JSON.stringify({ |
80 | query, |
81 | variables: { first: 100, after, filterBy }, |
82 | }), |
83 | }) |
84 |
|
85 | if (!response.ok) { |
86 | throw new Error(`${response.status} ${await response.text()}`) |
87 | } |
88 |
|
89 | const result = (await response.json()) as { data?: any; errors?: any } |
90 | if (result.errors) { |
91 | throw new Error(JSON.stringify(result.errors)) |
92 | } |
93 |
|
94 | const conn = result.data.issues |
95 | newIssues.push(...conn.nodes) |
96 |
|
97 | if (!conn.pageInfo.hasNextPage) break |
98 | after = conn.pageInfo.endCursor |
99 | } |
100 |
|
101 | |
102 | |
103 | if (newIssues.length > 0) { |
104 | const newest = newIssues.reduce( |
105 | (max, i) => (i.createdAt > max ? i.createdAt : max), |
106 | newIssues[0].createdAt |
107 | ) |
108 | await wmill.setState(newest) |
109 | newIssues.sort((a, b) => (a.createdAt < b.createdAt ? -1 : 1)) |
110 | } |
111 |
|
112 | return newIssues |
113 | } |
114 |
|