mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-08-21 05:13:00 +00:00
Compare commits
536
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fcb8e53b7 | ||
|
|
84e9a91186 | ||
|
|
c1eceb9b5a | ||
|
|
082915cad9 | ||
|
|
ec6878c824 | ||
|
|
1218b0920f | ||
|
|
06ea61ffbf | ||
|
|
8eeb37aea4 | ||
|
|
7ba96ab2e2 | ||
|
|
4987d8a08e | ||
|
|
2bd89ceb97 | ||
|
|
0d4a21b5a4 | ||
|
|
1460f7b188 | ||
|
|
e9bbde2f01 | ||
|
|
76239b7076 | ||
|
|
0c0f150cd4 | ||
|
|
43de7709cb | ||
|
|
bb18dcb882 | ||
|
|
f5fe9c7b40 | ||
|
|
60c81f6549 | ||
|
|
f18c2841a8 | ||
|
|
6b21e31a28 | ||
|
|
294e665d9e | ||
|
|
94e14660da | ||
|
|
339930ef13 | ||
|
|
d933a8ecab | ||
|
|
da09ad9813 | ||
|
|
2298d4a3f8 | ||
|
|
066d53467a | ||
|
|
cf433128ed | ||
|
|
0b782fcb8c | ||
|
|
b80e3a7bfb | ||
|
|
885555e080 | ||
|
|
641c418816 | ||
|
|
10d173f8da | ||
|
|
34de660755 | ||
|
|
fb75146e34 | ||
|
|
9f5ef80e69 | ||
|
|
2f6aedf291 | ||
|
|
dcf2736e8d | ||
|
|
7f0dc9b7da | ||
|
|
ff45f46047 | ||
|
|
3bd13d173d | ||
|
|
2b7f7e7dc0 | ||
|
|
98015dafef | ||
|
|
d70a7e3d02 | ||
|
|
7d2a22c45a | ||
|
|
bf82e9ff3d | ||
|
|
eece938350 | ||
|
|
2b0e34818c | ||
|
|
f587dbf419 | ||
|
|
2d684c1996 | ||
|
|
7f9d64a996 | ||
|
|
e943ded978 | ||
|
|
bb20f69a63 | ||
|
|
97233189c3 | ||
|
|
b624818b8e | ||
|
|
cb31856025 | ||
|
|
13a9caa803 | ||
|
|
4e22289a19 | ||
|
|
e035e0ff62 | ||
|
|
dd81793480 | ||
|
|
457297a5f6 | ||
|
|
4da5aed519 | ||
|
|
95521d26cb | ||
|
|
cbe06314ca | ||
|
|
4b72cc8dd4 | ||
|
|
ce9d46065a | ||
|
|
35b3ff2c8e | ||
|
|
d7d4cc4849 | ||
|
|
1459046985 | ||
|
|
84c3c9ce0a | ||
|
|
8271659fda | ||
|
|
9916e1ac66 | ||
|
|
ccec96ac5f | ||
|
|
f5ea116763 | ||
|
|
99ca260855 | ||
|
|
bfc6b9e577 | ||
|
|
bc3c922177 | ||
|
|
0e20d2388e | ||
|
|
e0ddc0de27 | ||
|
|
cf840588c7 | ||
|
|
e1c2e9373c | ||
|
|
641a11e4a0 | ||
|
|
59b13778e2 | ||
|
|
7d8c64d35b | ||
|
|
a022ad5c7a | ||
|
|
0f31f1a3c2 | ||
|
|
1ee71c1f20 | ||
|
|
0dc424031b | ||
|
|
0d899b3076 | ||
|
|
3fd9fca32f | ||
|
|
07d7e9d5bb | ||
|
|
31b8ab9b4b | ||
|
|
60c4e9b257 | ||
|
|
8e9af708f8 | ||
|
|
24b670ac36 | ||
|
|
cc6c731af6 | ||
|
|
c53acd4184 | ||
|
|
faa74f4c6c | ||
|
|
077dff7f68 | ||
|
|
a4752d6a27 | ||
|
|
a9f6e04a84 | ||
|
|
d9224e682f | ||
|
|
9822fd97d7 | ||
|
|
bd72ec91ea | ||
|
|
fa9b2c707b | ||
|
|
22401a54a0 | ||
|
|
9c15796c84 | ||
|
|
db9356ba86 | ||
|
|
54237d824a | ||
|
|
7df1054966 | ||
|
|
53772e2931 | ||
|
|
d37d5058bb | ||
|
|
8debf70d5c | ||
|
|
1a1e804c00 | ||
|
|
c7583a5633 | ||
|
|
470e218bb8 | ||
|
|
488f3d1bed | ||
|
|
0f6137dd04 | ||
|
|
f5f6e5768d | ||
|
|
c6dae3a711 | ||
|
|
4242297e72 | ||
|
|
8d4be0f586 | ||
|
|
d78e0189e0 | ||
|
|
cef131812c | ||
|
|
3c78a0df43 | ||
|
|
991e8b8061 | ||
|
|
dbdbd8a379 | ||
|
|
a0a0883578 | ||
|
|
a4bf70eecf | ||
|
|
5bc41b2609 | ||
|
|
58fd4170b5 | ||
|
|
3369e48260 | ||
|
|
106026614d | ||
|
|
3f2e5929d9 | ||
|
|
c93ea035fc | ||
|
|
847997672d | ||
|
|
08ee83c572 | ||
|
|
892cc2bc07 | ||
|
|
2a077cae5e | ||
|
|
1ee4de18be | ||
|
|
b44f8ebf5f | ||
|
|
6f72785a28 | ||
|
|
be78fe8374 | ||
|
|
01c004a7af | ||
|
|
0b36d08b92 | ||
|
|
bf9c53a4c3 | ||
|
|
80c5363649 | ||
|
|
9d899ae0e2 | ||
|
|
13bd6fc0bf | ||
|
|
18b3c49234 | ||
|
|
84ea68927f | ||
|
|
1af9cdd351 | ||
|
|
25bcbac309 | ||
|
|
20aad80a78 | ||
|
|
52875b5c7c | ||
|
|
c665005790 | ||
|
|
b2532ebfe5 | ||
|
|
3461f9ac49 | ||
|
|
d874fc573d | ||
|
|
991c204a88 | ||
|
|
eee17f0e8b | ||
|
|
80608bfdbc | ||
|
|
6c5f54808d | ||
|
|
bb6608c1d7 | ||
|
|
18e1bf5195 | ||
|
|
76a2d7bfe6 | ||
|
|
ac3f289de6 | ||
|
|
838f82b255 | ||
|
|
5e00c4bfcf | ||
|
|
f0ae56b8ea | ||
|
|
4484c2f192 | ||
|
|
e6bbcb8043 | ||
|
|
ad04e69c35 | ||
|
|
6cae37174c | ||
|
|
aa6de57430 | ||
|
|
a48aa37f87 | ||
|
|
3cc8f86780 | ||
|
|
4cb02a0ead | ||
|
|
221c558cee | ||
|
|
81c3152ebb | ||
|
|
855e9a043e | ||
|
|
7b5d724bb7 | ||
|
|
441f2a3c48 | ||
|
|
efb86bb7ab | ||
|
|
398a0a484f | ||
|
|
55924d8150 | ||
|
|
0cf6f2f83e | ||
|
|
cf18093261 | ||
|
|
1850e209e6 | ||
|
|
2e37188c9f | ||
|
|
4ae7c67d3f | ||
|
|
a76ba156d5 | ||
|
|
cd90a91158 | ||
|
|
e1b23e2526 | ||
|
|
537c3ea71e | ||
|
|
b6f74c7284 | ||
|
|
8cd008ba91 | ||
|
|
1f13e1d348 | ||
|
|
87a15d7bb8 | ||
|
|
f5bdacd07a | ||
|
|
ce80fcd430 | ||
|
|
c1c080b072 | ||
|
|
587187419f | ||
|
|
050a5915ca | ||
|
|
d4e4d12c5a | ||
|
|
72cf2a65a6 | ||
|
|
48db27481d | ||
|
|
1247c5fb33 | ||
|
|
69321418a3 | ||
|
|
301d13a4b9 | ||
|
|
3f8c1e822f | ||
|
|
1e1c1c018b | ||
|
|
a212916a49 | ||
|
|
883ffa1fdb | ||
|
|
21d9d6192e | ||
|
|
fd38a0ac03 | ||
|
|
af6918d57b | ||
|
|
9a9cbc997b | ||
|
|
aec500ee3b | ||
|
|
8fbc721223 | ||
|
|
c3ce3d2b3c | ||
|
|
c46cbaaa4a | ||
|
|
34e8e3ca61 | ||
|
|
5dac8a0a53 | ||
|
|
ed1fec8866 | ||
|
|
2abcb05e22 | ||
|
|
aba43440ca | ||
|
|
d47cbb4f8c | ||
|
|
36006de8ce | ||
|
|
5ccb044b85 | ||
|
|
e40e9a6483 | ||
|
|
717d48b0e0 | ||
|
|
4e7e2f7eab | ||
|
|
5404863a3e | ||
|
|
9d4b6c4d16 | ||
|
|
cd56985480 | ||
|
|
58eb0fa095 | ||
|
|
d850349a1c | ||
|
|
36a609f2ee | ||
|
|
0c5ceb7e72 | ||
|
|
ed04257e6c | ||
|
|
e2b2e568a6 | ||
|
|
dc432c8c3a | ||
|
|
6ffbb8373e | ||
|
|
6bea847232 | ||
|
|
78b94b7043 | ||
|
|
92634d4261 | ||
|
|
007e32c690 | ||
|
|
fe7e6675e0 | ||
|
|
d6f5f060ca | ||
|
|
9d013910e6 | ||
|
|
4c281aa464 | ||
|
|
086805df1d | ||
|
|
5793cd7e38 | ||
|
|
9b8b77cec0 | ||
|
|
5b9824c379 | ||
|
|
2b7db88694 | ||
|
|
fbddeea254 | ||
|
|
7785829969 | ||
|
|
d62abeb407 | ||
|
|
4e00cfd1b6 | ||
|
|
917e5d07ff | ||
|
|
76286f1186 | ||
|
|
93dd696a4f | ||
|
|
36be63af07 | ||
|
|
dc228e8b36 | ||
|
|
058a1dc8fe | ||
|
|
de7da3e960 | ||
|
|
3b0124d3fd | ||
|
|
fb995a79bf | ||
|
|
9d8c060c49 | ||
|
|
0cabceb09c | ||
|
|
3ee24fba51 | ||
|
|
48acb3bd2e | ||
|
|
c6c07957a6 | ||
|
|
0610866b73 | ||
|
|
2afa18d3da | ||
|
|
a0fdc96649 | ||
|
|
b6caaa4268 | ||
|
|
04e6e330a7 | ||
|
|
29329f6203 | ||
|
|
bdd44a0dce | ||
|
|
10d9fc35e6 | ||
|
|
6826422a6a | ||
|
|
ca48fd0719 | ||
|
|
b9298b4023 | ||
|
|
2375f1d962 | ||
|
|
5ee8b9680d | ||
|
|
45cfe4e411 | ||
|
|
30d737c7d7 | ||
|
|
869f0726f5 | ||
|
|
6dda1ae485 | ||
|
|
13f51de5c1 | ||
|
|
5d5f0676e0 | ||
|
|
8a1d7bcd6b | ||
|
|
ccc1f161d2 | ||
|
|
a7917f779a | ||
|
|
7f96d895bb | ||
|
|
bf3b5c2ba7 | ||
|
|
218f64595a | ||
|
|
6d8d553610 | ||
|
|
e10cf34ad6 | ||
|
|
bbf54011ea | ||
|
|
9e173978dc | ||
|
|
d27ec7d62e | ||
|
|
151dcd2895 | ||
|
|
d961d7efe4 | ||
|
|
99285d4656 | ||
|
|
19629e9bb8 | ||
|
|
b89b427a86 | ||
|
|
908d2ce766 | ||
|
|
853610bbbf | ||
|
|
751c7fc29f | ||
|
|
7dd08bc6bf | ||
|
|
2111d083df | ||
|
|
82d37c4978 | ||
|
|
aee3b392a7 | ||
|
|
ddbf3405a0 | ||
|
|
2293cab72c | ||
|
|
5a761f341b | ||
|
|
3c68a121e5 | ||
|
|
c0ae8b9739 | ||
|
|
5ec4a1cbba | ||
|
|
bae74b8181 | ||
|
|
5ecfd6ea20 | ||
|
|
e8ffe59ce2 | ||
|
|
e2ce41a492 | ||
|
|
6546d51a6c | ||
|
|
73059ffc7e | ||
|
|
f1a30f706a | ||
|
|
aa60dc3e17 | ||
|
|
54518bd127 | ||
|
|
8d4ec561c2 | ||
|
|
0463cd1559 | ||
|
|
ca97d7d983 | ||
|
|
7b8ce51263 | ||
|
|
262dfda0aa | ||
|
|
8bc42a5ded | ||
|
|
406712ffa3 | ||
|
|
c2d7187a0b | ||
|
|
e690e5e86b | ||
|
|
e4d5ca11b3 | ||
|
|
181197177c | ||
|
|
f21304c6a8 | ||
|
|
0cf48a2c35 | ||
|
|
6b4fb934f8 | ||
|
|
d1ed1eddef | ||
|
|
0c9f4f6578 | ||
|
|
e1f20487ce | ||
|
|
26b8c6b663 | ||
|
|
3960827a9c | ||
|
|
e419771b04 | ||
|
|
94ce76d679 | ||
|
|
28c064a9b7 | ||
|
|
eeb02453d1 | ||
|
|
cb4b889b20 | ||
|
|
f1e42d1681 | ||
|
|
ca7ce5a8c3 | ||
|
|
810d8d7686 | ||
|
|
dd1895d2c4 | ||
|
|
b5bb85c956 | ||
|
|
36fe48dbc5 | ||
|
|
e5bbffd47c | ||
|
|
566167489b | ||
|
|
3cb360e9ae | ||
|
|
24e3182329 | ||
|
|
49309b43d3 | ||
|
|
6db8ce672c | ||
|
|
9465b82747 | ||
|
|
383d2b218f | ||
|
|
dccd674cf9 | ||
|
|
a679865cce | ||
|
|
15bfa39b23 | ||
|
|
dc3433aaf0 | ||
|
|
25fc285966 | ||
|
|
9022a3a138 | ||
|
|
ca443b8ff1 | ||
|
|
79e066d3f5 | ||
|
|
56831a7392 | ||
|
|
2e82f1564f | ||
|
|
a394c0fdf6 | ||
|
|
20eca78767 | ||
|
|
bba594a1db | ||
|
|
65f00a197b | ||
|
|
ce27053c2d | ||
|
|
610febb5d5 | ||
|
|
c4378d5992 | ||
|
|
f1d741214a | ||
|
|
285974b7d4 | ||
|
|
989c3b174e | ||
|
|
75f95559d6 | ||
|
|
e085e14247 | ||
|
|
368d3a2661 | ||
|
|
3c8fde25ee | ||
|
|
ec0bb53839 | ||
|
|
bfb3fcea4c | ||
|
|
61cd4aea3f | ||
|
|
01b49f0743 | ||
|
|
4a5a49b5bb | ||
|
|
a21cb64a94 | ||
|
|
9a50dffaa0 | ||
|
|
e710ebff1c | ||
|
|
b3caee88e4 | ||
|
|
d9f90e50b8 | ||
|
|
58efb719fa | ||
|
|
355b7071aa | ||
|
|
b994b0b14e | ||
|
|
6c559fbb8d | ||
|
|
b2d74711d9 | ||
|
|
7e60e8f8da | ||
|
|
62955dd16b | ||
|
|
1f7caa6394 | ||
|
|
662e7e9e18 | ||
|
|
e3013d9918 | ||
|
|
0ea2f6d67e | ||
|
|
7692a1d76a | ||
|
|
1c9afc714e | ||
|
|
466f1a3d73 | ||
|
|
061fbaa7bb | ||
|
|
28b045302f | ||
|
|
5a2226c02c | ||
|
|
6f172a5c19 | ||
|
|
a7d180ea5b | ||
|
|
d4bbc8b5ad | ||
|
|
a5bc226f11 | ||
|
|
3a3d9d6146 | ||
|
|
bcd282d3d0 | ||
|
|
eb7949c884 | ||
|
|
e60a4462e5 | ||
|
|
f7f8747512 | ||
|
|
d573af911d | ||
|
|
cf9beb8234 | ||
|
|
7f67eac1bf | ||
|
|
a652e28b4a | ||
|
|
1b17304c4a | ||
|
|
c2cef99b33 | ||
|
|
a769e37615 | ||
|
|
9d2a8d9108 | ||
|
|
e05519ff9f | ||
|
|
67b26072f8 | ||
|
|
2222082631 | ||
|
|
8b0cb4b981 | ||
|
|
9422eff8ab | ||
|
|
e3c4368d32 | ||
|
|
2a641b39c8 | ||
|
|
02b713572b | ||
|
|
74251350bc | ||
|
|
8f745cdbe6 | ||
|
|
81226cd837 | ||
|
|
a2824f7166 | ||
|
|
edbbd86828 | ||
|
|
c58dad2ca4 | ||
|
|
b4befe3a10 | ||
|
|
d98933c2e7 | ||
|
|
03dfa3816d | ||
|
|
ad1261ce54 | ||
|
|
3252f9cf19 | ||
|
|
f1cdf3df15 | ||
|
|
03ddb8a708 | ||
|
|
2678c6bc9f | ||
|
|
48c071297f | ||
|
|
7e9a16d022 | ||
|
|
84b388f649 | ||
|
|
671d0c746c | ||
|
|
0e4b117161 | ||
|
|
a70bc3c1c7 | ||
|
|
723d161c63 | ||
|
|
d560020592 | ||
|
|
00451f8239 | ||
|
|
329dfdf8e6 | ||
|
|
4c59af93eb | ||
|
|
6ca8e726f7 | ||
|
|
546eb2d85a | ||
|
|
ec3efc94f5 | ||
|
|
6cffd0d17f | ||
|
|
528d32601b | ||
|
|
56b3044aae | ||
|
|
3a096b1750 | ||
|
|
6f01041178 | ||
|
|
13e2ede763 | ||
|
|
c53ddfd532 | ||
|
|
f86bd7b52e | ||
|
|
6177fc6286 | ||
|
|
0fd952ae2b | ||
|
|
74622dd576 | ||
|
|
4dfa2b9748 | ||
|
|
435424a680 | ||
|
|
dbbefd0558 | ||
|
|
e55d1d470e | ||
|
|
ab69f3b665 | ||
|
|
a28f4994a8 | ||
|
|
349611d52d | ||
|
|
10b965e3c7 | ||
|
|
554a5024c1 | ||
|
|
7d03bc5e17 | ||
|
|
c660b35ed6 | ||
|
|
f3bac38a55 | ||
|
|
61330937f7 | ||
|
|
5ac978b9a3 | ||
|
|
b4159bd590 | ||
|
|
ef4d45d409 | ||
|
|
f210c26c2f | ||
|
|
a55a736363 | ||
|
|
9f2af5abc2 | ||
|
|
fee98a58eb | ||
|
|
1ab8f2f0ac | ||
|
|
e7fbc8b391 | ||
|
|
8caab36c3f | ||
|
|
0c4794b060 | ||
|
|
782e3f238b | ||
|
|
e1c7e0a99b | ||
|
|
6ef70484c7 | ||
|
|
621170d9c9 | ||
|
|
b6e2255e9e | ||
|
|
3ce8eae72f | ||
|
|
81aa1883fd | ||
|
|
534dedb608 | ||
|
|
cff1704d7b | ||
|
|
0ca444895f | ||
|
|
a9865209a1 | ||
|
|
8526e8b4a6 | ||
|
|
a52ef3c04a | ||
|
|
1f3f5c2e27 | ||
|
|
6eebfcfe77 | ||
|
|
9d8b69abda | ||
|
|
60aa601e92 | ||
|
|
fc1fd538bd | ||
|
|
40c1761840 | ||
|
|
0ab0e8671d | ||
|
|
7a7c828fc7 | ||
|
|
5cbe473eb9 | ||
|
|
caf2f61563 | ||
|
|
9dc4deccb6 | ||
|
|
476e7d1010 |
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "prowler-plugins",
|
||||||
|
"description": "Prowler Cloud Security for Claude Code",
|
||||||
|
"owner": {
|
||||||
|
"name": "Prowler",
|
||||||
|
"email": "support@prowler.com"
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "prowler",
|
||||||
|
"source": "./claude_plugins/prowler",
|
||||||
|
"description": "Prowler for Claude Code — cloud security and compliance skills powered by the Prowler MCP server. Bundles compliance triage and remediation; more skills coming.",
|
||||||
|
"category": "security",
|
||||||
|
"homepage": "https://prowler.com"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+8
-1
@@ -11,7 +11,14 @@ envs = "wt step copy-ignored"
|
|||||||
[[pre-start]]
|
[[pre-start]]
|
||||||
deps = "uv sync"
|
deps = "uv sync"
|
||||||
|
|
||||||
# Block 3: reminder - last visible output before `wt switch` returns.
|
# Block 3: prepare pnpm via corepack.
|
||||||
|
[[pre-start]]
|
||||||
|
corepack-enable = "corepack enable"
|
||||||
|
|
||||||
|
[[pre-start]]
|
||||||
|
corepack-install = "cd ui && corepack install"
|
||||||
|
|
||||||
|
# Block 4: reminder - last visible output before `wt switch` returns.
|
||||||
# Hooks can't mutate the parent shell, so venv activation is manual.
|
# Hooks can't mutate the parent shell, so venv activation is manual.
|
||||||
[[pre-start]]
|
[[pre-start]]
|
||||||
reminder = "echo '>> Reminder: activate the venv in this shell with: source .venv/bin/activate'"
|
reminder = "echo '>> Reminder: activate the venv in this shell with: source .venv/bin/activate'"
|
||||||
|
|||||||
@@ -6,14 +6,20 @@
|
|||||||
PROWLER_UI_VERSION="stable"
|
PROWLER_UI_VERSION="stable"
|
||||||
AUTH_URL=http://localhost:3000
|
AUTH_URL=http://localhost:3000
|
||||||
API_BASE_URL=http://prowler-api:8080/api/v1
|
API_BASE_URL=http://prowler-api:8080/api/v1
|
||||||
|
# deprecated, use UI_API_BASE_URL
|
||||||
NEXT_PUBLIC_API_BASE_URL=${API_BASE_URL}
|
NEXT_PUBLIC_API_BASE_URL=${API_BASE_URL}
|
||||||
|
UI_API_BASE_URL=${API_BASE_URL}
|
||||||
|
# deprecated, use UI_API_DOCS_URL
|
||||||
NEXT_PUBLIC_API_DOCS_URL=http://prowler-api:8080/api/v1/docs
|
NEXT_PUBLIC_API_DOCS_URL=http://prowler-api:8080/api/v1/docs
|
||||||
|
UI_API_DOCS_URL=http://prowler-api:8080/api/v1/docs
|
||||||
AUTH_TRUST_HOST=true
|
AUTH_TRUST_HOST=true
|
||||||
UI_PORT=3000
|
UI_PORT=3000
|
||||||
# openssl rand -base64 32
|
# openssl rand -base64 32
|
||||||
AUTH_SECRET="N/c6mnaS5+SWq81+819OrzQZlmx1Vxtp/orjttJSmw8="
|
AUTH_SECRET="N/c6mnaS5+SWq81+819OrzQZlmx1Vxtp/orjttJSmw8="
|
||||||
# Google Tag Manager ID
|
# Google Tag Manager ID (empty/unset ⇒ GTM not loaded, zero egress)
|
||||||
|
# deprecated, use UI_GOOGLE_TAG_MANAGER_ID
|
||||||
NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID=""
|
NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID=""
|
||||||
|
UI_GOOGLE_TAG_MANAGER_ID=""
|
||||||
|
|
||||||
#### MCP Server ####
|
#### MCP Server ####
|
||||||
PROWLER_MCP_VERSION=stable
|
PROWLER_MCP_VERSION=stable
|
||||||
@@ -66,8 +72,8 @@ NEO4J_APOC_IMPORT_FILE_ENABLED=false
|
|||||||
NEO4J_APOC_IMPORT_FILE_USE_NEO4J_CONFIG=true
|
NEO4J_APOC_IMPORT_FILE_USE_NEO4J_CONFIG=true
|
||||||
NEO4J_APOC_TRIGGER_ENABLED=false
|
NEO4J_APOC_TRIGGER_ENABLED=false
|
||||||
NEO4J_DBMS_CONNECTOR_BOLT_LISTEN_ADDRESS=0.0.0.0:7687
|
NEO4J_DBMS_CONNECTOR_BOLT_LISTEN_ADDRESS=0.0.0.0:7687
|
||||||
# Neo4j Prowler settings
|
# Attack Paths graph settings
|
||||||
ATTACK_PATHS_BATCH_SIZE=1000
|
ATTACK_PATHS_GRAPH_MUTATION_BATCH_SIZE=1000
|
||||||
ATTACK_PATHS_SERVICE_UNAVAILABLE_MAX_RETRIES=3
|
ATTACK_PATHS_SERVICE_UNAVAILABLE_MAX_RETRIES=3
|
||||||
ATTACK_PATHS_READ_QUERY_TIMEOUT_SECONDS=30
|
ATTACK_PATHS_READ_QUERY_TIMEOUT_SECONDS=30
|
||||||
ATTACK_PATHS_MAX_CUSTOM_QUERY_NODES=250
|
ATTACK_PATHS_MAX_CUSTOM_QUERY_NODES=250
|
||||||
@@ -139,13 +145,20 @@ DJANGO_BROKER_VISIBILITY_TIMEOUT=86400
|
|||||||
DJANGO_SENTRY_DSN=
|
DJANGO_SENTRY_DSN=
|
||||||
DJANGO_THROTTLE_TOKEN_OBTAIN=50/minute
|
DJANGO_THROTTLE_TOKEN_OBTAIN=50/minute
|
||||||
|
|
||||||
# Sentry settings
|
# Sentry for the web app (server + browser). The UI_SENTRY_* values load only
|
||||||
SENTRY_ENVIRONMENT=local
|
# when UI_SENTRY_ENABLED="true"; without it they are ignored (default off, zero
|
||||||
|
# egress). The deprecated NEXT_PUBLIC_SENTRY_DSN still activates Sentry without
|
||||||
|
# the flag. SENTRY_RELEASE (unprefixed) feeds the web app's server/edge SDKs.
|
||||||
|
UI_SENTRY_DSN=
|
||||||
|
UI_SENTRY_ENVIRONMENT=local
|
||||||
SENTRY_RELEASE=local
|
SENTRY_RELEASE=local
|
||||||
NEXT_PUBLIC_SENTRY_ENVIRONMENT=${SENTRY_ENVIRONMENT}
|
# Reserved runtime public config (registered now; no UI consumer yet)
|
||||||
|
# UI_POSTHOG_KEY=
|
||||||
|
# UI_POSTHOG_HOST=
|
||||||
|
# REO_DEV_CLIENT_ID=
|
||||||
|
|
||||||
#### Prowler release version ####
|
#### Prowler release version ####
|
||||||
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.27.0
|
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.37.0
|
||||||
|
|
||||||
# Social login credentials
|
# Social login credentials
|
||||||
SOCIAL_GOOGLE_OAUTH_CALLBACK_URL="${AUTH_URL}/api/auth/callback/google"
|
SOCIAL_GOOGLE_OAUTH_CALLBACK_URL="${AUTH_URL}/api/auth/callback/google"
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# SDK
|
# SDK
|
||||||
/* @prowler-cloud/detection-remediation
|
/* @prowler-cloud/detection-remediation
|
||||||
/prowler/ @prowler-cloud/detection-remediation
|
/prowler/ @prowler-cloud/detection-remediation
|
||||||
/prowler/compliance/ @prowler-cloud/compliance
|
|
||||||
/tests/ @prowler-cloud/detection-remediation
|
/tests/ @prowler-cloud/detection-remediation
|
||||||
/dashboard/ @prowler-cloud/detection-remediation
|
/dashboard/ @prowler-cloud/detection-remediation
|
||||||
/docs/ @prowler-cloud/detection-remediation
|
/docs/ @prowler-cloud/detection-remediation
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: 'OSV-Scanner'
|
name: 'OSV-Scanner'
|
||||||
description: 'Install osv-scanner and scan a lockfile, failing on HIGH/CRITICAL/UNKNOWN severity findings. Posts/updates a PR comment with findings on pull_request events (requires pull-requests: write).'
|
description: 'Install osv-scanner and scan a lockfile, failing on CRITICAL severity findings. Posts/updates a PR comment with findings on pull_request events (requires pull-requests: write).'
|
||||||
author: 'Prowler'
|
author: 'Prowler'
|
||||||
|
|
||||||
inputs:
|
inputs:
|
||||||
@@ -7,9 +7,9 @@ inputs:
|
|||||||
description: 'Path to the lockfile to scan, relative to the repository root (e.g. uv.lock, api/uv.lock, ui/pnpm-lock.yaml).'
|
description: 'Path to the lockfile to scan, relative to the repository root (e.g. uv.lock, api/uv.lock, ui/pnpm-lock.yaml).'
|
||||||
required: true
|
required: true
|
||||||
severity-levels:
|
severity-levels:
|
||||||
description: 'Comma-separated severity levels that fail the scan. Default: HIGH,CRITICAL,UNKNOWN.'
|
description: 'Comma-separated severity levels that fail the scan. Default: CRITICAL.'
|
||||||
required: false
|
required: false
|
||||||
default: 'HIGH,CRITICAL,UNKNOWN'
|
default: 'CRITICAL'
|
||||||
version:
|
version:
|
||||||
description: 'osv-scanner release tag to install. When overriding, you MUST also override binary-sha256.'
|
description: 'osv-scanner release tag to install. When overriding, you MUST also override binary-sha256.'
|
||||||
required: false
|
required: false
|
||||||
|
|||||||
@@ -43,8 +43,17 @@ runs:
|
|||||||
if: github.repository_owner == 'prowler-cloud' && github.repository != 'prowler-cloud/prowler'
|
if: github.repository_owner == 'prowler-cloud' && github.repository != 'prowler-cloud/prowler'
|
||||||
shell: bash
|
shell: bash
|
||||||
working-directory: ${{ inputs.working-directory }}
|
working-directory: ${{ inputs.working-directory }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
run: |
|
run: |
|
||||||
LATEST_COMMIT=$(curl -s "https://api.github.com/repos/prowler-cloud/prowler/commits/master" | jq -r '.sha')
|
LATEST_COMMIT=$(curl -sf --retry 3 --retry-all-errors --retry-delay 2 --retry-max-time 60 \
|
||||||
|
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
|
||||||
|
-H "Accept: application/vnd.github+json" \
|
||||||
|
"https://api.github.com/repos/prowler-cloud/prowler/commits/master" \
|
||||||
|
| jq -er '.sha') || {
|
||||||
|
echo "::error::Failed to fetch latest prowler/master commit from the GitHub API (HTTP error or missing .sha). Check the GITHUB_TOKEN and API rate limits."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
echo "Latest commit hash: $LATEST_COMMIT"
|
echo "Latest commit hash: $LATEST_COMMIT"
|
||||||
sed -i "s|\(git = \"https://github\.com/prowler-cloud/prowler\.git?rev=master\)#[a-f0-9]\{40\}\"|\1#${LATEST_COMMIT}\"|g" uv.lock
|
sed -i "s|\(git = \"https://github\.com/prowler-cloud/prowler\.git?rev=master\)#[a-f0-9]\{40\}\"|\1#${LATEST_COMMIT}\"|g" uv.lock
|
||||||
echo "Updated uv.lock entry:"
|
echo "Updated uv.lock entry:"
|
||||||
@@ -54,8 +63,17 @@ runs:
|
|||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository == 'prowler-cloud/prowler'
|
if: github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository == 'prowler-cloud/prowler'
|
||||||
shell: bash
|
shell: bash
|
||||||
working-directory: ${{ inputs.working-directory }}
|
working-directory: ${{ inputs.working-directory }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
run: |
|
run: |
|
||||||
LATEST_COMMIT=$(curl -s "https://api.github.com/repos/prowler-cloud/prowler/commits/master" | jq -r '.sha')
|
LATEST_COMMIT=$(curl -sf --retry 3 --retry-all-errors --retry-delay 2 --retry-max-time 60 \
|
||||||
|
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
|
||||||
|
-H "Accept: application/vnd.github+json" \
|
||||||
|
"https://api.github.com/repos/prowler-cloud/prowler/commits/master" \
|
||||||
|
| jq -er '.sha') || {
|
||||||
|
echo "::error::Failed to fetch latest prowler/master commit from the GitHub API (HTTP error or missing .sha). Check the GITHUB_TOKEN and API rate limits."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
echo "Latest commit hash: $LATEST_COMMIT"
|
echo "Latest commit hash: $LATEST_COMMIT"
|
||||||
sed -i "s|\(git = \"https://github\.com/prowler-cloud/prowler\.git?rev=master\)#[a-f0-9]\{40\}\"|\1#${LATEST_COMMIT}\"|g" uv.lock
|
sed -i "s|\(git = \"https://github\.com/prowler-cloud/prowler\.git?rev=master\)#[a-f0-9]\{40\}\"|\1#${LATEST_COMMIT}\"|g" uv.lock
|
||||||
echo "Updated uv.lock entry:"
|
echo "Updated uv.lock entry:"
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ runs:
|
|||||||
exit-code: '0'
|
exit-code: '0'
|
||||||
scanners: 'vuln'
|
scanners: 'vuln'
|
||||||
timeout: '5m'
|
timeout: '5m'
|
||||||
version: 'v0.69.2'
|
version: 'v0.71.2'
|
||||||
|
|
||||||
- name: Run Trivy vulnerability scan (SARIF)
|
- name: Run Trivy vulnerability scan (SARIF)
|
||||||
if: inputs.upload-sarif == 'true' && github.event_name == 'push'
|
if: inputs.upload-sarif == 'true' && github.event_name == 'push'
|
||||||
@@ -76,7 +76,7 @@ runs:
|
|||||||
exit-code: '0'
|
exit-code: '0'
|
||||||
scanners: 'vuln'
|
scanners: 'vuln'
|
||||||
timeout: '5m'
|
timeout: '5m'
|
||||||
version: 'v0.69.2'
|
version: 'v0.71.2'
|
||||||
|
|
||||||
- name: Upload Trivy results to GitHub Security tab
|
- name: Upload Trivy results to GitHub Security tab
|
||||||
if: inputs.upload-sarif == 'true' && github.event_name == 'push'
|
if: inputs.upload-sarif == 'true' && github.event_name == 'push'
|
||||||
|
|||||||
@@ -5,10 +5,20 @@
|
|||||||
"version": "v8",
|
"version": "v8",
|
||||||
"sha": "ed597411d8f924073f98dfc5c65a23a2325f34cd"
|
"sha": "ed597411d8f924073f98dfc5c65a23a2325f34cd"
|
||||||
},
|
},
|
||||||
|
"github/gh-aw-actions/setup@v0.81.6": {
|
||||||
|
"repo": "github/gh-aw-actions/setup",
|
||||||
|
"version": "v0.81.6",
|
||||||
|
"sha": "ba6380cc6e5be5d21677bebe04d52fb48e3abec7"
|
||||||
|
},
|
||||||
"github/gh-aw/actions/setup@v0.43.23": {
|
"github/gh-aw/actions/setup@v0.43.23": {
|
||||||
"repo": "github/gh-aw/actions/setup",
|
"repo": "github/gh-aw/actions/setup",
|
||||||
"version": "v0.43.23",
|
"version": "v0.43.23",
|
||||||
"sha": "9382be3ca9ac18917e111a99d4e6bbff58d0dccc"
|
"sha": "9382be3ca9ac18917e111a99d4e6bbff58d0dccc"
|
||||||
|
},
|
||||||
|
"step-security/harden-runner@v2.20.0": {
|
||||||
|
"repo": "step-security/harden-runner",
|
||||||
|
"version": "v2.20.0",
|
||||||
|
"sha": "bf7454d06d71f1098171f2acdf0cd4708d7b5920"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,138 +0,0 @@
|
|||||||
# To get started with Dependabot version updates, you'll need to specify which
|
|
||||||
# package ecosystems to update and where the package manifests are located.
|
|
||||||
# Please see the documentation for all configuration options:
|
|
||||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
|
||||||
|
|
||||||
version: 2
|
|
||||||
updates:
|
|
||||||
# v5
|
|
||||||
- package-ecosystem: "pip"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "monthly"
|
|
||||||
open-pull-requests-limit: 25
|
|
||||||
target-branch: master
|
|
||||||
labels:
|
|
||||||
- "dependencies"
|
|
||||||
- "pip"
|
|
||||||
cooldown:
|
|
||||||
default-days: 7
|
|
||||||
|
|
||||||
# Dependabot Updates are temporary disabled - 2025/03/19
|
|
||||||
# - package-ecosystem: "pip"
|
|
||||||
# directory: "/api"
|
|
||||||
# schedule:
|
|
||||||
# interval: "daily"
|
|
||||||
# open-pull-requests-limit: 10
|
|
||||||
# target-branch: master
|
|
||||||
# labels:
|
|
||||||
# - "dependencies"
|
|
||||||
# - "pip"
|
|
||||||
# - "component/api"
|
|
||||||
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "monthly"
|
|
||||||
open-pull-requests-limit: 25
|
|
||||||
target-branch: master
|
|
||||||
labels:
|
|
||||||
- "dependencies"
|
|
||||||
- "github_actions"
|
|
||||||
cooldown:
|
|
||||||
default-days: 7
|
|
||||||
|
|
||||||
# Dependabot Updates are temporary disabled - 2025/03/19
|
|
||||||
# - package-ecosystem: "npm"
|
|
||||||
# directory: "/ui"
|
|
||||||
# schedule:
|
|
||||||
# interval: "daily"
|
|
||||||
# open-pull-requests-limit: 10
|
|
||||||
# target-branch: master
|
|
||||||
# labels:
|
|
||||||
# - "dependencies"
|
|
||||||
# - "npm"
|
|
||||||
# - "component/ui"
|
|
||||||
|
|
||||||
- package-ecosystem: "docker"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "monthly"
|
|
||||||
open-pull-requests-limit: 25
|
|
||||||
target-branch: master
|
|
||||||
labels:
|
|
||||||
- "dependencies"
|
|
||||||
- "docker"
|
|
||||||
cooldown:
|
|
||||||
default-days: 7
|
|
||||||
|
|
||||||
- package-ecosystem: "pre-commit"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "monthly"
|
|
||||||
open-pull-requests-limit: 25
|
|
||||||
target-branch: master
|
|
||||||
labels:
|
|
||||||
- "dependencies"
|
|
||||||
- "pre-commit"
|
|
||||||
cooldown:
|
|
||||||
default-days: 7
|
|
||||||
|
|
||||||
# Dependabot Updates are temporary disabled - 2025/04/15
|
|
||||||
# v4.6
|
|
||||||
# - package-ecosystem: "pip"
|
|
||||||
# directory: "/"
|
|
||||||
# schedule:
|
|
||||||
# interval: "weekly"
|
|
||||||
# open-pull-requests-limit: 10
|
|
||||||
# target-branch: v4.6
|
|
||||||
# labels:
|
|
||||||
# - "dependencies"
|
|
||||||
# - "pip"
|
|
||||||
# - "v4"
|
|
||||||
|
|
||||||
# - package-ecosystem: "github-actions"
|
|
||||||
# directory: "/"
|
|
||||||
# schedule:
|
|
||||||
# interval: "weekly"
|
|
||||||
# open-pull-requests-limit: 10
|
|
||||||
# target-branch: v4.6
|
|
||||||
# labels:
|
|
||||||
# - "dependencies"
|
|
||||||
# - "github_actions"
|
|
||||||
# - "v4"
|
|
||||||
|
|
||||||
# - package-ecosystem: "docker"
|
|
||||||
# directory: "/"
|
|
||||||
# schedule:
|
|
||||||
# interval: "weekly"
|
|
||||||
# open-pull-requests-limit: 10
|
|
||||||
# target-branch: v4.6
|
|
||||||
# labels:
|
|
||||||
# - "dependencies"
|
|
||||||
# - "docker"
|
|
||||||
# - "v4"
|
|
||||||
|
|
||||||
# Dependabot Updates are temporary disabled - 2025/03/19
|
|
||||||
# v3
|
|
||||||
# - package-ecosystem: "pip"
|
|
||||||
# directory: "/"
|
|
||||||
# schedule:
|
|
||||||
# interval: "monthly"
|
|
||||||
# open-pull-requests-limit: 10
|
|
||||||
# target-branch: v3
|
|
||||||
# labels:
|
|
||||||
# - "dependencies"
|
|
||||||
# - "pip"
|
|
||||||
# - "v3"
|
|
||||||
|
|
||||||
# - package-ecosystem: "github-actions"
|
|
||||||
# directory: "/"
|
|
||||||
# schedule:
|
|
||||||
# interval: "monthly"
|
|
||||||
# open-pull-requests-limit: 10
|
|
||||||
# target-branch: v3
|
|
||||||
# labels:
|
|
||||||
# - "dependencies"
|
|
||||||
# - "github_actions"
|
|
||||||
# - "v3"
|
|
||||||
@@ -77,6 +77,11 @@ provider/okta:
|
|||||||
- any-glob-to-any-file: "prowler/providers/okta/**"
|
- any-glob-to-any-file: "prowler/providers/okta/**"
|
||||||
- any-glob-to-any-file: "tests/providers/okta/**"
|
- any-glob-to-any-file: "tests/providers/okta/**"
|
||||||
|
|
||||||
|
provider/linode:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: "prowler/providers/linode/**"
|
||||||
|
- any-glob-to-any-file: "tests/providers/linode/**"
|
||||||
|
|
||||||
github_actions:
|
github_actions:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file: ".github/workflows/*"
|
- any-glob-to-any-file: ".github/workflows/*"
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ Please add a detailed description of how to review this PR.
|
|||||||
|
|
||||||
<summary><b>Community Checklist</b></summary>
|
<summary><b>Community Checklist</b></summary>
|
||||||
|
|
||||||
- [ ] This feature/issue is listed in [here](https://github.com/prowler-cloud/prowler/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen) or roadmap.prowler.com
|
- [ ] This feature/issue is listed in the [open issues](https://github.com/prowler-cloud/prowler/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen) or roadmap.prowler.com
|
||||||
- [ ] Is it assigned to me, if not, request it via the issue/feature in [here](https://github.com/prowler-cloud/prowler/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen) or [Prowler Community Slack](goto.prowler.com/slack)
|
- [ ] Is it assigned to me, if not, request it via the [open issues](https://github.com/prowler-cloud/prowler/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen) or [Prowler Community Slack](https://goto.prowler.com/slack)
|
||||||
|
- [ ] I have reviewed the [open pull requests](https://github.com/prowler-cloud/prowler/pulls?q=sort%3Aupdated-desc+is%3Apr+is%3Aopen) and confirmed there is no existing PR that implements the same outcome
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@@ -27,8 +28,8 @@ Please add a detailed description of how to review this PR.
|
|||||||
- [ ] Review if the code is being covered by tests.
|
- [ ] Review if the code is being covered by tests.
|
||||||
- [ ] Review if code is being documented following this specification https://github.com/google/styleguide/blob/gh-pages/pyguide.md#38-comments-and-docstrings
|
- [ ] Review if code is being documented following this specification https://github.com/google/styleguide/blob/gh-pages/pyguide.md#38-comments-and-docstrings
|
||||||
- [ ] Review if backport is needed.
|
- [ ] Review if backport is needed.
|
||||||
- [ ] Review if is needed to change the [Readme.md](https://github.com/prowler-cloud/prowler/blob/master/README.md)
|
- [ ] Review if is needed to change the [README.md](https://github.com/prowler-cloud/prowler/blob/master/README.md)
|
||||||
- [ ] Ensure new entries are added to [CHANGELOG.md](https://github.com/prowler-cloud/prowler/blob/master/prowler/CHANGELOG.md), if applicable.
|
- [ ] Ensure a changelog fragment is added under [prowler/changelog.d/](https://github.com/prowler-cloud/prowler/tree/master/prowler/changelog.d), if applicable.
|
||||||
|
|
||||||
#### SDK/CLI
|
#### SDK/CLI
|
||||||
- Are there new checks included in this PR? Yes / No
|
- Are there new checks included in this PR? Yes / No
|
||||||
@@ -40,7 +41,7 @@ Please add a detailed description of how to review this PR.
|
|||||||
- [ ] Screenshots/Video of the functionality flow (if applicable) - Mobile (X < 640px)
|
- [ ] Screenshots/Video of the functionality flow (if applicable) - Mobile (X < 640px)
|
||||||
- [ ] Screenshots/Video of the functionality flow (if applicable) - Table (640px > X < 1024px)
|
- [ ] Screenshots/Video of the functionality flow (if applicable) - Table (640px > X < 1024px)
|
||||||
- [ ] Screenshots/Video of the functionality flow (if applicable) - Desktop (X > 1024px)
|
- [ ] Screenshots/Video of the functionality flow (if applicable) - Desktop (X > 1024px)
|
||||||
- [ ] Ensure new entries are added to [CHANGELOG.md](https://github.com/prowler-cloud/prowler/blob/master/ui/CHANGELOG.md), if applicable.
|
- [ ] Ensure a changelog fragment is added under [ui/changelog.d/](https://github.com/prowler-cloud/prowler/tree/master/ui/changelog.d), if applicable.
|
||||||
|
|
||||||
#### API
|
#### API
|
||||||
- [ ] All issue/task requirements work as expected on the API
|
- [ ] All issue/task requirements work as expected on the API
|
||||||
@@ -50,7 +51,11 @@ Please add a detailed description of how to review this PR.
|
|||||||
- [ ] Any other relevant evidence of the implementation (if applicable)
|
- [ ] Any other relevant evidence of the implementation (if applicable)
|
||||||
- [ ] Verify if API specs need to be regenerated.
|
- [ ] Verify if API specs need to be regenerated.
|
||||||
- [ ] Check if version updates are required (e.g., specs, uv, etc.).
|
- [ ] Check if version updates are required (e.g., specs, uv, etc.).
|
||||||
- [ ] Ensure new entries are added to [CHANGELOG.md](https://github.com/prowler-cloud/prowler/blob/master/api/CHANGELOG.md), if applicable.
|
- [ ] Ensure a changelog fragment is added under [api/changelog.d/](https://github.com/prowler-cloud/prowler/tree/master/api/changelog.d), if applicable.
|
||||||
|
|
||||||
|
#### MCP Server
|
||||||
|
- [ ] All issue/task requirements work as expected on the MCP Server
|
||||||
|
- [ ] Ensure a changelog fragment is added under [mcp_server/changelog.d/](https://github.com/prowler-cloud/prowler/tree/master/mcp_server/changelog.d), if applicable.
|
||||||
|
|
||||||
### License
|
### License
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"config:best-practices",
|
||||||
|
":enablePreCommit",
|
||||||
|
":semanticCommits",
|
||||||
|
":enableVulnerabilityAlertsWithLabel(security)",
|
||||||
|
"docker:enableMajor",
|
||||||
|
"helpers:pinGitHubActionDigestsToSemver",
|
||||||
|
"helpers:disableTypesNodeMajor",
|
||||||
|
"security:openssf-scorecard",
|
||||||
|
"customManagers:githubActionsVersions",
|
||||||
|
"customManagers:dockerfileVersions"
|
||||||
|
],
|
||||||
|
"timezone": "Europe/Madrid",
|
||||||
|
"baseBranchPatterns": [
|
||||||
|
"master"
|
||||||
|
],
|
||||||
|
"labels": [
|
||||||
|
"dependencies"
|
||||||
|
],
|
||||||
|
"dependencyDashboardTitle": "Dependency Dashboard",
|
||||||
|
"prConcurrentLimit": 20,
|
||||||
|
"prHourlyLimit": 10,
|
||||||
|
"vulnerabilityAlerts": {
|
||||||
|
"labels": [
|
||||||
|
"dependencies",
|
||||||
|
"security"
|
||||||
|
],
|
||||||
|
"prHourlyLimit": 0,
|
||||||
|
"prConcurrentLimit": 0
|
||||||
|
},
|
||||||
|
"configMigration": true,
|
||||||
|
"minimumReleaseAge": "7 days",
|
||||||
|
"rangeStrategy": "pin",
|
||||||
|
"packageRules": [
|
||||||
|
{
|
||||||
|
"description": "Patches: 1st of every month, Madrid overnight window (22:00-06:00)",
|
||||||
|
"matchUpdateTypes": [
|
||||||
|
"patch"
|
||||||
|
],
|
||||||
|
"schedule": [
|
||||||
|
"* 22-23,0-5 1 * *"
|
||||||
|
],
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Minors: 8th of every 3 months, Madrid overnight window (22:00-06:00)",
|
||||||
|
"matchUpdateTypes": [
|
||||||
|
"minor"
|
||||||
|
],
|
||||||
|
"schedule": [
|
||||||
|
"* 22-23,0-5 8 */3 *"
|
||||||
|
],
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Majors: 15th of every 3 months, Madrid overnight window",
|
||||||
|
"matchUpdateTypes": [
|
||||||
|
"major"
|
||||||
|
],
|
||||||
|
"schedule": [
|
||||||
|
"* 22-23,0-5 15 */3 *"
|
||||||
|
],
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "gh-aw compiled lock files - generated by 'gh aw compile', action pins must match the compiler version, never bump directly",
|
||||||
|
"matchFileNames": [
|
||||||
|
".github/workflows/*.lock.yml"
|
||||||
|
],
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "GitHub Actions - single grouped PR, no changelog, scope=ci",
|
||||||
|
"matchManagers": [
|
||||||
|
"github-actions"
|
||||||
|
],
|
||||||
|
"groupName": "github-actions",
|
||||||
|
"semanticCommitScope": "ci",
|
||||||
|
"addLabels": [
|
||||||
|
"no-changelog"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Docker images - single grouped PR, no changelog, scope=docker",
|
||||||
|
"matchManagers": [
|
||||||
|
"dockerfile",
|
||||||
|
"docker-compose"
|
||||||
|
],
|
||||||
|
"groupName": "docker",
|
||||||
|
"semanticCommitScope": "docker",
|
||||||
|
"addLabels": [
|
||||||
|
"no-changelog"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Pre-commit hooks - single grouped PR, scope=pre-commit",
|
||||||
|
"matchManagers": [
|
||||||
|
"pre-commit"
|
||||||
|
],
|
||||||
|
"groupName": "pre-commit hooks",
|
||||||
|
"semanticCommitScope": "pre-commit",
|
||||||
|
"addLabels": [
|
||||||
|
"no-changelog"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "UI - scope=ui",
|
||||||
|
"matchFileNames": [
|
||||||
|
"ui/**"
|
||||||
|
],
|
||||||
|
"semanticCommitScope": "ui"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "API - scope=api",
|
||||||
|
"matchFileNames": [
|
||||||
|
"api/**"
|
||||||
|
],
|
||||||
|
"semanticCommitScope": "api"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "MCP server - scope=mcp",
|
||||||
|
"matchFileNames": [
|
||||||
|
"mcp_server/**"
|
||||||
|
],
|
||||||
|
"semanticCommitScope": "mcp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Python SDK (root) - scope=sdk",
|
||||||
|
"matchFileNames": [
|
||||||
|
"pyproject.toml",
|
||||||
|
"poetry.lock",
|
||||||
|
"util/prowler-bulk-provisioning/**"
|
||||||
|
],
|
||||||
|
"semanticCommitScope": "sdk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "UI devDependencies - no changelog",
|
||||||
|
"matchFileNames": [
|
||||||
|
"ui/**"
|
||||||
|
],
|
||||||
|
"matchDepTypes": [
|
||||||
|
"devDependencies"
|
||||||
|
],
|
||||||
|
"addLabels": [
|
||||||
|
"no-changelog"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Rename changelog fragments to their PR number before running towncrier.
|
||||||
|
|
||||||
|
For every <slug>.<type>.md in <component_dir>/changelog.d/, find the commit that
|
||||||
|
added it, resolve its PR via the GitHub API (falling back to the squash-commit
|
||||||
|
subject), and `git mv` it to <PR>.<type>.md so towncrier renders the PR link.
|
||||||
|
Unresolvable fragments become +<slug>.<type>.md orphans (rendered without link).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
FRAGMENT_RE = re.compile(
|
||||||
|
r"^(?P<slug>[A-Za-z0-9][A-Za-z0-9._-]*?)"
|
||||||
|
r"\.(?P<type>added|changed|deprecated|removed|fixed|security)"
|
||||||
|
r"(?:\.(?P<counter>[0-9]+))?\.md$"
|
||||||
|
)
|
||||||
|
SUBJECT_PR_RE = re.compile(r" \(#([0-9]+)\)$")
|
||||||
|
IGNORED_FILES = {".gitkeep", "README.md"}
|
||||||
|
API_TIMEOUT_SECONDS = 10
|
||||||
|
|
||||||
|
|
||||||
|
def git(*args: str) -> str:
|
||||||
|
result = subprocess.run(["git", *args], check=True, capture_output=True, text=True)
|
||||||
|
return result.stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def find_adding_commit(path: str) -> str | None:
|
||||||
|
"""Find the commit that added a file, following renames.
|
||||||
|
|
||||||
|
Falls back to a plain (no --follow) lookup: rename detection can lose the
|
||||||
|
add event for degenerate content (e.g. files identical to many others).
|
||||||
|
"""
|
||||||
|
sha = git("log", "--follow", "--diff-filter=A", "--format=%H", "-1", "--", path)
|
||||||
|
if not sha:
|
||||||
|
sha = git("log", "--diff-filter=A", "--format=%H", "-1", "--", path)
|
||||||
|
return sha or None
|
||||||
|
|
||||||
|
|
||||||
|
def pr_from_api(repo: str, sha: str) -> int | None:
|
||||||
|
"""Resolve the PR associated with a commit via the GitHub API.
|
||||||
|
|
||||||
|
Returns None on any network/API failure so the caller can fall back to
|
||||||
|
parsing the squash-commit subject.
|
||||||
|
"""
|
||||||
|
url = f"https://api.github.com/repos/{repo}/commits/{sha}/pulls"
|
||||||
|
headers = {
|
||||||
|
"Accept": "application/vnd.github+json",
|
||||||
|
"X-GitHub-Api-Version": "2022-11-28",
|
||||||
|
"User-Agent": "prowler-changelog-attribution",
|
||||||
|
}
|
||||||
|
token = os.environ.get("GITHUB_TOKEN")
|
||||||
|
if token:
|
||||||
|
headers["Authorization"] = f"Bearer {token}"
|
||||||
|
request = urllib.request.Request(url, headers=headers)
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(request, timeout=API_TIMEOUT_SECONDS) as response:
|
||||||
|
pulls = json.load(response)
|
||||||
|
except (urllib.error.URLError, TimeoutError, json.JSONDecodeError):
|
||||||
|
return None
|
||||||
|
if isinstance(pulls, list) and pulls:
|
||||||
|
return pulls[0].get("number")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def pr_from_subject(sha: str) -> int | None:
|
||||||
|
subject = git("log", "-1", "--format=%s", sha)
|
||||||
|
match = SUBJECT_PR_RE.search(subject)
|
||||||
|
return int(match.group(1)) if match else None
|
||||||
|
|
||||||
|
|
||||||
|
def unique_destination(directory: str, base_name: str, fragment_type: str) -> str:
|
||||||
|
"""Return a non-colliding fragment path, appending a numeric counter if needed."""
|
||||||
|
candidate = os.path.join(directory, f"{base_name}.{fragment_type}.md")
|
||||||
|
counter = 0
|
||||||
|
while os.path.exists(candidate):
|
||||||
|
counter += 1
|
||||||
|
candidate = os.path.join(directory, f"{base_name}.{fragment_type}.{counter}.md")
|
||||||
|
return candidate
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("component_dir", help="Component directory, e.g. prowler")
|
||||||
|
parser.add_argument("--repo", default="prowler-cloud/prowler")
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-api",
|
||||||
|
action="store_true",
|
||||||
|
help="Skip the GitHub API and resolve PRs from commit subjects only",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
fragments_dir = os.path.join(args.component_dir, "changelog.d")
|
||||||
|
if not os.path.isdir(fragments_dir):
|
||||||
|
print(f"::error::Fragments directory not found: {fragments_dir}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
malformed = []
|
||||||
|
to_process = []
|
||||||
|
for name in sorted(os.listdir(fragments_dir)):
|
||||||
|
if name in IGNORED_FILES or name.startswith("+"):
|
||||||
|
continue
|
||||||
|
match = FRAGMENT_RE.match(name)
|
||||||
|
if not match:
|
||||||
|
malformed.append(name)
|
||||||
|
continue
|
||||||
|
if match.group("slug").isdigit():
|
||||||
|
continue
|
||||||
|
to_process.append((name, match))
|
||||||
|
|
||||||
|
if malformed:
|
||||||
|
for name in malformed:
|
||||||
|
print(
|
||||||
|
f"::error::Malformed fragment filename in {fragments_dir}: {name} "
|
||||||
|
"(expected <slug>.<type>.md with type one of added|changed|"
|
||||||
|
"deprecated|removed|fixed|security)"
|
||||||
|
)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
for name, match in to_process:
|
||||||
|
slug, fragment_type = match.group("slug"), match.group("type")
|
||||||
|
|
||||||
|
path = os.path.join(fragments_dir, name)
|
||||||
|
sha = find_adding_commit(path)
|
||||||
|
pr_number = None
|
||||||
|
if sha:
|
||||||
|
if not args.no_api:
|
||||||
|
pr_number = pr_from_api(args.repo, sha)
|
||||||
|
if pr_number is None:
|
||||||
|
pr_number = pr_from_subject(sha)
|
||||||
|
|
||||||
|
if pr_number is not None:
|
||||||
|
destination = unique_destination(
|
||||||
|
fragments_dir, str(pr_number), fragment_type
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
destination = unique_destination(fragments_dir, f"+{slug}", fragment_type)
|
||||||
|
print(
|
||||||
|
f"::warning::Could not resolve a PR for {path}; renamed to "
|
||||||
|
f"{os.path.basename(destination)} (entry will render without a PR link)"
|
||||||
|
)
|
||||||
|
git("mv", path, destination)
|
||||||
|
print(f"{path} -> {destination}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -6,8 +6,7 @@
|
|||||||
# - .github/workflows/api-security.yml, sdk-security.yml, ui-security.yml
|
# - .github/workflows/api-security.yml, sdk-security.yml, ui-security.yml
|
||||||
#
|
#
|
||||||
# Severity levels (comma-separated) are read from OSV_SEVERITY_LEVELS.
|
# Severity levels (comma-separated) are read from OSV_SEVERITY_LEVELS.
|
||||||
# Default: HIGH,CRITICAL,UNKNOWN — preserves prior .safety-policy.yml policy
|
# Default: CRITICAL — only CVSS >= 9.0 findings fail the scan.
|
||||||
# (ignore-cvss-severity-below: 7 + ignore-cvss-unknown-severity: False).
|
|
||||||
# osv-scanner has no native CVSS threshold (google/osv-scanner#1400, closed
|
# osv-scanner has no native CVSS threshold (google/osv-scanner#1400, closed
|
||||||
# not-planned). Severity is derived from $group.max_severity (numeric CVSS
|
# not-planned). Severity is derived from $group.max_severity (numeric CVSS
|
||||||
# score string) which osv-scanner emits per group.
|
# score string) which osv-scanner emits per group.
|
||||||
@@ -33,7 +32,7 @@ set -euo pipefail
|
|||||||
|
|
||||||
ROOT="$(git rev-parse --show-toplevel)"
|
ROOT="$(git rev-parse --show-toplevel)"
|
||||||
CONFIG="${ROOT}/osv-scanner.toml"
|
CONFIG="${ROOT}/osv-scanner.toml"
|
||||||
SEVERITY_LEVELS="${OSV_SEVERITY_LEVELS:-HIGH,CRITICAL,UNKNOWN}"
|
SEVERITY_LEVELS="${OSV_SEVERITY_LEVELS:-CRITICAL}"
|
||||||
|
|
||||||
for bin in osv-scanner jq; do
|
for bin in osv-scanner jq; do
|
||||||
if ! command -v "${bin}" >/dev/null 2>&1; then
|
if ! command -v "${bin}" >/dev/null 2>&1; then
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{% set category_order = definitions.keys() %}
|
||||||
|
{% for section, _ in sections.items() %}
|
||||||
|
{% for category in category_order if category in sections[section] %}
|
||||||
|
### {{ definitions[category]['name'] }}
|
||||||
|
|
||||||
|
{% for text, values in sections[section][category].items() -%}
|
||||||
|
- {{ text }}{% if values %} {{ values|join(', ') }}{% endif %}{{ "\n" }}
|
||||||
|
{%- endfor %}
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
---
|
||||||
|
{{ "\n" }}
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -53,7 +53,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Check for API changes
|
- name: Check for API changes
|
||||||
id: check-changes
|
id: check-changes
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
api/**
|
api/**
|
||||||
@@ -62,6 +62,7 @@ jobs:
|
|||||||
api/docs/**
|
api/docs/**
|
||||||
api/README.md
|
api/README.md
|
||||||
api/CHANGELOG.md
|
api/CHANGELOG.md
|
||||||
|
api/changelog.d/**
|
||||||
api/AGENTS.md
|
api/AGENTS.md
|
||||||
|
|
||||||
- name: Setup Python with uv
|
- name: Setup Python with uv
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ on:
|
|||||||
- 'api/**'
|
- 'api/**'
|
||||||
- '.github/workflows/api-codeql.yml'
|
- '.github/workflows/api-codeql.yml'
|
||||||
- '.github/codeql/api-codeql-config.yml'
|
- '.github/codeql/api-codeql-config.yml'
|
||||||
|
- '!api/CHANGELOG.md'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- 'master'
|
- 'master'
|
||||||
@@ -17,6 +18,7 @@ on:
|
|||||||
- 'api/**'
|
- 'api/**'
|
||||||
- '.github/workflows/api-codeql.yml'
|
- '.github/workflows/api-codeql.yml'
|
||||||
- '.github/codeql/api-codeql-config.yml'
|
- '.github/codeql/api-codeql-config.yml'
|
||||||
|
- '!api/CHANGELOG.md'
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '00 12 * * *'
|
- cron: '00 12 * * *'
|
||||||
|
|
||||||
@@ -44,7 +46,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -61,12 +63,12 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
|
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
config-file: ./.github/codeql/api-codeql-config.yml
|
config-file: ./.github/codeql/api-codeql-config.yml
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
|
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||||
with:
|
with:
|
||||||
category: '/language:${{ matrix.language }}'
|
category: '/language:${{ matrix.language }}'
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -122,6 +122,7 @@ jobs:
|
|||||||
github.com:443
|
github.com:443
|
||||||
powershellinfraartifacts-gkhedzdeaghdezhr.z01.azurefd.net:443
|
powershellinfraartifacts-gkhedzdeaghdezhr.z01.azurefd.net:443
|
||||||
production.cloudflare.docker.com:443
|
production.cloudflare.docker.com:443
|
||||||
|
production.cloudfront.docker.com:443
|
||||||
pypi.org:443
|
pypi.org:443
|
||||||
registry-1.docker.io:443
|
registry-1.docker.io:443
|
||||||
release-assets.githubusercontent.com:443
|
release-assets.githubusercontent.com:443
|
||||||
@@ -132,14 +133,18 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Pin prowler SDK to latest master commit
|
- name: Refresh prowler SDK pin to current branch tip
|
||||||
if: github.event_name == 'push'
|
|
||||||
run: |
|
run: |
|
||||||
LATEST_SHA=$(git ls-remote https://github.com/prowler-cloud/prowler.git refs/heads/master | cut -f1)
|
# api/pyproject.toml has `@master` on master and `@v5.X` on release
|
||||||
sed -i "s|prowler-cloud/prowler.git@master|prowler-cloud/prowler.git@${LATEST_SHA}|" api/pyproject.toml
|
# branches (set by prepare-release.yml). uv lock --upgrade-package
|
||||||
|
# re-resolves whichever ref is present against the current branch tip
|
||||||
|
# and writes the SHA into api/uv.lock. The Dockerfile runs
|
||||||
|
# `uv sync --locked`, which is what actually drives the install.
|
||||||
|
pip install --no-cache-dir "uv==0.11.14"
|
||||||
|
(cd api && uv lock --upgrade-package prowler)
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
@@ -150,7 +155,7 @@ jobs:
|
|||||||
- name: Build and push API container for ${{ matrix.arch }}
|
- name: Build and push API container for ${{ matrix.arch }}
|
||||||
id: container-push
|
id: container-push
|
||||||
if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||||
with:
|
with:
|
||||||
context: ${{ env.WORKING_DIRECTORY }}
|
context: ${{ env.WORKING_DIRECTORY }}
|
||||||
push: true
|
push: true
|
||||||
@@ -170,7 +175,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -179,8 +184,9 @@ jobs:
|
|||||||
registry-1.docker.io:443
|
registry-1.docker.io:443
|
||||||
auth.docker.io:443
|
auth.docker.io:443
|
||||||
production.cloudflare.docker.com:443
|
production.cloudflare.docker.com:443
|
||||||
|
production.cloudfront.docker.com:443
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
@@ -209,7 +215,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install regctl
|
- name: Install regctl
|
||||||
if: always()
|
if: always()
|
||||||
uses: regclient/actions/regctl-installer@da9319db8e44e8b062b3a147e1dfb2f574d41a03 # main
|
uses: regclient/actions/regctl-installer@9a2d4216180dbb3e2dccfa60d2dd4afd98e42ec5 # main
|
||||||
|
|
||||||
- name: Cleanup intermediate architecture tags
|
- name: Cleanup intermediate architecture tags
|
||||||
if: always()
|
if: always()
|
||||||
@@ -230,7 +236,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -266,27 +272,3 @@ jobs:
|
|||||||
payload-file-path: "./.github/scripts/slack-messages/container-release-completed.json"
|
payload-file-path: "./.github/scripts/slack-messages/container-release-completed.json"
|
||||||
step-outcome: ${{ steps.outcome.outputs.outcome }}
|
step-outcome: ${{ steps.outcome.outputs.outcome }}
|
||||||
update-ts: ${{ needs.notify-release-started.outputs.message-ts }}
|
update-ts: ${{ needs.notify-release-started.outputs.message-ts }}
|
||||||
|
|
||||||
trigger-deployment:
|
|
||||||
needs: [setup, container-build-push]
|
|
||||||
if: always() && github.event_name == 'push' && needs.setup.result == 'success' && needs.container-build-push.result == 'success'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 5
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Harden Runner
|
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
|
||||||
with:
|
|
||||||
egress-policy: block
|
|
||||||
allowed-endpoints: >
|
|
||||||
api.github.com:443
|
|
||||||
|
|
||||||
- name: Trigger API deployment
|
|
||||||
uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
|
||||||
repository: ${{ secrets.CLOUD_DISPATCH }}
|
|
||||||
event-type: api-prowler-deployment
|
|
||||||
client-payload: '{"sha": "${{ github.sha }}", "short_sha": "${{ needs.setup.outputs.short-sha }}"}'
|
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- 'master'
|
- 'master'
|
||||||
- 'v5.*'
|
- 'v5.*'
|
||||||
paths:
|
|
||||||
- 'api/**'
|
|
||||||
- '.github/workflows/api-container-checks.yml'
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
@@ -36,7 +33,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -50,7 +47,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Check if Dockerfile changed
|
- name: Check if Dockerfile changed
|
||||||
id: dockerfile-changed
|
id: dockerfile-changed
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: api/Dockerfile
|
files: api/Dockerfile
|
||||||
|
|
||||||
@@ -72,7 +69,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -83,6 +80,7 @@ jobs:
|
|||||||
registry-1.docker.io:443
|
registry-1.docker.io:443
|
||||||
auth.docker.io:443
|
auth.docker.io:443
|
||||||
production.cloudflare.docker.com:443
|
production.cloudflare.docker.com:443
|
||||||
|
production.cloudfront.docker.com:443
|
||||||
debian.map.fastlydns.net:80
|
debian.map.fastlydns.net:80
|
||||||
release-assets.githubusercontent.com:443
|
release-assets.githubusercontent.com:443
|
||||||
objects.githubusercontent.com:443
|
objects.githubusercontent.com:443
|
||||||
@@ -94,6 +92,8 @@ jobs:
|
|||||||
_http._tcp.deb.debian.org:443
|
_http._tcp.deb.debian.org:443
|
||||||
powershellinfraartifacts-gkhedzdeaghdezhr.z01.azurefd.net:443
|
powershellinfraartifacts-gkhedzdeaghdezhr.z01.azurefd.net:443
|
||||||
get.trivy.dev:443
|
get.trivy.dev:443
|
||||||
|
raw.githubusercontent.com:443
|
||||||
|
releases.astral.sh:443
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
@@ -103,22 +103,32 @@ jobs:
|
|||||||
|
|
||||||
- name: Check for API changes
|
- name: Check for API changes
|
||||||
id: check-changes
|
id: check-changes
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: api/**
|
files: api/**
|
||||||
files_ignore: |
|
files_ignore: |
|
||||||
api/docs/**
|
api/docs/**
|
||||||
api/README.md
|
api/README.md
|
||||||
api/CHANGELOG.md
|
api/CHANGELOG.md
|
||||||
|
api/changelog.d/**
|
||||||
api/AGENTS.md
|
api/AGENTS.md
|
||||||
|
|
||||||
|
# api-container-build-push.yml resolves the SDK pin to the branch tip
|
||||||
|
# before building, so match it here and scan what ships. Push only: PRs
|
||||||
|
# stay deterministic against the committed lock.
|
||||||
|
- name: Refresh prowler SDK pin to current branch tip
|
||||||
|
if: steps.check-changes.outputs.any_changed == 'true' && github.event_name == 'push'
|
||||||
|
run: |
|
||||||
|
pip install --no-cache-dir "uv==0.11.14"
|
||||||
|
(cd api && uv lock --upgrade-package prowler)
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||||
|
|
||||||
- name: Build container
|
- name: Build container
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||||
with:
|
with:
|
||||||
context: ${{ env.API_WORKING_DIR }}
|
context: ${{ env.API_WORKING_DIR }}
|
||||||
push: false
|
push: false
|
||||||
@@ -133,5 +143,5 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
image-name: ${{ env.IMAGE_NAME }}
|
image-name: ${{ env.IMAGE_NAME }}
|
||||||
image-tag: ${{ github.sha }}
|
image-tag: ${{ github.sha }}
|
||||||
fail-on-critical: 'false'
|
fail-on-critical: 'true'
|
||||||
severity: 'CRITICAL'
|
severity: 'CRITICAL'
|
||||||
|
|||||||
@@ -16,13 +16,6 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- "master"
|
- "master"
|
||||||
- "v5.*"
|
- "v5.*"
|
||||||
paths:
|
|
||||||
- 'api/**'
|
|
||||||
- '.github/workflows/api-tests.yml'
|
|
||||||
- '.github/workflows/api-security.yml'
|
|
||||||
- '.github/actions/setup-python-uv/**'
|
|
||||||
- '.github/actions/osv-scanner/**'
|
|
||||||
- '.github/scripts/osv-scan.sh'
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
@@ -50,7 +43,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -73,7 +66,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Check for API changes
|
- name: Check for API changes
|
||||||
id: check-changes
|
id: check-changes
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
api/**
|
api/**
|
||||||
@@ -84,6 +77,7 @@ jobs:
|
|||||||
api/docs/**
|
api/docs/**
|
||||||
api/README.md
|
api/README.md
|
||||||
api/CHANGELOG.md
|
api/CHANGELOG.md
|
||||||
|
api/changelog.d/**
|
||||||
api/AGENTS.md
|
api/AGENTS.md
|
||||||
|
|
||||||
- name: Setup Python with uv
|
- name: Setup Python with uv
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ jobs:
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:17@sha256:2cd82735a36356842d5eb1ef80db3ae8f1154172f0f653db48fde079b2a0b7f7
|
image: postgres:17@sha256:5c855ad7b85e68e48a62f34662853f38b57c1c1d80f3a927ab58034fd6d31c5e
|
||||||
env:
|
env:
|
||||||
POSTGRES_HOST: ${{ env.POSTGRES_HOST }}
|
POSTGRES_HOST: ${{ env.POSTGRES_HOST }}
|
||||||
POSTGRES_PORT: ${{ env.POSTGRES_PORT }}
|
POSTGRES_PORT: ${{ env.POSTGRES_PORT }}
|
||||||
@@ -63,7 +63,7 @@ jobs:
|
|||||||
--health-timeout 5s
|
--health-timeout 5s
|
||||||
--health-retries 5
|
--health-retries 5
|
||||||
valkey:
|
valkey:
|
||||||
image: valkey/valkey:7-alpine3.19
|
image: valkey/valkey:7-alpine3.19@sha256:4054fe7fc607b9326ac7c4691ed26e9670d2ff17a9fb28c2577adecf928acbcc
|
||||||
env:
|
env:
|
||||||
VALKEY_HOST: ${{ env.VALKEY_HOST }}
|
VALKEY_HOST: ${{ env.VALKEY_HOST }}
|
||||||
VALKEY_PORT: ${{ env.VALKEY_PORT }}
|
VALKEY_PORT: ${{ env.VALKEY_PORT }}
|
||||||
@@ -78,7 +78,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -102,7 +102,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Check for API changes
|
- name: Check for API changes
|
||||||
id: check-changes
|
id: check-changes
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
api/**
|
api/**
|
||||||
@@ -111,6 +111,7 @@ jobs:
|
|||||||
api/docs/**
|
api/docs/**
|
||||||
api/README.md
|
api/README.md
|
||||||
api/CHANGELOG.md
|
api/CHANGELOG.md
|
||||||
|
api/changelog.d/**
|
||||||
api/AGENTS.md
|
api/AGENTS.md
|
||||||
|
|
||||||
- name: Setup Python with uv
|
- name: Setup Python with uv
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
patch_version: ${{ steps.detect.outputs.patch_version }}
|
patch_version: ${{ steps.detect.outputs.patch_version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -139,6 +139,17 @@ jobs:
|
|||||||
sed -i "s|version = \"${CURRENT_API_VERSION}\"|version = \"${NEXT_API_VERSION}\"|" api/pyproject.toml
|
sed -i "s|version = \"${CURRENT_API_VERSION}\"|version = \"${NEXT_API_VERSION}\"|" api/pyproject.toml
|
||||||
sed -i "s| version: ${CURRENT_API_VERSION}| version: ${NEXT_API_VERSION}|" api/src/backend/api/specs/v1.yaml
|
sed -i "s| version: ${CURRENT_API_VERSION}| version: ${NEXT_API_VERSION}|" api/src/backend/api/specs/v1.yaml
|
||||||
|
|
||||||
|
- name: Regenerate lockfiles after version bump
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
# The bumps above edit pyproject.toml / api/pyproject.toml but leave
|
||||||
|
# uv.lock / api/uv.lock stale, which makes `uv sync --locked` fail in
|
||||||
|
# the container builds. Refresh both with the uv version the images
|
||||||
|
# pin (plain `uv lock`, no --upgrade: only the version line changes).
|
||||||
|
pip install --no-cache-dir "uv==0.11.14"
|
||||||
|
uv lock
|
||||||
|
(cd api && uv lock)
|
||||||
|
|
||||||
- name: Bump UI version (.env)
|
- name: Bump UI version (.env)
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
@@ -155,7 +166,7 @@ jobs:
|
|||||||
run: git --no-pager diff
|
run: git --no-pager diff
|
||||||
|
|
||||||
- name: Create PR for next versions to master
|
- name: Create PR for next versions to master
|
||||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||||
with:
|
with:
|
||||||
author: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
|
author: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
|
||||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||||
@@ -191,7 +202,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -240,6 +251,17 @@ jobs:
|
|||||||
sed -i "s|version = \"${CURRENT_API_VERSION}\"|version = \"${FIRST_API_PATCH_VERSION}\"|" api/pyproject.toml
|
sed -i "s|version = \"${CURRENT_API_VERSION}\"|version = \"${FIRST_API_PATCH_VERSION}\"|" api/pyproject.toml
|
||||||
sed -i "s| version: ${CURRENT_API_VERSION}| version: ${FIRST_API_PATCH_VERSION}|" api/src/backend/api/specs/v1.yaml
|
sed -i "s| version: ${CURRENT_API_VERSION}| version: ${FIRST_API_PATCH_VERSION}|" api/src/backend/api/specs/v1.yaml
|
||||||
|
|
||||||
|
- name: Regenerate lockfiles after version bump
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
# The bumps above edit pyproject.toml / api/pyproject.toml but leave
|
||||||
|
# uv.lock / api/uv.lock stale, which makes `uv sync --locked` fail in
|
||||||
|
# the container builds. Refresh both with the uv version the images
|
||||||
|
# pin (plain `uv lock`, no --upgrade: only the version line changes).
|
||||||
|
pip install --no-cache-dir "uv==0.11.14"
|
||||||
|
uv lock
|
||||||
|
(cd api && uv lock)
|
||||||
|
|
||||||
- name: Bump UI version (.env)
|
- name: Bump UI version (.env)
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
@@ -249,7 +271,7 @@ jobs:
|
|||||||
run: git --no-pager diff
|
run: git --no-pager diff
|
||||||
|
|
||||||
- name: Create PR for first patch versions to version branch
|
- name: Create PR for first patch versions to version branch
|
||||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||||
with:
|
with:
|
||||||
author: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
|
author: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
|
||||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||||
@@ -285,7 +307,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -341,6 +363,17 @@ jobs:
|
|||||||
sed -i "s|version = \"${CURRENT_API_VERSION}\"|version = \"${NEXT_API_PATCH_VERSION}\"|" api/pyproject.toml
|
sed -i "s|version = \"${CURRENT_API_VERSION}\"|version = \"${NEXT_API_PATCH_VERSION}\"|" api/pyproject.toml
|
||||||
sed -i "s| version: ${CURRENT_API_VERSION}| version: ${NEXT_API_PATCH_VERSION}|" api/src/backend/api/specs/v1.yaml
|
sed -i "s| version: ${CURRENT_API_VERSION}| version: ${NEXT_API_PATCH_VERSION}|" api/src/backend/api/specs/v1.yaml
|
||||||
|
|
||||||
|
- name: Regenerate lockfiles after version bump
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
# The bumps above edit pyproject.toml / api/pyproject.toml but leave
|
||||||
|
# uv.lock / api/uv.lock stale, which makes `uv sync --locked` fail in
|
||||||
|
# the container builds. Refresh both with the uv version the images
|
||||||
|
# pin (plain `uv lock`, no --upgrade: only the version line changes).
|
||||||
|
pip install --no-cache-dir "uv==0.11.14"
|
||||||
|
uv lock
|
||||||
|
(cd api && uv lock)
|
||||||
|
|
||||||
- name: Bump UI version (.env)
|
- name: Bump UI version (.env)
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
@@ -350,7 +383,7 @@ jobs:
|
|||||||
run: git --no-pager diff
|
run: git --no-pager diff
|
||||||
|
|
||||||
- name: Create PR for next patch versions to version branch
|
- name: Create PR for next patch versions to version branch
|
||||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||||
with:
|
with:
|
||||||
author: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
|
author: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
|
||||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
name: 'Tools: Check Test Init Files'
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
- 'v5.*'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-test-init-files:
|
||||||
|
if: github.repository == 'prowler-cloud/prowler'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
|
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Check for __init__.py files in test directories
|
||||||
|
run: python3 scripts/check_test_init_files.py .
|
||||||
@@ -36,7 +36,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -51,6 +51,6 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Run zizmor
|
- name: Run zizmor
|
||||||
uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
|
uses: zizmorcore/zizmor-action@a16621b09c6db4281f81a93cb393b05dcd7b7165 # v0.5.5
|
||||||
with:
|
with:
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,527 @@
|
|||||||
|
name: 'Tools: Compile Changelogs'
|
||||||
|
|
||||||
|
run-name: 'Compile changelogs for Prowler ${{ inputs.prowler_version }}'
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
prowler_version:
|
||||||
|
description: 'Prowler version being released (e.g., 5.31.0)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
target_branch:
|
||||||
|
description: 'Branch to compile on (master for minor releases, v5.X for patches)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
sdk_version:
|
||||||
|
description: 'SDK version override (empty = mirrors prowler_version; "skip" = hold this component back)'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
api_version:
|
||||||
|
description: 'API version override (empty = auto-derive 1.<prowler_minor + 1>.<prowler_patch>; "skip" = hold back)'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
ui_version:
|
||||||
|
description: 'UI version override (empty = auto-derive 1.<prowler_minor>.<prowler_patch>; "skip" = hold back)'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
mcp_version:
|
||||||
|
description: 'MCP Server version override (empty = auto-derive from pending fragment types; "skip" = hold back)'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ inputs.prowler_version }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
env:
|
||||||
|
PROWLER_VERSION: ${{ inputs.prowler_version }}
|
||||||
|
TARGET_BRANCH: ${{ inputs.target_branch }}
|
||||||
|
SDK_VERSION: ${{ inputs.sdk_version }}
|
||||||
|
API_VERSION: ${{ inputs.api_version }}
|
||||||
|
UI_VERSION: ${{ inputs.ui_version }}
|
||||||
|
MCP_VERSION: ${{ inputs.mcp_version }}
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
compile-changelogs:
|
||||||
|
if: github.event_name == 'workflow_dispatch' && github.repository == 'prowler-cloud/prowler'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 30
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
steps:
|
||||||
|
- name: Harden the runner (Block outbound calls)
|
||||||
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
|
with:
|
||||||
|
egress-policy: block
|
||||||
|
allowed-endpoints: >
|
||||||
|
api.github.com:443
|
||||||
|
github.com:443
|
||||||
|
objects.githubusercontent.com:443
|
||||||
|
pypi.org:443
|
||||||
|
files.pythonhosted.org:443
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
ref: ${{ inputs.target_branch }}
|
||||||
|
fetch-depth: 0 # PR attribution resolves each fragment's adding commit from history
|
||||||
|
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
|
with:
|
||||||
|
python-version: '3.12'
|
||||||
|
|
||||||
|
- name: Install towncrier
|
||||||
|
run: pip install --no-cache-dir towncrier==25.8.0
|
||||||
|
|
||||||
|
- name: Configure Git
|
||||||
|
run: |
|
||||||
|
git config --global user.name 'prowler-bot'
|
||||||
|
git config --global user.email '179230569+prowler-bot@users.noreply.github.com'
|
||||||
|
|
||||||
|
- name: Validate version inputs
|
||||||
|
run: |
|
||||||
|
if [[ ! "$PROWLER_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
echo "::error::Invalid prowler_version syntax: '$PROWLER_VERSION' (must be N.N.N)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ "$TARGET_BRANCH" != "master" ] && [[ ! "$TARGET_BRANCH" =~ ^v[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
echo "::error::Invalid target_branch syntax: '$TARGET_BRANCH' (must be 'master' or vN.N, e.g. v5.31)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
IFS=. read -r prowler_major prowler_minor prowler_patch <<< "$PROWLER_VERSION"
|
||||||
|
prowler_major=$((10#$prowler_major))
|
||||||
|
prowler_minor=$((10#$prowler_minor))
|
||||||
|
prowler_patch=$((10#$prowler_patch))
|
||||||
|
if [ "$prowler_patch" -eq 0 ]; then
|
||||||
|
if [ "$TARGET_BRANCH" != "master" ]; then
|
||||||
|
echo "::error::target_branch must be 'master' for Prowler ${PROWLER_VERSION}; got '${TARGET_BRANCH}'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
expected_target_branch="v${prowler_major}.${prowler_minor}"
|
||||||
|
if [ "$TARGET_BRANCH" != "$expected_target_branch" ]; then
|
||||||
|
echo "::error::target_branch must be '${expected_target_branch}' for Prowler ${PROWLER_VERSION}; got '${TARGET_BRANCH}'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
for pair in "sdk_version:$SDK_VERSION" "api_version:$API_VERSION" "ui_version:$UI_VERSION" "mcp_version:$MCP_VERSION"; do
|
||||||
|
input_name="${pair%%:*}"
|
||||||
|
input_value="${pair#*:}"
|
||||||
|
if [ -n "$input_value" ] && [ "$input_value" != "skip" ] && [[ ! "$input_value" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
echo "::error::Invalid $input_name syntax: '$input_value' (must be N.N.N, empty for auto-derivation, or 'skip')"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Compile changelogs
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
component_input() {
|
||||||
|
case "$1" in
|
||||||
|
prowler) echo "$SDK_VERSION" ;;
|
||||||
|
api) echo "$API_VERSION" ;;
|
||||||
|
ui) echo "$UI_VERSION" ;;
|
||||||
|
mcp_server) echo "$MCP_VERSION" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
version_key() {
|
||||||
|
local version="$1"
|
||||||
|
local major minor patch
|
||||||
|
IFS=. read -r major minor patch <<< "$version"
|
||||||
|
printf '%06d.%06d.%06d' "$((10#$major))" "$((10#$minor))" "$((10#$patch))"
|
||||||
|
}
|
||||||
|
|
||||||
|
pending_fragments() {
|
||||||
|
find "$1/changelog.d" -maxdepth 1 -type f ! -name '.gitkeep' ! -name 'README.md' | sort
|
||||||
|
}
|
||||||
|
|
||||||
|
# The component's last released version is the first stamped heading
|
||||||
|
# of its CHANGELOG.md, the same source prepare-release.yml greps.
|
||||||
|
latest_released_version() {
|
||||||
|
grep -m1 -E '^## \[v?[0-9]+\.[0-9]+\.[0-9]+\]' "$1/CHANGELOG.md" | sed -E 's/^## \[v?([0-9]+\.[0-9]+\.[0-9]+)\].*/\1/'
|
||||||
|
}
|
||||||
|
|
||||||
|
has_removed_fragments() {
|
||||||
|
echo "$1" | grep -qE '\.removed(\.[0-9]+)?\.md$'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Resolve every component's effective version before compiling
|
||||||
|
# anything, so a wrong input cannot leave the tree half-compiled.
|
||||||
|
# Empty input = auto-derive (latest released version + semver bump
|
||||||
|
# from the pending fragment types). 'skip' = hold the component back.
|
||||||
|
errors=0
|
||||||
|
compiling=""
|
||||||
|
for component in prowler api ui mcp_server; do
|
||||||
|
input=$(component_input "$component")
|
||||||
|
fragments=$(pending_fragments "$component")
|
||||||
|
|
||||||
|
if [ "$input" = "skip" ]; then
|
||||||
|
if [ -n "$fragments" ]; then
|
||||||
|
echo "::warning::${component}: held back by request; these pending fragments stay for a future release:"
|
||||||
|
echo "$fragments"
|
||||||
|
fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [ -n "$input" ] && [ -z "$fragments" ]; then
|
||||||
|
echo "::error::${component}: version input '$input' provided but ${component}/changelog.d/ has no pending fragments (wrong input?)"
|
||||||
|
errors=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [ -z "$fragments" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
removed_fragments=false
|
||||||
|
if has_removed_fragments "$fragments"; then
|
||||||
|
removed_fragments=true
|
||||||
|
fi
|
||||||
|
current=$(latest_released_version "$component")
|
||||||
|
if [ -z "$current" ]; then
|
||||||
|
echo "::error::${component}: could not read the latest released version from ${component}/CHANGELOG.md; restore the released heading before compiling"
|
||||||
|
errors=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$input" ]; then
|
||||||
|
effective="$input"
|
||||||
|
mode="explicit"
|
||||||
|
current_key=$(version_key "$current")
|
||||||
|
effective_key=$(version_key "$effective")
|
||||||
|
if [[ "$effective_key" < "$current_key" || "$effective_key" == "$current_key" ]]; then
|
||||||
|
echo "::error::${component}: explicit version '${effective}' must be greater than the latest released version (${current})"
|
||||||
|
errors=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ "$removed_fragments" = "true" ]; then
|
||||||
|
echo "::error::${component}: pending 'removed' fragments imply a major bump (breaking change); provide its version input explicitly"
|
||||||
|
errors=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
# SDK, UI, and API versions are deterministic mirrors of the
|
||||||
|
# Prowler version (the scheme bump-version.yml codifies): the SDK
|
||||||
|
# mirrors it directly, the UI tracks 1.<minor>.<patch>, and the
|
||||||
|
# API is the independent 1.<minor + 1>.<patch> stream. Only the
|
||||||
|
# MCP Server has its own cadence, derived from fragment types.
|
||||||
|
IFS=. read -r _ prowler_minor prowler_patch <<< "$PROWLER_VERSION"
|
||||||
|
prowler_minor=$((10#$prowler_minor))
|
||||||
|
prowler_patch=$((10#$prowler_patch))
|
||||||
|
case "$component" in
|
||||||
|
prowler) effective="$PROWLER_VERSION" ;;
|
||||||
|
ui) effective="1.${prowler_minor}.${prowler_patch}" ;;
|
||||||
|
api) effective="1.$((prowler_minor + 1)).${prowler_patch}" ;;
|
||||||
|
mcp_server)
|
||||||
|
IFS=. read -r major minor patch <<< "$current"
|
||||||
|
major=$((10#$major))
|
||||||
|
minor=$((10#$minor))
|
||||||
|
patch=$((10#$patch))
|
||||||
|
# Prowler patch releases (vN.N target) are maintenance
|
||||||
|
# releases, so the MCP Server bumps patch regardless of
|
||||||
|
# fragment types; a deliberate exception needs the explicit
|
||||||
|
# version input.
|
||||||
|
if [ "$TARGET_BRANCH" != "master" ]; then
|
||||||
|
effective="${major}.${minor}.$((patch + 1))"
|
||||||
|
if echo "$fragments" | grep -qE '\.(added|deprecated)(\.[0-9]+)?\.md$'; then
|
||||||
|
echo "::warning::${component}: 'added'/'deprecated' fragments are shipping in a Prowler patch; auto-derived a patch bump (${current} -> ${effective}), pass the version input to override"
|
||||||
|
fi
|
||||||
|
elif echo "$fragments" | grep -qE '\.(added|changed|deprecated)(\.[0-9]+)?\.md$'; then
|
||||||
|
effective="${major}.$((minor + 1)).0"
|
||||||
|
else
|
||||||
|
effective="${major}.${minor}.$((patch + 1))"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
current_key=$(version_key "$current")
|
||||||
|
effective_key=$(version_key "$effective")
|
||||||
|
if [[ "$effective_key" < "$current_key" || "$effective_key" == "$current_key" ]]; then
|
||||||
|
echo "::error::${component}: auto-derived version '${effective}' is not greater than the latest released version (${current}); check prowler_version or pass the version input explicitly"
|
||||||
|
errors=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
mode="auto"
|
||||||
|
echo "::notice::${component}: version auto-derived ${current} -> ${effective}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$removed_fragments" = "true" ]; then
|
||||||
|
IFS=. read -r current_major _ <<< "$current"
|
||||||
|
current_major=$((10#$current_major))
|
||||||
|
IFS=. read -r effective_major effective_minor effective_patch <<< "$effective"
|
||||||
|
effective_major=$((10#$effective_major))
|
||||||
|
effective_minor=$((10#$effective_minor))
|
||||||
|
effective_patch=$((10#$effective_patch))
|
||||||
|
if [ "$effective_major" -le "$current_major" ] || [ "$effective_minor" -ne 0 ] || [ "$effective_patch" -ne 0 ]; then
|
||||||
|
echo "::error::${component}: removed fragments require a major component release (${current} -> X.0.0 with X > ${current_major}); got ${effective}"
|
||||||
|
errors=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Without the marker the build would insert the new block above the
|
||||||
|
# file header instead of below it.
|
||||||
|
if ! grep -q '^<!-- changelog: release notes start -->$' "$component/CHANGELOG.md"; then
|
||||||
|
echo "::error::${component}/CHANGELOG.md is missing the '<!-- changelog: release notes start -->' marker; restore it after the intro line before compiling"
|
||||||
|
errors=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
# A hand-written UNRELEASED block means someone followed the old
|
||||||
|
# convention; its entries would be left out of the compiled block
|
||||||
|
# and out of the release notes extraction.
|
||||||
|
if grep -q '(Prowler UNRELEASED)' "$component/CHANGELOG.md"; then
|
||||||
|
echo "::error::${component}/CHANGELOG.md contains a hand-written '(Prowler UNRELEASED)' block; convert its entries to fragments in ${component}/changelog.d/ and delete the block before compiling"
|
||||||
|
errors=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "${effective} ${mode}" > "${RUNNER_TEMP}/version-${component}.txt"
|
||||||
|
compiling="${compiling}${component} "
|
||||||
|
done
|
||||||
|
if [ "$errors" -ne 0 ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ -z "$compiling" ]; then
|
||||||
|
echo "::error::Nothing to compile: no component has pending fragments to release"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
body_file="${RUNNER_TEMP}/compile-changelogs-pr-body.md"
|
||||||
|
{
|
||||||
|
echo "### Description"
|
||||||
|
echo ""
|
||||||
|
echo "Compiles the pending changelog fragments into the per-component \`CHANGELOG.md\` files for Prowler v${PROWLER_VERSION}, replacing the manual stamping PR."
|
||||||
|
echo ""
|
||||||
|
echo "| Component | Version | Fragments consumed |"
|
||||||
|
echo "|---|---|---|"
|
||||||
|
} > "$body_file"
|
||||||
|
|
||||||
|
compiled_components=""
|
||||||
|
for component in prowler api ui mcp_server; do
|
||||||
|
if [ ! -f "${RUNNER_TEMP}/version-${component}.txt" ]; then
|
||||||
|
echo "Skipping ${component} (no pending fragments or held back)"
|
||||||
|
echo "| \`${component}\` | - | 0 |" >> "$body_file"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
read -r version mode < "${RUNNER_TEMP}/version-${component}.txt"
|
||||||
|
version_label="$version"
|
||||||
|
if [ "$mode" = "auto" ]; then
|
||||||
|
version_label="${version} (auto)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
count=$(pending_fragments "$component" | wc -l | tr -d ' ')
|
||||||
|
echo "Compiling ${component} ${version} (${count} fragments, ${mode} version)..."
|
||||||
|
|
||||||
|
# Captured before attribution renames them: these original paths are
|
||||||
|
# what the forward-sync deletes on master (backports copy fragments
|
||||||
|
# verbatim, so filenames match across branches).
|
||||||
|
pending_fragments "$component" > "${RUNNER_TEMP}/consumed-${component}.txt"
|
||||||
|
pre_lines=$(wc -l < "$component/CHANGELOG.md")
|
||||||
|
|
||||||
|
# Attribution must run before the build: towncrier renders the
|
||||||
|
# first dotted segment of each filename as the PR number.
|
||||||
|
python .github/scripts/changelog_attribution.py "$component"
|
||||||
|
towncrier build --config "$component/towncrier.toml" --version "$version" --name "Prowler v${PROWLER_VERSION}" --yes
|
||||||
|
|
||||||
|
# The build only inserts lines right after the marker, so the new
|
||||||
|
# stamped block is exactly the added lines following it. Captured
|
||||||
|
# for the forward-sync to master.
|
||||||
|
post_lines=$(wc -l < "$component/CHANGELOG.md")
|
||||||
|
delta=$((post_lines - pre_lines))
|
||||||
|
marker_line=$(grep -n -m1 '^<!-- changelog: release notes start -->$' "$component/CHANGELOG.md" | cut -d: -f1)
|
||||||
|
sed -n "$((marker_line + 1)),$((marker_line + delta))p" "$component/CHANGELOG.md" > "${RUNNER_TEMP}/block-${component}.md"
|
||||||
|
|
||||||
|
compiled_components="${compiled_components}${component} "
|
||||||
|
echo "| \`${component}\` | ${version_label} | ${count} |" >> "$body_file"
|
||||||
|
done
|
||||||
|
echo "COMPILED_COMPONENTS=${compiled_components}" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
{
|
||||||
|
echo ""
|
||||||
|
echo "Review that no pending fragment was dropped (the diff must delete every consumed fragment) and that each new version block is correct, then squash-merge."
|
||||||
|
echo ""
|
||||||
|
echo "### License"
|
||||||
|
echo ""
|
||||||
|
echo "By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license."
|
||||||
|
} >> "$body_file"
|
||||||
|
|
||||||
|
echo "PR_BODY_FILE=${body_file}" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Create compile PR
|
||||||
|
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||||
|
commit-message: 'chore(changelog): v${{ env.PROWLER_VERSION }}'
|
||||||
|
branch: compile-changelogs-${{ env.PROWLER_VERSION }}
|
||||||
|
base: ${{ env.TARGET_BRANCH }}
|
||||||
|
title: 'chore(changelog): v${{ env.PROWLER_VERSION }}'
|
||||||
|
body-path: ${{ env.PR_BODY_FILE }}
|
||||||
|
author: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
|
||||||
|
labels: |
|
||||||
|
no-changelog
|
||||||
|
skip-sync
|
||||||
|
|
||||||
|
# Patch compiles (target_branch = v5.X) leave master holding the consumed
|
||||||
|
# fragments and missing the new version block. This applies the equivalent
|
||||||
|
# change to master: insert the same stamped blocks under the marker and
|
||||||
|
# delete the consumed fragments, so the next minor compile cannot
|
||||||
|
# re-release entries that already shipped in the patch.
|
||||||
|
- name: Apply forward-sync to master
|
||||||
|
if: env.TARGET_BRANCH != 'master'
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
git checkout -B master origin/master
|
||||||
|
|
||||||
|
version_key() {
|
||||||
|
local version="$1"
|
||||||
|
local major minor patch
|
||||||
|
IFS=. read -r major minor patch <<< "$version"
|
||||||
|
printf '%06d.%06d.%06d' "$((10#$major))" "$((10#$minor))" "$((10#$patch))"
|
||||||
|
}
|
||||||
|
|
||||||
|
release_from_heading() {
|
||||||
|
local heading="$1"
|
||||||
|
echo "$heading" | sed -E 's/^## \[[^]]+\] \(Prowler v?([0-9]+\.[0-9]+\.[0-9]+)\).*/\1/'
|
||||||
|
}
|
||||||
|
|
||||||
|
insert_changelog_block_ordered() {
|
||||||
|
local component="$1"
|
||||||
|
local block_file="$2"
|
||||||
|
local changelog="${component}/CHANGELOG.md"
|
||||||
|
local incoming_heading incoming_release incoming_key
|
||||||
|
local marker_line insertion_line duplicate_line total_lines
|
||||||
|
local line heading existing_release existing_key
|
||||||
|
|
||||||
|
marker_line=$(grep -n -m1 '^<!-- changelog: release notes start -->$' "$changelog" | cut -d: -f1)
|
||||||
|
incoming_heading=$(grep -m1 -E '^## \[[^]]+\] \(Prowler v?[0-9]+\.[0-9]+\.[0-9]+\)' "$block_file" || true)
|
||||||
|
if [ -z "$incoming_heading" ]; then
|
||||||
|
echo "::error::${block_file} does not contain a stamped Prowler release heading"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
incoming_release=$(release_from_heading "$incoming_heading")
|
||||||
|
incoming_key=$(version_key "$incoming_release")
|
||||||
|
insertion_line=""
|
||||||
|
duplicate_line=""
|
||||||
|
|
||||||
|
while IFS=: read -r line heading; do
|
||||||
|
existing_release=$(release_from_heading "$heading")
|
||||||
|
existing_key=$(version_key "$existing_release")
|
||||||
|
if [[ "$incoming_key" == "$existing_key" ]]; then
|
||||||
|
duplicate_line="$line"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
if [[ "$incoming_key" > "$existing_key" ]]; then
|
||||||
|
insertion_line="$line"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done < <(grep -n -E '^## \[[^]]+\] \(Prowler v?[0-9]+\.[0-9]+\.[0-9]+\)' "$changelog" || true)
|
||||||
|
|
||||||
|
if [ -n "$duplicate_line" ]; then
|
||||||
|
echo "::error::${changelog} already contains a block for Prowler v${incoming_release} at line ${duplicate_line}; refusing to insert a duplicate"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ -z "$insertion_line" ]; then
|
||||||
|
insertion_line=$(($(wc -l < "$changelog") + 1))
|
||||||
|
fi
|
||||||
|
if [ "$insertion_line" -le "$marker_line" ]; then
|
||||||
|
insertion_line=$((marker_line + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The captured block window can be off by one blank line on either
|
||||||
|
# end (towncrier re-emits the blank after the marker), so strip the
|
||||||
|
# outer blank lines and pad exactly one on each side: the block
|
||||||
|
# must never glue to the marker above or the next heading below.
|
||||||
|
awk '
|
||||||
|
/[^[:space:]]/ { for (i = 0; i < pending; i++) print ""; pending = 0; print; started = 1; next }
|
||||||
|
started { pending++ }
|
||||||
|
' "$block_file" > "${RUNNER_TEMP}/block-normalized.md"
|
||||||
|
|
||||||
|
total_lines=$(wc -l < "$changelog")
|
||||||
|
{
|
||||||
|
head -n "$((insertion_line - 1))" "$changelog"
|
||||||
|
if [ "$insertion_line" -gt 1 ] && [ -n "$(sed -n "$((insertion_line - 1))p" "$changelog")" ]; then
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
cat "${RUNNER_TEMP}/block-normalized.md"
|
||||||
|
if [ "$insertion_line" -le "$total_lines" ]; then
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
tail -n +"$insertion_line" "$changelog"
|
||||||
|
} > "${RUNNER_TEMP}/changelog.tmp"
|
||||||
|
mv "${RUNNER_TEMP}/changelog.tmp" "$changelog"
|
||||||
|
|
||||||
|
echo "::notice::Inserted ${component} changelog block for Prowler v${incoming_release} at line ${insertion_line}"
|
||||||
|
}
|
||||||
|
|
||||||
|
sync_body="${RUNNER_TEMP}/forward-sync-pr-body.md"
|
||||||
|
{
|
||||||
|
echo "### Description"
|
||||||
|
echo ""
|
||||||
|
echo "Forward-syncs the v${PROWLER_VERSION} compiled changelogs from \`${TARGET_BRANCH}\` to \`master\`: inserts the same stamped version blocks under the insertion marker and deletes the consumed fragments, so the next minor compile cannot re-release entries that already shipped in this patch. Opened automatically by the same run that opened the compile PR; review and squash-merge after it."
|
||||||
|
echo ""
|
||||||
|
echo "| Component | Fragments deleted on master | Skipped (only on ${TARGET_BRANCH}) |"
|
||||||
|
echo "|---|---|---|"
|
||||||
|
} > "$sync_body"
|
||||||
|
|
||||||
|
for component in $COMPILED_COMPONENTS; do
|
||||||
|
block_file="${RUNNER_TEMP}/block-${component}.md"
|
||||||
|
consumed_file="${RUNNER_TEMP}/consumed-${component}.txt"
|
||||||
|
|
||||||
|
if ! grep -qm1 '^<!-- changelog: release notes start -->$' "$component/CHANGELOG.md"; then
|
||||||
|
echo "::error::${component}/CHANGELOG.md on master is missing the insertion marker; cannot forward-sync"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
deleted=0
|
||||||
|
skipped=0
|
||||||
|
while IFS= read -r fragment; do
|
||||||
|
if [ -z "$fragment" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [ -f "$fragment" ]; then
|
||||||
|
git rm -q "$fragment"
|
||||||
|
deleted=$((deleted + 1))
|
||||||
|
else
|
||||||
|
echo "::notice::${fragment} does not exist on master (change landed only on ${TARGET_BRANCH}); skipping its deletion"
|
||||||
|
skipped=$((skipped + 1))
|
||||||
|
fi
|
||||||
|
done < "$consumed_file"
|
||||||
|
|
||||||
|
insert_changelog_block_ordered "$component" "$block_file"
|
||||||
|
|
||||||
|
echo "| \`${component}\` | ${deleted} | ${skipped} |" >> "$sync_body"
|
||||||
|
done
|
||||||
|
|
||||||
|
{
|
||||||
|
echo ""
|
||||||
|
echo "### License"
|
||||||
|
echo ""
|
||||||
|
echo "By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license."
|
||||||
|
} >> "$sync_body"
|
||||||
|
|
||||||
|
echo "SYNC_BODY_FILE=${sync_body}" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Create forward-sync PR
|
||||||
|
if: env.TARGET_BRANCH != 'master'
|
||||||
|
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||||
|
commit-message: 'chore(changelog): v${{ env.PROWLER_VERSION }} forward-sync to master'
|
||||||
|
branch: forward-sync-changelogs-${{ env.PROWLER_VERSION }}
|
||||||
|
base: master
|
||||||
|
title: 'chore(changelog): v${{ env.PROWLER_VERSION }} forward-sync to master'
|
||||||
|
body-path: ${{ env.SYNC_BODY_FILE }}
|
||||||
|
author: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
|
||||||
|
labels: |
|
||||||
|
no-changelog
|
||||||
|
skip-sync
|
||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
name: 'Docs: Check Provider Cards Snippet'
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
- 'v5.*'
|
||||||
|
paths:
|
||||||
|
- 'docs/user-guide/providers/**/getting-started-*.mdx'
|
||||||
|
- 'docs/scripts/generate_provider_cards.py'
|
||||||
|
- 'docs/snippets/provider-cards.mdx'
|
||||||
|
- 'api/src/backend/api/models.py'
|
||||||
|
- '.github/workflows/docs-check-provider-cards.yml'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-provider-cards:
|
||||||
|
if: github.repository == 'prowler-cloud/prowler'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||||
|
with:
|
||||||
|
egress-policy: block
|
||||||
|
allowed-endpoints: >
|
||||||
|
github.com:443
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Verify provider cards snippet is up to date
|
||||||
|
run: |
|
||||||
|
if ! python3 docs/scripts/generate_provider_cards.py; then
|
||||||
|
echo "::error::docs/snippets/provider-cards.mdx is out of sync with the provider getting-started pages or the API ProviderChoices enum."
|
||||||
|
echo "Run 'python3 docs/scripts/generate_provider_cards.py' locally and commit the regenerated snippet."
|
||||||
|
echo "--- diff ---"
|
||||||
|
git diff docs/snippets/provider-cards.mdx
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -25,14 +25,15 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
# We can't block as Trufflehog needs to verify secrets against vendors
|
# We can't block as Trufflehog needs to verify secrets against vendors
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
# allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
# github.com:443
|
github.com:443
|
||||||
# ghcr.io:443
|
ghcr.io:443
|
||||||
# pkg-containers.githubusercontent.com:443
|
pkg-containers.githubusercontent.com:443
|
||||||
|
www.formbucket.com:443
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
@@ -44,6 +45,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Scan diff for secrets with TruffleHog
|
- name: Scan diff for secrets with TruffleHog
|
||||||
# Action auto-injects --since-commit/--branch from event payload; passing them in extra_args produces duplicate flags.
|
# Action auto-injects --since-commit/--branch from event payload; passing them in extra_args produces duplicate flags.
|
||||||
uses: trufflesecurity/trufflehog@ef6e76c3c4023279497fab4721ffa071a722fd05 # v3.92.4
|
uses: trufflesecurity/trufflehog@37b77001d0174ebec2fcca2bd83ff83a6d45a3ab # v3.95.3
|
||||||
with:
|
with:
|
||||||
extra_args: --results=verified,unknown
|
extra_args: --results=verified,unknown
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -38,11 +38,14 @@ jobs:
|
|||||||
- name: Set up Helm
|
- name: Set up Helm
|
||||||
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0
|
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0
|
||||||
|
|
||||||
- name: Set appVersion from release tag
|
- name: Set chart version and appVersion from release tag
|
||||||
run: |
|
run: |
|
||||||
RELEASE_TAG="${GITHUB_EVENT_RELEASE_TAG_NAME}"
|
# Strip any leading "v" so the chart version is valid SemVer 2.
|
||||||
echo "Setting appVersion to ${RELEASE_TAG}"
|
RELEASE_TAG="${GITHUB_EVENT_RELEASE_TAG_NAME#v}"
|
||||||
sed -i "s/^appVersion:.*/appVersion: \"${RELEASE_TAG}\"/" ${{ env.CHART_PATH }}/Chart.yaml
|
echo "Setting chart version and appVersion to ${RELEASE_TAG}"
|
||||||
|
# Publish an immutable chart version per release instead of the static
|
||||||
|
# 0.0.1 in source, so every release is a distinct, addressable artifact.
|
||||||
|
yq -i ".version = \"${RELEASE_TAG}\" | .appVersion = \"${RELEASE_TAG}\"" ${{ env.CHART_PATH }}/Chart.yaml
|
||||||
env:
|
env:
|
||||||
GITHUB_EVENT_RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
|
GITHUB_EVENT_RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
|
|||||||
Generated
+1301
-703
File diff suppressed because one or more lines are too long
@@ -12,8 +12,8 @@ if: contains(toJson(github.event.issue.labels), 'status/needs-triage')
|
|||||||
|
|
||||||
timeout-minutes: 12
|
timeout-minutes: 12
|
||||||
|
|
||||||
rate-limit:
|
user-rate-limit:
|
||||||
max: 5
|
max-runs-per-window: 5
|
||||||
window: 60
|
window: 60
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
@@ -30,6 +30,12 @@ permissions:
|
|||||||
engine: copilot
|
engine: copilot
|
||||||
strict: false
|
strict: false
|
||||||
|
|
||||||
|
pre-steps:
|
||||||
|
- name: Harden the runner
|
||||||
|
uses: step-security/harden-runner@v2.20.0
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
imports:
|
imports:
|
||||||
- ../agents/issue-triage.md
|
- ../agents/issue-triage.md
|
||||||
|
|
||||||
@@ -108,7 +114,7 @@ Triage the following GitHub issue using the Prowler Issue Triage Agent persona.
|
|||||||
|
|
||||||
## Sanitized Issue Content
|
## Sanitized Issue Content
|
||||||
|
|
||||||
${{ needs.activation.outputs.text }}
|
${{ steps.sanitized.outputs.text }}
|
||||||
|
|
||||||
## Instructions
|
## Instructions
|
||||||
|
|
||||||
|
|||||||
@@ -27,12 +27,12 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Apply labels to PR
|
- name: Apply labels to PR
|
||||||
uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
|
uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0
|
||||||
with:
|
with:
|
||||||
sync-labels: true
|
sync-labels: true
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Add community label
|
- name: Add community label
|
||||||
if: steps.check_membership.outputs.is_member == 'false'
|
if: steps.check_membership.outputs.is_member == 'false' && github.event.pull_request.user.type != 'Bot'
|
||||||
env:
|
env:
|
||||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
name: 'Docs: Markdown Lint'
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
- 'v5.*'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
- 'v5.*'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
markdown-lint:
|
||||||
|
if: github.repository == 'prowler-cloud/prowler'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
|
with:
|
||||||
|
egress-policy: block
|
||||||
|
allowed-endpoints: >
|
||||||
|
api.github.com:443
|
||||||
|
github.com:443
|
||||||
|
registry.npmjs.org:443
|
||||||
|
release-assets.githubusercontent.com:443
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
|
with:
|
||||||
|
node-version-file: ui/.nvmrc
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||||
|
with:
|
||||||
|
package_json_file: ui/package.json
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Run markdownlint
|
||||||
|
# Pin must match .pre-commit-config.yaml so prek and CI behave identically.
|
||||||
|
# pnpm dlx doesn't accept --ignore-scripts as a flag; the env var
|
||||||
|
# disables postinstall scripts on transitives the same way.
|
||||||
|
env:
|
||||||
|
pnpm_config_ignore_scripts: 'true'
|
||||||
|
run: pnpm dlx markdownlint-cli@0.45.0 '**/*.md'
|
||||||
@@ -45,7 +45,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -114,6 +114,7 @@ jobs:
|
|||||||
registry-1.docker.io:443
|
registry-1.docker.io:443
|
||||||
auth.docker.io:443
|
auth.docker.io:443
|
||||||
production.cloudflare.docker.com:443
|
production.cloudflare.docker.com:443
|
||||||
|
production.cloudfront.docker.com:443
|
||||||
ghcr.io:443
|
ghcr.io:443
|
||||||
pkg-containers.githubusercontent.com:443
|
pkg-containers.githubusercontent.com:443
|
||||||
files.pythonhosted.org:443
|
files.pythonhosted.org:443
|
||||||
@@ -125,7 +126,7 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
@@ -136,7 +137,7 @@ jobs:
|
|||||||
- name: Build and push MCP container for ${{ matrix.arch }}
|
- name: Build and push MCP container for ${{ matrix.arch }}
|
||||||
id: container-push
|
id: container-push
|
||||||
if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||||
with:
|
with:
|
||||||
context: ${{ env.WORKING_DIRECTORY }}
|
context: ${{ env.WORKING_DIRECTORY }}
|
||||||
push: true
|
push: true
|
||||||
@@ -164,18 +165,19 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
registry-1.docker.io:443
|
registry-1.docker.io:443
|
||||||
auth.docker.io:443
|
auth.docker.io:443
|
||||||
production.cloudflare.docker.com:443
|
production.cloudflare.docker.com:443
|
||||||
|
production.cloudfront.docker.com:443
|
||||||
github.com:443
|
github.com:443
|
||||||
release-assets.githubusercontent.com:443
|
release-assets.githubusercontent.com:443
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
@@ -204,7 +206,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install regctl
|
- name: Install regctl
|
||||||
if: always()
|
if: always()
|
||||||
uses: regclient/actions/regctl-installer@da9319db8e44e8b062b3a147e1dfb2f574d41a03 # main
|
uses: regclient/actions/regctl-installer@9a2d4216180dbb3e2dccfa60d2dd4afd98e42ec5 # main
|
||||||
|
|
||||||
- name: Cleanup intermediate architecture tags
|
- name: Cleanup intermediate architecture tags
|
||||||
if: always()
|
if: always()
|
||||||
@@ -225,7 +227,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -261,27 +263,3 @@ jobs:
|
|||||||
payload-file-path: "./.github/scripts/slack-messages/container-release-completed.json"
|
payload-file-path: "./.github/scripts/slack-messages/container-release-completed.json"
|
||||||
step-outcome: ${{ steps.outcome.outputs.outcome }}
|
step-outcome: ${{ steps.outcome.outputs.outcome }}
|
||||||
update-ts: ${{ needs.notify-release-started.outputs.message-ts }}
|
update-ts: ${{ needs.notify-release-started.outputs.message-ts }}
|
||||||
|
|
||||||
trigger-deployment:
|
|
||||||
needs: [setup, container-build-push]
|
|
||||||
if: always() && github.event_name == 'push' && needs.setup.result == 'success' && needs.container-build-push.result == 'success'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 5
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Harden Runner
|
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
|
||||||
with:
|
|
||||||
egress-policy: block
|
|
||||||
allowed-endpoints: >
|
|
||||||
api.github.com:443
|
|
||||||
|
|
||||||
- name: Trigger MCP deployment
|
|
||||||
uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
|
||||||
repository: ${{ secrets.CLOUD_DISPATCH }}
|
|
||||||
event-type: mcp-prowler-deployment
|
|
||||||
client-payload: '{"sha": "${{ github.sha }}", "short_sha": "${{ needs.setup.outputs.short-sha }}"}'
|
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- 'master'
|
- 'master'
|
||||||
- 'v5.*'
|
- 'v5.*'
|
||||||
paths:
|
|
||||||
- 'mcp_server/**'
|
|
||||||
- '.github/workflows/mcp-container-checks.yml'
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
@@ -36,7 +33,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -50,7 +47,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Check if Dockerfile changed
|
- name: Check if Dockerfile changed
|
||||||
id: dockerfile-changed
|
id: dockerfile-changed
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: mcp_server/Dockerfile
|
files: mcp_server/Dockerfile
|
||||||
|
|
||||||
@@ -71,7 +68,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -99,12 +96,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Check for MCP changes
|
- name: Check for MCP changes
|
||||||
id: check-changes
|
id: check-changes
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: mcp_server/**
|
files: mcp_server/**
|
||||||
files_ignore: |
|
files_ignore: |
|
||||||
mcp_server/README.md
|
mcp_server/README.md
|
||||||
mcp_server/CHANGELOG.md
|
mcp_server/CHANGELOG.md
|
||||||
|
mcp_server/changelog.d/**
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
@@ -112,7 +110,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build MCP container
|
- name: Build MCP container
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||||
with:
|
with:
|
||||||
context: ${{ env.MCP_WORKING_DIR }}
|
context: ${{ env.MCP_WORKING_DIR }}
|
||||||
push: false
|
push: false
|
||||||
@@ -127,5 +125,5 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
image-name: ${{ env.IMAGE_NAME }}
|
image-name: ${{ env.IMAGE_NAME }}
|
||||||
image-tag: ${{ github.sha }}
|
image-tag: ${{ github.sha }}
|
||||||
fail-on-critical: 'false'
|
fail-on-critical: 'true'
|
||||||
severity: 'CRITICAL'
|
severity: 'CRITICAL'
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Publish prowler-mcp package to PyPI
|
- name: Publish prowler-mcp package to PyPI
|
||||||
if: steps.pypi-check.outputs.skip != 'true'
|
if: steps.pypi-check.outputs.skip != 'true'
|
||||||
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
|
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
||||||
with:
|
with:
|
||||||
packages-dir: ${{ env.WORKING_DIRECTORY }}/dist/
|
packages-dir: ${{ env.WORKING_DIRECTORY }}/dist/
|
||||||
print-hash: true
|
print-hash: true
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
name: 'MCP: Security'
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
- 'v5.*'
|
||||||
|
paths:
|
||||||
|
- 'mcp_server/pyproject.toml'
|
||||||
|
- 'mcp_server/uv.lock'
|
||||||
|
- '.github/workflows/mcp-security.yml'
|
||||||
|
- '.github/actions/osv-scanner/**'
|
||||||
|
- '.github/scripts/osv-scan.sh'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
- 'v5.*'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
mcp-security-scans:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write # osv-scanner action posts/updates a PR comment with findings
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
|
with:
|
||||||
|
egress-policy: block
|
||||||
|
allowed-endpoints: >
|
||||||
|
github.com:443
|
||||||
|
api.github.com:443
|
||||||
|
objects.githubusercontent.com:443
|
||||||
|
release-assets.githubusercontent.com:443
|
||||||
|
api.osv.dev:443
|
||||||
|
api.deps.dev:443
|
||||||
|
osv-vulnerabilities.storage.googleapis.com:443
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
# zizmor: ignore[artipacked]
|
||||||
|
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
|
||||||
|
|
||||||
|
- name: Check for MCP dependency changes
|
||||||
|
id: check-changes
|
||||||
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
mcp_server/pyproject.toml
|
||||||
|
mcp_server/uv.lock
|
||||||
|
.github/workflows/mcp-security.yml
|
||||||
|
.github/actions/osv-scanner/**
|
||||||
|
.github/scripts/osv-scan.sh
|
||||||
|
|
||||||
|
- name: Dependency vulnerability scan with osv-scanner
|
||||||
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
|
uses: ./.github/actions/osv-scanner
|
||||||
|
with:
|
||||||
|
lockfile: mcp_server/uv.lock
|
||||||
@@ -48,7 +48,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||||
|
|
||||||
- name: Build ${{ matrix.component }} container (linux/arm64)
|
- name: Build ${{ matrix.component }} container (linux/arm64)
|
||||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||||
with:
|
with:
|
||||||
context: ${{ matrix.context }}
|
context: ${{ matrix.context }}
|
||||||
file: ${{ matrix.dockerfile }}
|
file: ${{ matrix.dockerfile }}
|
||||||
@@ -83,7 +83,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,60 @@ concurrency:
|
|||||||
permissions: {}
|
permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
test-changelog-attribution:
|
||||||
|
if: github.repository == 'prowler-cloud/prowler'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
|
with:
|
||||||
|
egress-policy: block
|
||||||
|
allowed-endpoints: >
|
||||||
|
api.github.com:443
|
||||||
|
github.com:443
|
||||||
|
objects.githubusercontent.com:443
|
||||||
|
pypi.org:443
|
||||||
|
files.pythonhosted.org:443
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Fetch PR base ref for tj-actions/changed-files
|
||||||
|
env:
|
||||||
|
BASE_REF: ${{ github.event.pull_request.base.ref }}
|
||||||
|
run: git fetch --depth=1 origin "${BASE_REF}"
|
||||||
|
|
||||||
|
- name: Get changed files
|
||||||
|
id: changed-files
|
||||||
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
.github/scripts/changelog_attribution.py
|
||||||
|
.github/workflows/pr-check-changelog.yml
|
||||||
|
.github/workflows/compile-changelogs.yml
|
||||||
|
.github/towncrier/template.md.jinja
|
||||||
|
*/towncrier.toml
|
||||||
|
tests/github/**
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
|
with:
|
||||||
|
python-version: '3.12'
|
||||||
|
|
||||||
|
- name: Test changelog attribution
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
|
run: |
|
||||||
|
python3 -m pip install --user --disable-pip-version-check pytest==9.0.3 towncrier==25.8.0
|
||||||
|
python3 -m pytest tests/github
|
||||||
|
|
||||||
check-changelog:
|
check-changelog:
|
||||||
if: contains(github.event.pull_request.labels.*.name, 'no-changelog') == false
|
if: contains(github.event.pull_request.labels.*.name, 'no-changelog') == false
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -31,7 +85,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -52,7 +106,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Get changed files
|
- name: Get changed files
|
||||||
id: changed-files
|
id: changed-files
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
api/**
|
api/**
|
||||||
@@ -62,53 +116,160 @@ jobs:
|
|||||||
uv.lock
|
uv.lock
|
||||||
pyproject.toml
|
pyproject.toml
|
||||||
|
|
||||||
- name: Check for folder changes and changelog presence
|
- name: Check for folder changes and changelog fragment presence
|
||||||
id: check-folders
|
id: check-folders
|
||||||
run: |
|
run: |
|
||||||
missing_changelogs=""
|
fragment_name_re='^[A-Za-z0-9][A-Za-z0-9._-]*\.(added|changed|deprecated|removed|fixed|security)(\.[0-9]+)?\.md$'
|
||||||
|
manual_pr_link_re='(\[\(#[0-9]+\)\]|\[#[0-9]+\]\(|\(#[0-9]+\)|github\.com/[^[:space:]/]+/[^[:space:]/]+/(pull|issues)/[0-9]+)'
|
||||||
|
folder_alt=$(echo "$MONITORED_FOLDERS" | tr ' ' '|')
|
||||||
|
|
||||||
|
missing_fragments=""
|
||||||
|
invalid_fragments=""
|
||||||
|
linked_fragments=""
|
||||||
|
handwritten_changelogs=""
|
||||||
|
|
||||||
|
all_changed=$(echo "${STEPS_CHANGED_FILES_OUTPUTS_ALL_CHANGED_FILES}" | tr ' ' '\n')
|
||||||
|
added=$(echo "${STEPS_CHANGED_FILES_OUTPUTS_ADDED_FILES}" | tr ' ' '\n')
|
||||||
|
added_or_renamed=$(printf '%s\n%s' "${STEPS_CHANGED_FILES_OUTPUTS_ADDED_FILES}" "${STEPS_CHANGED_FILES_OUTPUTS_RENAMED_FILES}" | tr ' ' '\n')
|
||||||
|
added_modified_or_renamed=$(printf '%s\n%s\n%s' "${STEPS_CHANGED_FILES_OUTPUTS_ADDED_FILES}" "${STEPS_CHANGED_FILES_OUTPUTS_MODIFIED_FILES}" "${STEPS_CHANGED_FILES_OUTPUTS_RENAMED_FILES}" | tr ' ' '\n')
|
||||||
|
|
||||||
|
# Returns success if the folder has a valid fragment added, modified, or renamed.
|
||||||
|
has_changelog_update() {
|
||||||
|
local folder="$1"
|
||||||
|
if echo "$added_modified_or_renamed" | grep "^${folder}/changelog.d/" | sed "s|^${folder}/changelog.d/||" | grep -qE "$fragment_name_re"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
if [[ "${STEPS_CHANGED_FILES_OUTPUTS_ANY_CHANGED}" == "true" ]]; then
|
if [[ "${STEPS_CHANGED_FILES_OUTPUTS_ANY_CHANGED}" == "true" ]]; then
|
||||||
# Check monitored folders
|
# Check monitored folders
|
||||||
for folder in $MONITORED_FOLDERS; do
|
for folder in $MONITORED_FOLDERS; do
|
||||||
# Get files changed in this folder
|
if echo "$all_changed" | grep -q "^${folder}/CHANGELOG.md$"; then
|
||||||
changed_in_folder=$(echo "${STEPS_CHANGED_FILES_OUTPUTS_ALL_CHANGED_FILES}" | tr ' ' '\n' | grep "^${folder}/" || true)
|
echo "Direct CHANGELOG.md edits are not allowed for ${folder}/"
|
||||||
|
handwritten_changelogs="${handwritten_changelogs}- \`${folder}/CHANGELOG.md\`"$'\n'
|
||||||
|
fi
|
||||||
|
|
||||||
|
changed_in_folder=$(echo "$all_changed" | grep "^${folder}/" | grep -v "^${folder}/CHANGELOG.md$" || true)
|
||||||
|
|
||||||
if [ -n "$changed_in_folder" ]; then
|
if [ -n "$changed_in_folder" ]; then
|
||||||
echo "Detected changes in ${folder}/"
|
echo "Detected changes in ${folder}/"
|
||||||
|
|
||||||
# Check if CHANGELOG.md was updated
|
if ! has_changelog_update "$folder"; then
|
||||||
if ! echo "$changed_in_folder" | grep -q "^${folder}/CHANGELOG.md$"; then
|
echo "No changelog fragment found for ${folder}/"
|
||||||
echo "No changelog update found for ${folder}/"
|
missing_fragments="${missing_fragments}- \`${folder}\`"$'\n'
|
||||||
missing_changelogs="${missing_changelogs}- \`${folder}\`"$'\n'
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Check root-level dependency files (uv.lock, pyproject.toml)
|
# Check root-level dependency files (uv.lock, pyproject.toml)
|
||||||
# These are associated with the prowler folder changelog
|
# These are associated with the prowler folder changelog
|
||||||
root_deps_changed=$(echo "${STEPS_CHANGED_FILES_OUTPUTS_ALL_CHANGED_FILES}" | tr ' ' '\n' | grep -E "^(uv\.lock|pyproject\.toml)$" || true)
|
root_deps_changed=$(echo "$all_changed" | grep -E "^(uv\.lock|pyproject\.toml)$" || true)
|
||||||
if [ -n "$root_deps_changed" ]; then
|
if [ -n "$root_deps_changed" ]; then
|
||||||
echo "Detected changes in root dependency files: $root_deps_changed"
|
echo "Detected changes in root dependency files: $root_deps_changed"
|
||||||
# Check if prowler/CHANGELOG.md was already updated (might have been caught above)
|
if ! has_changelog_update "prowler"; then
|
||||||
prowler_changelog_updated=$(echo "${STEPS_CHANGED_FILES_OUTPUTS_ALL_CHANGED_FILES}" | tr ' ' '\n' | grep "^prowler/CHANGELOG.md$" || true)
|
|
||||||
if [ -z "$prowler_changelog_updated" ]; then
|
|
||||||
# Only add if prowler wasn't already flagged
|
# Only add if prowler wasn't already flagged
|
||||||
if ! echo "$missing_changelogs" | grep -q "prowler"; then
|
if ! echo "$missing_fragments" | grep -q "prowler"; then
|
||||||
echo "No changelog update found for root dependency changes"
|
echo "No changelog fragment found for root dependency changes"
|
||||||
missing_changelogs="${missing_changelogs}- \`prowler\` (root dependency files changed)"$'\n'
|
missing_fragments="${missing_fragments}- \`prowler\` (root dependency files changed)"$'\n'
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Validate the filename of every fragment added by this PR
|
||||||
|
added_fragments=$(echo "$added_or_renamed" | grep -E "^(${folder_alt})/changelog\.d/" || true)
|
||||||
|
for fragment in $added_fragments; do
|
||||||
|
name=$(basename "$fragment")
|
||||||
|
if [ "$name" = ".gitkeep" ] || [ "$name" = "README.md" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if ! echo "$name" | grep -qE "$fragment_name_re"; then
|
||||||
|
echo "Invalid fragment filename: $fragment"
|
||||||
|
invalid_fragments="${invalid_fragments}- \`${fragment}\`"$'\n'
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Lint fragment content: the PR link is attached automatically at
|
||||||
|
# compile time, so a hand-written PR or issue link would be wrong
|
||||||
|
touched_fragments=$(echo "$added_modified_or_renamed" | grep -E "^(${folder_alt})/changelog\.d/" || true)
|
||||||
|
for fragment in $touched_fragments; do
|
||||||
|
name=$(basename "$fragment")
|
||||||
|
if [ "$name" = ".gitkeep" ] || [ "$name" = "README.md" ] || [ ! -f "$fragment" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if grep -qE "$manual_pr_link_re" "$fragment"; then
|
||||||
|
echo "Fragment contains a hand-written PR or issue link: $fragment"
|
||||||
|
linked_fragments="${linked_fragments}- \`${fragment}\`"$'\n'
|
||||||
|
fi
|
||||||
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
{
|
# Suggest a slug derived from the branch name for the bot comment
|
||||||
echo "missing_changelogs<<EOF"
|
suggested_slug=$(echo "$HEAD_REF" | tr '[:upper:]' '[:lower:]' | sed 's|.*/||; s/[^a-z0-9._-]/-/g; s/^[^a-z0-9]*//')
|
||||||
echo -e "${missing_changelogs}"
|
if [ -z "$suggested_slug" ]; then
|
||||||
echo "EOF"
|
suggested_slug="my-change"
|
||||||
} >> $GITHUB_OUTPUT
|
fi
|
||||||
|
|
||||||
|
fragment_help="A changelog fragment is a small Markdown file named \`<slug>.<type>.md\` under \`<component>/changelog.d/\`, where \`<type>\` is one of \`added\`, \`changed\`, \`deprecated\`, \`removed\`, \`fixed\` or \`security\`. Its content is the changelog entry text, without the PR link (added automatically at release time) and without a trailing period. For example:
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
echo 'Entry text describing the change' > <component>/changelog.d/${suggested_slug}.fixed.md
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
If this PR does not need a changelog entry, add the \`no-changelog\` label instead."
|
||||||
|
|
||||||
|
if [ -n "$missing_fragments" ] || [ -n "$invalid_fragments" ] || [ -n "$linked_fragments" ] || [ -n "$handwritten_changelogs" ]; then
|
||||||
|
comment_body=""
|
||||||
|
if [ -n "$missing_fragments" ]; then
|
||||||
|
comment_body="⚠️ **Changes detected in the following folders without a changelog fragment:**"$'\n\n'"${missing_fragments}"$'\n'
|
||||||
|
fi
|
||||||
|
if [ -n "$invalid_fragments" ]; then
|
||||||
|
comment_body="${comment_body}⚠️ **Changelog fragment filenames that do not follow the naming convention:**"$'\n\n'"${invalid_fragments}"$'\n'
|
||||||
|
fi
|
||||||
|
if [ -n "$linked_fragments" ]; then
|
||||||
|
comment_body="${comment_body}⚠️ **Changelog fragments containing a hand-written PR or issue link (remove it; the PR link is attached automatically at release time):**"$'\n\n'"${linked_fragments}"$'\n'
|
||||||
|
fi
|
||||||
|
if [ -n "$handwritten_changelogs" ]; then
|
||||||
|
comment_body="${comment_body}⚠️ **Direct \`CHANGELOG.md\` edits are not allowed in regular PRs:**"$'\n\n'"${handwritten_changelogs}"$'\n'
|
||||||
|
fi
|
||||||
|
comment_body="${comment_body}${fragment_help}"
|
||||||
|
else
|
||||||
|
comment_body="✅ All required changelog fragments are present."
|
||||||
|
fi
|
||||||
|
|
||||||
|
write_multiline_output() {
|
||||||
|
local name="$1"
|
||||||
|
local value="$2"
|
||||||
|
local delimiter
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
delimiter="EOF_$(openssl rand -hex 16)"
|
||||||
|
if ! grep -qxF "$delimiter" <<< "$value"; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "${name}<<${delimiter}"
|
||||||
|
if [ -n "$value" ]; then
|
||||||
|
printf '%s\n' "$value"
|
||||||
|
fi
|
||||||
|
echo "${delimiter}"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_multiline_output "missing_fragments" "$missing_fragments"
|
||||||
|
write_multiline_output "invalid_fragments" "$invalid_fragments"
|
||||||
|
write_multiline_output "linked_fragments" "$linked_fragments"
|
||||||
|
write_multiline_output "handwritten_changelogs" "$handwritten_changelogs"
|
||||||
|
write_multiline_output "comment_body" "$comment_body"
|
||||||
env:
|
env:
|
||||||
STEPS_CHANGED_FILES_OUTPUTS_ANY_CHANGED: ${{ steps.changed-files.outputs.any_changed }}
|
STEPS_CHANGED_FILES_OUTPUTS_ANY_CHANGED: ${{ steps.changed-files.outputs.any_changed }}
|
||||||
STEPS_CHANGED_FILES_OUTPUTS_ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
STEPS_CHANGED_FILES_OUTPUTS_ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
||||||
|
STEPS_CHANGED_FILES_OUTPUTS_ADDED_FILES: ${{ steps.changed-files.outputs.added_files }}
|
||||||
|
STEPS_CHANGED_FILES_OUTPUTS_MODIFIED_FILES: ${{ steps.changed-files.outputs.modified_files }}
|
||||||
|
STEPS_CHANGED_FILES_OUTPUTS_RENAMED_FILES: ${{ steps.changed-files.outputs.renamed_files }}
|
||||||
|
HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
||||||
|
|
||||||
- name: Find existing changelog comment
|
- name: Find existing changelog comment
|
||||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||||
@@ -128,14 +289,10 @@ jobs:
|
|||||||
edit-mode: replace
|
edit-mode: replace
|
||||||
body: |
|
body: |
|
||||||
<!-- changelog-check -->
|
<!-- changelog-check -->
|
||||||
${{ steps.check-folders.outputs.missing_changelogs != '' && format('⚠️ **Changes detected in the following folders without a corresponding update to the `CHANGELOG.md`:**
|
${{ steps.check-folders.outputs.comment_body }}
|
||||||
|
|
||||||
{0}
|
- name: Fail if changelog fragment is missing or invalid
|
||||||
|
if: steps.check-folders.outputs.missing_fragments != '' || steps.check-folders.outputs.invalid_fragments != '' || steps.check-folders.outputs.linked_fragments != '' || steps.check-folders.outputs.handwritten_changelogs != ''
|
||||||
Please add an entry to the corresponding `CHANGELOG.md` file to maintain a clear history of changes.', steps.check-folders.outputs.missing_changelogs) || '✅ All necessary `CHANGELOG.md` files have been updated.' }}
|
|
||||||
|
|
||||||
- name: Fail if changelog is missing
|
|
||||||
if: steps.check-folders.outputs.missing_changelogs != ''
|
|
||||||
run: |
|
run: |
|
||||||
echo "::error::Missing changelog updates in some folders"
|
echo "::error::Missing, invalid, or disallowed changelog updates"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -56,7 +56,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Get changed files
|
- name: Get changed files
|
||||||
id: changed-files
|
id: changed-files
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
prowler/providers/**/services/**/*.metadata.json
|
prowler/providers/**/services/**/*.metadata.json
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -37,8 +37,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
# zizmor: ignore[artipacked]
|
persist-credentials: false # No write token in the untrusted PR-head tree; public repo so base fetch/changed-files work unauthenticated
|
||||||
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
|
|
||||||
|
|
||||||
- name: Fetch PR base ref for tj-actions/changed-files
|
- name: Fetch PR base ref for tj-actions/changed-files
|
||||||
env:
|
env:
|
||||||
@@ -47,9 +46,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Get changed files
|
- name: Get changed files
|
||||||
id: changed-files
|
id: changed-files
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: '**'
|
files: '**'
|
||||||
|
safe_output: false # Raw paths (list read via env var, injection-safe); default escaping backslash-quotes chars like () and breaks the -f test
|
||||||
|
separator: "\n" # Newline-delimited so the reader tolerates spaces and glob chars in paths
|
||||||
|
|
||||||
- name: Check for conflict markers
|
- name: Check for conflict markers
|
||||||
id: conflict-check
|
id: conflict-check
|
||||||
@@ -59,19 +60,18 @@ jobs:
|
|||||||
CONFLICT_FILES=""
|
CONFLICT_FILES=""
|
||||||
HAS_CONFLICTS=false
|
HAS_CONFLICTS=false
|
||||||
|
|
||||||
# Check each changed file for conflict markers
|
# Read newline-delimited paths so spaces/globs neither word-split nor glob-expand
|
||||||
for file in ${STEPS_CHANGED_FILES_OUTPUTS_ALL_CHANGED_FILES}; do
|
while IFS= read -r file; do
|
||||||
if [ -f "$file" ]; then
|
[ -n "$file" ] || continue
|
||||||
echo "Checking file: $file"
|
[ -f "$file" ] || continue
|
||||||
|
echo "Checking file: $file"
|
||||||
|
|
||||||
# Look for conflict markers (more precise regex)
|
if grep -qE '^(<<<<<<<|=======|>>>>>>>)' "$file" 2>/dev/null; then
|
||||||
if grep -qE '^(<<<<<<<|=======|>>>>>>>)' "$file" 2>/dev/null; then
|
echo "Conflict markers found in: $file"
|
||||||
echo "Conflict markers found in: $file"
|
CONFLICT_FILES="${CONFLICT_FILES}- \`${file}\`"$'\n'
|
||||||
CONFLICT_FILES="${CONFLICT_FILES}- \`${file}\`"$'\n'
|
HAS_CONFLICTS=true
|
||||||
HAS_CONFLICTS=true
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
done
|
done <<< "$STEPS_CHANGED_FILES_OUTPUTS_ALL_CHANGED_FILES"
|
||||||
|
|
||||||
if [ "$HAS_CONFLICTS" = true ]; then
|
if [ "$HAS_CONFLICTS" = true ]; then
|
||||||
echo "has_conflicts=true" >> $GITHUB_OUTPUT
|
echo "has_conflicts=true" >> $GITHUB_OUTPUT
|
||||||
@@ -88,18 +88,49 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
STEPS_CHANGED_FILES_OUTPUTS_ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
STEPS_CHANGED_FILES_OUTPUTS_ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
||||||
|
|
||||||
|
- name: Check base-branch mergeability
|
||||||
|
id: merge-check
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
run: |
|
||||||
|
MERGEABLE=null
|
||||||
|
|
||||||
|
# GitHub computes mergeability async, so .mergeable is null until ready; poll until resolved
|
||||||
|
for attempt in 1 2 3 4 5; do
|
||||||
|
MERGEABLE=$(gh api "repos/${REPO}/pulls/${PR_NUMBER}" --jq '.mergeable')
|
||||||
|
if [ "$MERGEABLE" != "null" ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "Attempt ${attempt}: mergeability not computed yet, retrying..."
|
||||||
|
sleep 3
|
||||||
|
done
|
||||||
|
|
||||||
|
# Keep 'unknown' distinct from 'clean' so we never assert a clean merge we could not confirm
|
||||||
|
case "$MERGEABLE" in
|
||||||
|
false) STATUS=conflict; echo "PR branch cannot be merged cleanly into its base branch" ;;
|
||||||
|
true) STATUS=clean; echo "PR branch merges cleanly into its base branch" ;;
|
||||||
|
*) STATUS=unknown; echo "::warning::Mergeability did not resolve after retries; leaving it undetermined" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "merge_status=${STATUS}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Manage conflict label
|
- name: Manage conflict label
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
HAS_CONFLICTS: ${{ steps.conflict-check.outputs.has_conflicts }}
|
HAS_CONFLICTS: ${{ steps.conflict-check.outputs.has_conflicts }}
|
||||||
|
MERGE_STATUS: ${{ steps.merge-check.outputs.merge_status }}
|
||||||
run: |
|
run: |
|
||||||
LABEL_NAME="has-conflicts"
|
LABEL_NAME="has-conflicts"
|
||||||
|
|
||||||
# Add or remove label based on conflict status
|
if [ "$HAS_CONFLICTS" = "true" ] || [ "$MERGE_STATUS" = "conflict" ]; then
|
||||||
if [ "$HAS_CONFLICTS" = "true" ]; then
|
|
||||||
echo "Adding conflict label to PR #${PR_NUMBER}..."
|
echo "Adding conflict label to PR #${PR_NUMBER}..."
|
||||||
gh pr edit "$PR_NUMBER" --add-label "$LABEL_NAME" --repo ${{ github.repository }} || true
|
gh pr edit "$PR_NUMBER" --add-label "$LABEL_NAME" --repo ${{ github.repository }} || true
|
||||||
|
elif [ "$MERGE_STATUS" = "unknown" ]; then
|
||||||
|
# Don't drop the label on an undetermined merge state; a later run will settle it
|
||||||
|
echo "Mergeability undetermined; leaving label unchanged"
|
||||||
else
|
else
|
||||||
echo "Removing conflict label from PR #${PR_NUMBER}..."
|
echo "Removing conflict label from PR #${PR_NUMBER}..."
|
||||||
gh pr edit "$PR_NUMBER" --remove-label "$LABEL_NAME" --repo ${{ github.repository }} || true
|
gh pr edit "$PR_NUMBER" --remove-label "$LABEL_NAME" --repo ${{ github.repository }} || true
|
||||||
@@ -121,20 +152,25 @@ jobs:
|
|||||||
edit-mode: replace
|
edit-mode: replace
|
||||||
body: |
|
body: |
|
||||||
<!-- conflict-checker-comment -->
|
<!-- conflict-checker-comment -->
|
||||||
${{ steps.conflict-check.outputs.has_conflicts == 'true' && '⚠️ **Conflict Markers Detected**' || '✅ **Conflict Markers Resolved**' }}
|
${{ (steps.conflict-check.outputs.has_conflicts == 'true' || steps.merge-check.outputs.merge_status == 'conflict') && '⚠️ **Conflicts Detected**' || (steps.merge-check.outputs.merge_status == 'unknown' && 'ℹ️ **Conflict Check Incomplete**' || '✅ **No Conflicts**') }}
|
||||||
|
${{ steps.conflict-check.outputs.has_conflicts == 'true' && format('
|
||||||
${{ steps.conflict-check.outputs.has_conflicts == 'true' && format('This pull request contains unresolved conflict markers in the following files:
|
**Conflict markers** are present in the following files:
|
||||||
|
|
||||||
{0}
|
{0}
|
||||||
|
Resolve them by removing every `<<<<<<<`, `=======`, and `>>>>>>>` marker, then commit and push.', steps.conflict-check.outputs.conflict_files) || '' }}
|
||||||
Please resolve these conflicts by:
|
${{ steps.merge-check.outputs.merge_status == 'conflict' && '
|
||||||
1. Locating the conflict markers: `<<<<<<<`, `=======`, and `>>>>>>>`
|
**Merge conflict with the base branch.** This PR cannot be merged cleanly. Update your branch with the latest base (rebase or merge) and resolve the conflicts.' || '' }}
|
||||||
2. Manually editing the files to resolve the conflicts
|
${{ steps.merge-check.outputs.merge_status == 'unknown' && '
|
||||||
3. Removing all conflict markers
|
GitHub had not finished computing mergeability, so base-branch conflict status could not be verified on this run.' || '' }}
|
||||||
4. Committing and pushing the changes', steps.conflict-check.outputs.conflict_files) || 'All conflict markers have been successfully resolved in this pull request.' }}
|
${{ (steps.conflict-check.outputs.has_conflicts != 'true' && steps.merge-check.outputs.merge_status == 'clean') && '
|
||||||
|
No conflict markers, and the branch merges cleanly into its base.' || '' }}
|
||||||
|
|
||||||
- name: Fail workflow if conflicts detected
|
- name: Fail workflow if conflicts detected
|
||||||
if: steps.conflict-check.outputs.has_conflicts == 'true'
|
if: steps.conflict-check.outputs.has_conflicts == 'true' || steps.merge-check.outputs.merge_status == 'conflict'
|
||||||
|
env:
|
||||||
|
HAS_CONFLICTS: ${{ steps.conflict-check.outputs.has_conflicts }}
|
||||||
|
MERGE_STATUS: ${{ steps.merge-check.outputs.merge_status }}
|
||||||
run: |
|
run: |
|
||||||
echo "::error::Workflow failed due to conflict markers detected in the PR"
|
[ "$HAS_CONFLICTS" = "true" ] && echo "::error::Conflict markers detected in changed files"
|
||||||
|
[ "$MERGE_STATUS" = "conflict" ] && echo "::error::PR branch has merge conflicts with the base branch"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -56,6 +56,6 @@ jobs:
|
|||||||
"PROWLER_PR_BODY": ${{ toJson(github.event.pull_request.body) }},
|
"PROWLER_PR_BODY": ${{ toJson(github.event.pull_request.body) }},
|
||||||
"PROWLER_PR_URL": ${{ toJson(github.event.pull_request.html_url) }},
|
"PROWLER_PR_URL": ${{ toJson(github.event.pull_request.html_url) }},
|
||||||
"PROWLER_PR_MERGED_BY": "${{ github.event.pull_request.merged_by.login }}",
|
"PROWLER_PR_MERGED_BY": "${{ github.event.pull_request.merged_by.login }}",
|
||||||
"PROWLER_PR_BASE_BRANCH": "${{ github.event.pull_request.base.ref }}",
|
"PROWLER_PR_BASE_BRANCH": ${{ toJson(github.event.pull_request.base.ref) }},
|
||||||
"PROWLER_PR_HEAD_BRANCH": "${{ github.event.pull_request.head.ref }}"
|
"PROWLER_PR_HEAD_BRANCH": ${{ toJson(github.event.pull_request.head.ref) }}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -338,7 +338,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create PR for API dependency update
|
- name: Create PR for API dependency update
|
||||||
if: ${{ env.PATCH_VERSION == '0' }}
|
if: ${{ env.PATCH_VERSION == '0' }}
|
||||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||||
commit-message: 'chore(api): update prowler dependency to ${{ env.BRANCH_NAME }} for release ${{ env.PROWLER_VERSION }}'
|
commit-message: 'chore(api): update prowler dependency to ${{ env.BRANCH_NAME }} for release ${{ env.PROWLER_VERSION }}'
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
name: 'CI: Renovate Config Validate'
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
paths:
|
||||||
|
- '.github/renovate.json'
|
||||||
|
- '.pre-commit-config.yaml'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
env:
|
||||||
|
# renovate: datasource=pypi depName=prek
|
||||||
|
PREK_VERSION: '0.4.0'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate:
|
||||||
|
name: Validate Renovate config
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
|
with:
|
||||||
|
egress-policy: block
|
||||||
|
allowed-endpoints: >
|
||||||
|
api.github.com:443
|
||||||
|
github.com:443
|
||||||
|
raw.githubusercontent.com:443
|
||||||
|
objects.githubusercontent.com:443
|
||||||
|
codeload.github.com:443
|
||||||
|
release-assets.githubusercontent.com:443
|
||||||
|
pypi.org:443
|
||||||
|
files.pythonhosted.org:443
|
||||||
|
registry.npmjs.org:443
|
||||||
|
nodejs.org:443
|
||||||
|
releases.astral.sh:443
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Set up uv
|
||||||
|
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1
|
||||||
|
|
||||||
|
- name: Install prek
|
||||||
|
run: uv tool install "prek==${PREK_VERSION}"
|
||||||
|
|
||||||
|
- name: Validate Renovate config
|
||||||
|
run: prek run renovate-config-validator --files .github/renovate.json
|
||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
|
|||||||
@@ -29,10 +29,11 @@ jobs:
|
|||||||
- '3.10'
|
- '3.10'
|
||||||
- '3.11'
|
- '3.11'
|
||||||
- '3.12'
|
- '3.12'
|
||||||
|
- '3.13'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -48,12 +49,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Check for SDK changes
|
- name: Check for SDK changes
|
||||||
id: check-changes
|
id: check-changes
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: ./**
|
files: ./**
|
||||||
files_ignore: |
|
files_ignore: |
|
||||||
.github/**
|
.github/**
|
||||||
prowler/CHANGELOG.md
|
prowler/CHANGELOG.md
|
||||||
|
prowler/changelog.d/**
|
||||||
docs/**
|
docs/**
|
||||||
permissions/**
|
permissions/**
|
||||||
api/**
|
api/**
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ on:
|
|||||||
- '.github/workflows/sdk-codeql.yml'
|
- '.github/workflows/sdk-codeql.yml'
|
||||||
- '.github/codeql/sdk-codeql-config.yml'
|
- '.github/codeql/sdk-codeql-config.yml'
|
||||||
- '!prowler/CHANGELOG.md'
|
- '!prowler/CHANGELOG.md'
|
||||||
|
- '!prowler/changelog.d/**'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- 'master'
|
- 'master'
|
||||||
@@ -23,6 +24,7 @@ on:
|
|||||||
- '.github/workflows/sdk-codeql.yml'
|
- '.github/workflows/sdk-codeql.yml'
|
||||||
- '.github/codeql/sdk-codeql-config.yml'
|
- '.github/codeql/sdk-codeql-config.yml'
|
||||||
- '!prowler/CHANGELOG.md'
|
- '!prowler/CHANGELOG.md'
|
||||||
|
- '!prowler/changelog.d/**'
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '00 12 * * *'
|
- cron: '00 12 * * *'
|
||||||
|
|
||||||
@@ -51,7 +53,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -66,12 +68,12 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
|
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
config-file: ./.github/codeql/sdk-codeql-config.yml
|
config-file: ./.github/codeql/sdk-codeql-config.yml
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
|
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||||
with:
|
with:
|
||||||
category: '/language:${{ matrix.language }}'
|
category: '/language:${{ matrix.language }}'
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -98,7 +98,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -138,17 +138,21 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
api.ecr-public.us-east-1.amazonaws.com:443
|
api.ecr-public.us-east-1.amazonaws.com:443
|
||||||
public.ecr.aws:443
|
public.ecr.aws:443
|
||||||
|
sts.amazonaws.com:443
|
||||||
|
sts.us-east-1.amazonaws.com:443
|
||||||
registry-1.docker.io:443
|
registry-1.docker.io:443
|
||||||
production.cloudflare.docker.com:443
|
production.cloudflare.docker.com:443
|
||||||
|
production.cloudfront.docker.com:443
|
||||||
auth.docker.io:443
|
auth.docker.io:443
|
||||||
debian.map.fastlydns.net:80
|
debian.map.fastlydns.net:80
|
||||||
github.com:443
|
github.com:443
|
||||||
@@ -167,19 +171,21 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Login to Public ECR
|
- name: Configure AWS credentials (OIDC)
|
||||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
uses: aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885 # v6.1.1
|
||||||
with:
|
with:
|
||||||
registry: public.ecr.aws
|
aws-region: us-east-1
|
||||||
username: ${{ secrets.PUBLIC_ECR_AWS_ACCESS_KEY_ID }}
|
role-to-assume: ${{ secrets.PUBLIC_ECR_PUSH_ROLE_ARN }}
|
||||||
password: ${{ secrets.PUBLIC_ECR_AWS_SECRET_ACCESS_KEY }}
|
|
||||||
env:
|
- name: Login to Public ECR
|
||||||
AWS_REGION: ${{ env.AWS_REGION }}
|
uses: aws-actions/amazon-ecr-login@d539f0932e70871a027e9d5a9d8fc38589180a64 # v2.1.6
|
||||||
|
with:
|
||||||
|
registry-type: public
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||||
@@ -187,7 +193,7 @@ jobs:
|
|||||||
- name: Build and push SDK container for ${{ matrix.arch }}
|
- name: Build and push SDK container for ${{ matrix.arch }}
|
||||||
id: container-push
|
id: container-push
|
||||||
if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ${{ env.DOCKERFILE_PATH }}
|
file: ${{ env.DOCKERFILE_PATH }}
|
||||||
@@ -205,10 +211,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -216,25 +223,30 @@ jobs:
|
|||||||
auth.docker.io:443
|
auth.docker.io:443
|
||||||
public.ecr.aws:443
|
public.ecr.aws:443
|
||||||
production.cloudflare.docker.com:443
|
production.cloudflare.docker.com:443
|
||||||
|
production.cloudfront.docker.com:443
|
||||||
github.com:443
|
github.com:443
|
||||||
release-assets.githubusercontent.com:443
|
release-assets.githubusercontent.com:443
|
||||||
api.ecr-public.us-east-1.amazonaws.com:443
|
api.ecr-public.us-east-1.amazonaws.com:443
|
||||||
|
sts.amazonaws.com:443
|
||||||
|
sts.us-east-1.amazonaws.com:443
|
||||||
|
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Login to Public ECR
|
- name: Configure AWS credentials (OIDC)
|
||||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
uses: aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885 # v6.1.1
|
||||||
with:
|
with:
|
||||||
registry: public.ecr.aws
|
aws-region: us-east-1
|
||||||
username: ${{ secrets.PUBLIC_ECR_AWS_ACCESS_KEY_ID }}
|
role-to-assume: ${{ secrets.PUBLIC_ECR_PUSH_ROLE_ARN }}
|
||||||
password: ${{ secrets.PUBLIC_ECR_AWS_SECRET_ACCESS_KEY }}
|
|
||||||
env:
|
- name: Login to Public ECR
|
||||||
AWS_REGION: ${{ env.AWS_REGION }}
|
uses: aws-actions/amazon-ecr-login@d539f0932e70871a027e9d5a9d8fc38589180a64 # v2.1.6
|
||||||
|
with:
|
||||||
|
registry-type: public
|
||||||
|
|
||||||
- name: Create and push manifests for push event
|
- name: Create and push manifests for push event
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
@@ -265,7 +277,7 @@ jobs:
|
|||||||
# Push to toniblyx/prowler only for current version (latest/stable/release tags)
|
# Push to toniblyx/prowler only for current version (latest/stable/release tags)
|
||||||
- name: Login to DockerHub (toniblyx)
|
- name: Login to DockerHub (toniblyx)
|
||||||
if: needs.setup.outputs.latest_tag == 'latest'
|
if: needs.setup.outputs.latest_tag == 'latest'
|
||||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.TONIBLYX_DOCKERHUB_USERNAME }}
|
username: ${{ secrets.TONIBLYX_DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.TONIBLYX_DOCKERHUB_PASSWORD }}
|
password: ${{ secrets.TONIBLYX_DOCKERHUB_PASSWORD }}
|
||||||
@@ -290,14 +302,14 @@ jobs:
|
|||||||
# Re-login as prowlercloud for cleanup of intermediate tags
|
# Re-login as prowlercloud for cleanup of intermediate tags
|
||||||
- name: Login to DockerHub (prowlercloud)
|
- name: Login to DockerHub (prowlercloud)
|
||||||
if: always()
|
if: always()
|
||||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Install regctl
|
- name: Install regctl
|
||||||
if: always()
|
if: always()
|
||||||
uses: regclient/actions/regctl-installer@da9319db8e44e8b062b3a147e1dfb2f574d41a03 # main
|
uses: regclient/actions/regctl-installer@9a2d4216180dbb3e2dccfa60d2dd4afd98e42ec5 # main
|
||||||
|
|
||||||
- name: Cleanup intermediate architecture tags
|
- name: Cleanup intermediate architecture tags
|
||||||
if: always()
|
if: always()
|
||||||
@@ -318,7 +330,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
@@ -15,12 +15,6 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- 'master'
|
- 'master'
|
||||||
- 'v5.*'
|
- 'v5.*'
|
||||||
paths:
|
|
||||||
- 'prowler/**'
|
|
||||||
- 'Dockerfile*'
|
|
||||||
- 'pyproject.toml'
|
|
||||||
- 'uv.lock'
|
|
||||||
- '.github/workflows/sdk-container-checks.yml'
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
@@ -41,7 +35,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -55,7 +49,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Check if Dockerfile changed
|
- name: Check if Dockerfile changed
|
||||||
id: dockerfile-changed
|
id: dockerfile-changed
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: Dockerfile
|
files: Dockerfile
|
||||||
|
|
||||||
@@ -77,7 +71,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -85,6 +79,7 @@ jobs:
|
|||||||
registry-1.docker.io:443
|
registry-1.docker.io:443
|
||||||
auth.docker.io:443
|
auth.docker.io:443
|
||||||
production.cloudflare.docker.com:443
|
production.cloudflare.docker.com:443
|
||||||
|
production.cloudfront.docker.com:443
|
||||||
api.github.com:443
|
api.github.com:443
|
||||||
mirror.gcr.io:443
|
mirror.gcr.io:443
|
||||||
check.trivy.dev:443
|
check.trivy.dev:443
|
||||||
@@ -99,6 +94,8 @@ jobs:
|
|||||||
_http._tcp.deb.debian.org:443
|
_http._tcp.deb.debian.org:443
|
||||||
powershellinfraartifacts-gkhedzdeaghdezhr.z01.azurefd.net:443
|
powershellinfraartifacts-gkhedzdeaghdezhr.z01.azurefd.net:443
|
||||||
get.trivy.dev:443
|
get.trivy.dev:443
|
||||||
|
raw.githubusercontent.com:443
|
||||||
|
releases.astral.sh:443
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
@@ -108,27 +105,17 @@ jobs:
|
|||||||
|
|
||||||
- name: Check for SDK changes
|
- name: Check for SDK changes
|
||||||
id: check-changes
|
id: check-changes
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: ./**
|
files: |
|
||||||
|
prowler/**
|
||||||
|
Dockerfile*
|
||||||
|
pyproject.toml
|
||||||
|
uv.lock
|
||||||
|
.github/workflows/sdk-container-checks.yml
|
||||||
files_ignore: |
|
files_ignore: |
|
||||||
.github/**
|
|
||||||
prowler/CHANGELOG.md
|
prowler/CHANGELOG.md
|
||||||
docs/**
|
prowler/changelog.d/**
|
||||||
permissions/**
|
|
||||||
api/**
|
|
||||||
ui/**
|
|
||||||
dashboard/**
|
|
||||||
mcp_server/**
|
|
||||||
skills/**
|
|
||||||
README.md
|
|
||||||
mkdocs.yml
|
|
||||||
.backportrc.json
|
|
||||||
.env
|
|
||||||
docker-compose*
|
|
||||||
examples/**
|
|
||||||
.gitignore
|
|
||||||
contrib/**
|
|
||||||
**/AGENTS.md
|
**/AGENTS.md
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
@@ -137,7 +124,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build SDK container
|
- name: Build SDK container
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: false
|
push: false
|
||||||
@@ -152,5 +139,5 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
image-name: ${{ env.IMAGE_NAME }}
|
image-name: ${{ env.IMAGE_NAME }}
|
||||||
image-tag: ${{ github.sha }}
|
image-tag: ${{ github.sha }}
|
||||||
fail-on-critical: 'false'
|
fail-on-critical: 'true'
|
||||||
severity: 'CRITICAL'
|
severity: 'CRITICAL'
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ jobs:
|
|||||||
run: uv build
|
run: uv build
|
||||||
|
|
||||||
- name: Publish Prowler package to PyPI
|
- name: Publish Prowler package to PyPI
|
||||||
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
|
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
||||||
with:
|
with:
|
||||||
print-hash: true
|
print-hash: true
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -129,6 +129,6 @@ jobs:
|
|||||||
run: uv build
|
run: uv build
|
||||||
|
|
||||||
- name: Publish prowler-cloud package to PyPI
|
- name: Publish prowler-cloud package to PyPI
|
||||||
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
|
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
||||||
with:
|
with:
|
||||||
print-hash: true
|
print-hash: true
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ jobs:
|
|||||||
run: pip install boto3
|
run: pip install boto3
|
||||||
|
|
||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6.0.0
|
uses: aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885 # v6.1.1
|
||||||
with:
|
with:
|
||||||
aws-region: ${{ env.AWS_REGION }}
|
aws-region: ${{ env.AWS_REGION }}
|
||||||
role-to-assume: ${{ secrets.DEV_IAM_ROLE_ARN }}
|
role-to-assume: ${{ secrets.DEV_IAM_ROLE_ARN }}
|
||||||
@@ -58,7 +58,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create pull request
|
- name: Create pull request
|
||||||
id: create-pr
|
id: create-pr
|
||||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||||
author: 'prowler-bot <179230569+prowler-bot@users.noreply.github.com>'
|
author: 'prowler-bot <179230569+prowler-bot@users.noreply.github.com>'
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create pull request
|
- name: Create pull request
|
||||||
id: create-pr
|
id: create-pr
|
||||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||||
author: 'prowler-bot <179230569+prowler-bot@users.noreply.github.com>'
|
author: 'prowler-bot <179230569+prowler-bot@users.noreply.github.com>'
|
||||||
|
|||||||
@@ -19,16 +19,6 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- 'master'
|
- 'master'
|
||||||
- 'v5.*'
|
- 'v5.*'
|
||||||
paths:
|
|
||||||
- 'prowler/**'
|
|
||||||
- 'tests/**'
|
|
||||||
- 'pyproject.toml'
|
|
||||||
- 'uv.lock'
|
|
||||||
- '.github/workflows/sdk-tests.yml'
|
|
||||||
- '.github/workflows/sdk-security.yml'
|
|
||||||
- '.github/actions/setup-python-uv/**'
|
|
||||||
- '.github/actions/osv-scanner/**'
|
|
||||||
- '.github/scripts/osv-scan.sh'
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
@@ -47,7 +37,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -69,29 +59,21 @@ jobs:
|
|||||||
|
|
||||||
- name: Check for SDK changes
|
- name: Check for SDK changes
|
||||||
id: check-changes
|
id: check-changes
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files:
|
files: |
|
||||||
./**
|
prowler/**
|
||||||
|
tests/**
|
||||||
|
pyproject.toml
|
||||||
|
uv.lock
|
||||||
|
.github/workflows/sdk-tests.yml
|
||||||
.github/workflows/sdk-security.yml
|
.github/workflows/sdk-security.yml
|
||||||
|
.github/actions/setup-python-uv/**
|
||||||
|
.github/actions/osv-scanner/**
|
||||||
|
.github/scripts/osv-scan.sh
|
||||||
files_ignore: |
|
files_ignore: |
|
||||||
.github/**
|
|
||||||
prowler/CHANGELOG.md
|
prowler/CHANGELOG.md
|
||||||
docs/**
|
prowler/changelog.d/**
|
||||||
permissions/**
|
|
||||||
api/**
|
|
||||||
ui/**
|
|
||||||
dashboard/**
|
|
||||||
mcp_server/**
|
|
||||||
skills/**
|
|
||||||
README.md
|
|
||||||
mkdocs.yml
|
|
||||||
.backportrc.json
|
|
||||||
.env
|
|
||||||
docker-compose*
|
|
||||||
examples/**
|
|
||||||
.gitignore
|
|
||||||
contrib/**
|
|
||||||
**/AGENTS.md
|
**/AGENTS.md
|
||||||
|
|
||||||
- name: Setup Python with uv
|
- name: Setup Python with uv
|
||||||
|
|||||||
+144
-18
@@ -29,10 +29,11 @@ jobs:
|
|||||||
- '3.10'
|
- '3.10'
|
||||||
- '3.11'
|
- '3.11'
|
||||||
- '3.12'
|
- '3.12'
|
||||||
|
- '3.13'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -46,6 +47,7 @@ jobs:
|
|||||||
schema.ocsf.io:443
|
schema.ocsf.io:443
|
||||||
registry-1.docker.io:443
|
registry-1.docker.io:443
|
||||||
production.cloudflare.docker.com:443
|
production.cloudflare.docker.com:443
|
||||||
|
production.cloudfront.docker.com:443
|
||||||
powershellinfraartifacts-gkhedzdeaghdezhr.z01.azurefd.net:443
|
powershellinfraartifacts-gkhedzdeaghdezhr.z01.azurefd.net:443
|
||||||
o26192.ingest.us.sentry.io:443
|
o26192.ingest.us.sentry.io:443
|
||||||
management.azure.com:443
|
management.azure.com:443
|
||||||
@@ -69,12 +71,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Check for SDK changes
|
- name: Check for SDK changes
|
||||||
id: check-changes
|
id: check-changes
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: ./**
|
files: ./**
|
||||||
files_ignore: |
|
files_ignore: |
|
||||||
.github/**
|
.github/**
|
||||||
prowler/CHANGELOG.md
|
prowler/CHANGELOG.md
|
||||||
|
prowler/changelog.d/**
|
||||||
docs/**
|
docs/**
|
||||||
permissions/**
|
permissions/**
|
||||||
api/**
|
api/**
|
||||||
@@ -102,7 +105,7 @@ jobs:
|
|||||||
- name: Check if AWS files changed
|
- name: Check if AWS files changed
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
id: changed-aws
|
id: changed-aws
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
./prowler/**/aws/**
|
./prowler/**/aws/**
|
||||||
@@ -232,7 +235,7 @@ jobs:
|
|||||||
- name: Check if Azure files changed
|
- name: Check if Azure files changed
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
id: changed-azure
|
id: changed-azure
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
./prowler/**/azure/**
|
./prowler/**/azure/**
|
||||||
@@ -256,7 +259,7 @@ jobs:
|
|||||||
- name: Check if GCP files changed
|
- name: Check if GCP files changed
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
id: changed-gcp
|
id: changed-gcp
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
./prowler/**/gcp/**
|
./prowler/**/gcp/**
|
||||||
@@ -280,7 +283,7 @@ jobs:
|
|||||||
- name: Check if Kubernetes files changed
|
- name: Check if Kubernetes files changed
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
id: changed-kubernetes
|
id: changed-kubernetes
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
./prowler/**/kubernetes/**
|
./prowler/**/kubernetes/**
|
||||||
@@ -304,7 +307,7 @@ jobs:
|
|||||||
- name: Check if GitHub files changed
|
- name: Check if GitHub files changed
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
id: changed-github
|
id: changed-github
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
./prowler/**/github/**
|
./prowler/**/github/**
|
||||||
@@ -328,7 +331,7 @@ jobs:
|
|||||||
- name: Check if Okta files changed
|
- name: Check if Okta files changed
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
id: changed-okta
|
id: changed-okta
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
./prowler/**/okta/**
|
./prowler/**/okta/**
|
||||||
@@ -352,7 +355,7 @@ jobs:
|
|||||||
- name: Check if NHN files changed
|
- name: Check if NHN files changed
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
id: changed-nhn
|
id: changed-nhn
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
./prowler/**/nhn/**
|
./prowler/**/nhn/**
|
||||||
@@ -376,7 +379,7 @@ jobs:
|
|||||||
- name: Check if M365 files changed
|
- name: Check if M365 files changed
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
id: changed-m365
|
id: changed-m365
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
./prowler/**/m365/**
|
./prowler/**/m365/**
|
||||||
@@ -400,7 +403,7 @@ jobs:
|
|||||||
- name: Check if IaC files changed
|
- name: Check if IaC files changed
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
id: changed-iac
|
id: changed-iac
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
./prowler/**/iac/**
|
./prowler/**/iac/**
|
||||||
@@ -424,7 +427,7 @@ jobs:
|
|||||||
- name: Check if MongoDB Atlas files changed
|
- name: Check if MongoDB Atlas files changed
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
id: changed-mongodbatlas
|
id: changed-mongodbatlas
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
./prowler/**/mongodbatlas/**
|
./prowler/**/mongodbatlas/**
|
||||||
@@ -448,7 +451,7 @@ jobs:
|
|||||||
- name: Check if OCI files changed
|
- name: Check if OCI files changed
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
id: changed-oraclecloud
|
id: changed-oraclecloud
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
./prowler/**/oraclecloud/**
|
./prowler/**/oraclecloud/**
|
||||||
@@ -472,7 +475,7 @@ jobs:
|
|||||||
- name: Check if OpenStack files changed
|
- name: Check if OpenStack files changed
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
id: changed-openstack
|
id: changed-openstack
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
./prowler/**/openstack/**
|
./prowler/**/openstack/**
|
||||||
@@ -496,7 +499,7 @@ jobs:
|
|||||||
- name: Check if Google Workspace files changed
|
- name: Check if Google Workspace files changed
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
id: changed-googleworkspace
|
id: changed-googleworkspace
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
./prowler/**/googleworkspace/**
|
./prowler/**/googleworkspace/**
|
||||||
@@ -520,7 +523,7 @@ jobs:
|
|||||||
- name: Check if Vercel files changed
|
- name: Check if Vercel files changed
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
id: changed-vercel
|
id: changed-vercel
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
./prowler/**/vercel/**
|
./prowler/**/vercel/**
|
||||||
@@ -540,11 +543,134 @@ jobs:
|
|||||||
flags: prowler-py${{ matrix.python-version }}-vercel
|
flags: prowler-py${{ matrix.python-version }}-vercel
|
||||||
files: ./vercel_coverage.xml
|
files: ./vercel_coverage.xml
|
||||||
|
|
||||||
|
# Scaleway Provider
|
||||||
|
- name: Check if Scaleway files changed
|
||||||
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
|
id: changed-scaleway
|
||||||
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
./prowler/**/scaleway/**
|
||||||
|
./tests/**/scaleway/**
|
||||||
|
./uv.lock
|
||||||
|
|
||||||
|
- name: Run Scaleway tests
|
||||||
|
if: steps.changed-scaleway.outputs.any_changed == 'true'
|
||||||
|
run: uv run pytest -n auto --cov=./prowler/providers/scaleway --cov-report=xml:scaleway_coverage.xml tests/providers/scaleway
|
||||||
|
|
||||||
|
- name: Upload Scaleway coverage to Codecov
|
||||||
|
if: steps.changed-scaleway.outputs.any_changed == 'true'
|
||||||
|
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||||
|
env:
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
with:
|
||||||
|
flags: prowler-py${{ matrix.python-version }}-scaleway
|
||||||
|
files: ./scaleway_coverage.xml
|
||||||
|
|
||||||
|
# StackIT Provider
|
||||||
|
- name: Check if StackIT files changed
|
||||||
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
|
id: changed-stackit
|
||||||
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
./prowler/**/stackit/**
|
||||||
|
./tests/**/stackit/**
|
||||||
|
./uv.lock
|
||||||
|
|
||||||
|
- name: Run StackIT tests
|
||||||
|
if: steps.changed-stackit.outputs.any_changed == 'true'
|
||||||
|
run: uv run pytest -n auto --cov=./prowler/providers/stackit --cov-report=xml:stackit_coverage.xml tests/providers/stackit
|
||||||
|
|
||||||
|
- name: Upload StackIT coverage to Codecov
|
||||||
|
if: steps.changed-stackit.outputs.any_changed == 'true'
|
||||||
|
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||||
|
env:
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
with:
|
||||||
|
flags: prowler-py${{ matrix.python-version }}-stackit
|
||||||
|
files: ./stackit_coverage.xml
|
||||||
|
|
||||||
|
# Linode Provider
|
||||||
|
- name: Check if Linode files changed
|
||||||
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
|
id: changed-linode
|
||||||
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
./prowler/**/linode/**
|
||||||
|
./tests/**/linode/**
|
||||||
|
./uv.lock
|
||||||
|
|
||||||
|
- name: Run Linode tests
|
||||||
|
if: steps.changed-linode.outputs.any_changed == 'true'
|
||||||
|
run: uv run pytest -n auto --cov=./prowler/providers/linode --cov-report=xml:linode_coverage.xml tests/providers/linode
|
||||||
|
|
||||||
|
- name: Upload Linode coverage to Codecov
|
||||||
|
if: steps.changed-linode.outputs.any_changed == 'true'
|
||||||
|
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||||
|
env:
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
with:
|
||||||
|
flags: prowler-py${{ matrix.python-version }}-linode
|
||||||
|
files: ./linode_coverage.xml
|
||||||
|
|
||||||
|
# E2E Networks Provider
|
||||||
|
- name: Check if E2E Networks files changed
|
||||||
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
|
id: changed-e2enetworks
|
||||||
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
./prowler/**/e2enetworks/**
|
||||||
|
./tests/**/e2enetworks/**
|
||||||
|
./uv.lock
|
||||||
|
|
||||||
|
- name: Run E2E Networks tests
|
||||||
|
if: steps.changed-e2enetworks.outputs.any_changed == 'true'
|
||||||
|
run: uv run pytest -n auto --cov=./prowler/providers/e2enetworks --cov-report=xml:e2enetworks_coverage.xml tests/providers/e2enetworks
|
||||||
|
|
||||||
|
- name: Upload E2E Networks coverage to Codecov
|
||||||
|
if: steps.changed-e2enetworks.outputs.any_changed == 'true'
|
||||||
|
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||||
|
env:
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
with:
|
||||||
|
flags: prowler-py${{ matrix.python-version }}-e2enetworks
|
||||||
|
files: ./e2enetworks_coverage.xml
|
||||||
|
|
||||||
|
# External Provider (dynamic loading)
|
||||||
|
- name: Check if External Provider files changed
|
||||||
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
|
id: changed-external
|
||||||
|
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
./prowler/providers/common/**
|
||||||
|
./prowler/config/**
|
||||||
|
./prowler/lib/**
|
||||||
|
./tests/providers/external/**
|
||||||
|
./uv.lock
|
||||||
|
|
||||||
|
- name: Run External Provider tests
|
||||||
|
if: steps.changed-external.outputs.any_changed == 'true'
|
||||||
|
run: uv run pytest -n auto --cov=./prowler/providers/common --cov=./prowler/config --cov=./prowler/lib --cov-report=xml:external_coverage.xml tests/providers/external
|
||||||
|
|
||||||
|
- name: Upload External Provider coverage to Codecov
|
||||||
|
if: steps.changed-external.outputs.any_changed == 'true'
|
||||||
|
|
||||||
|
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||||
|
env:
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
with:
|
||||||
|
flags: prowler-py${{ matrix.python-version }}-external
|
||||||
|
files: ./external_coverage.xml
|
||||||
|
|
||||||
# Lib
|
# Lib
|
||||||
- name: Check if Lib files changed
|
- name: Check if Lib files changed
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
id: changed-lib
|
id: changed-lib
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
./prowler/lib/**
|
./prowler/lib/**
|
||||||
@@ -568,7 +694,7 @@ jobs:
|
|||||||
- name: Check if Config files changed
|
- name: Check if Config files changed
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
id: changed-config
|
id: changed-config
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
./prowler/config/**
|
./prowler/config/**
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -68,12 +68,12 @@ jobs:
|
|||||||
|
|
||||||
- name: Get changed files
|
- name: Get changed files
|
||||||
id: changed-files
|
id: changed-files
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
with:
|
with:
|
||||||
python-version: '3.12'
|
python-version: '3.12.13'
|
||||||
|
|
||||||
- name: Install PyYAML
|
- name: Install PyYAML
|
||||||
run: pip install pyyaml
|
run: pip install pyyaml
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ on:
|
|||||||
- '.github/workflows/ui-codeql.yml'
|
- '.github/workflows/ui-codeql.yml'
|
||||||
- '.github/codeql/ui-codeql-config.yml'
|
- '.github/codeql/ui-codeql-config.yml'
|
||||||
- '!ui/CHANGELOG.md'
|
- '!ui/CHANGELOG.md'
|
||||||
|
- '!ui/changelog.d/**'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- 'master'
|
- 'master'
|
||||||
@@ -19,6 +20,7 @@ on:
|
|||||||
- '.github/workflows/ui-codeql.yml'
|
- '.github/workflows/ui-codeql.yml'
|
||||||
- '.github/codeql/ui-codeql-config.yml'
|
- '.github/codeql/ui-codeql-config.yml'
|
||||||
- '!ui/CHANGELOG.md'
|
- '!ui/CHANGELOG.md'
|
||||||
|
- '!ui/changelog.d/**'
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '00 12 * * *'
|
- cron: '00 12 * * *'
|
||||||
|
|
||||||
@@ -47,7 +49,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -62,12 +64,12 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
|
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
config-file: ./.github/codeql/ui-codeql-config.yml
|
config-file: ./.github/codeql/ui-codeql-config.yml
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
|
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||||
with:
|
with:
|
||||||
category: '/language:${{ matrix.language }}'
|
category: '/language:${{ matrix.language }}'
|
||||||
|
|||||||
@@ -32,9 +32,6 @@ env:
|
|||||||
PROWLERCLOUD_DOCKERHUB_REPOSITORY: prowlercloud
|
PROWLERCLOUD_DOCKERHUB_REPOSITORY: prowlercloud
|
||||||
PROWLERCLOUD_DOCKERHUB_IMAGE: prowler-ui
|
PROWLERCLOUD_DOCKERHUB_IMAGE: prowler-ui
|
||||||
|
|
||||||
# Build args
|
|
||||||
NEXT_PUBLIC_API_BASE_URL: http://prowler-api:8080/api/v1
|
|
||||||
|
|
||||||
permissions: {}
|
permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -48,7 +45,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -67,7 +64,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -110,12 +107,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
registry-1.docker.io:443
|
registry-1.docker.io:443
|
||||||
production.cloudflare.docker.com:443
|
production.cloudflare.docker.com:443
|
||||||
|
production.cloudfront.docker.com:443
|
||||||
auth.docker.io:443
|
auth.docker.io:443
|
||||||
registry.npmjs.org:443
|
registry.npmjs.org:443
|
||||||
dl-cdn.alpinelinux.org:443
|
dl-cdn.alpinelinux.org:443
|
||||||
@@ -129,7 +127,7 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
@@ -140,12 +138,11 @@ jobs:
|
|||||||
- name: Build and push UI container for ${{ matrix.arch }}
|
- name: Build and push UI container for ${{ matrix.arch }}
|
||||||
id: container-push
|
id: container-push
|
||||||
if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||||
with:
|
with:
|
||||||
context: ${{ env.WORKING_DIRECTORY }}
|
context: ${{ env.WORKING_DIRECTORY }}
|
||||||
build-args: |
|
build-args: |
|
||||||
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=${{ (github.event_name == 'release' || github.event_name == 'workflow_dispatch') && format('v{0}', env.RELEASE_TAG) || needs.setup.outputs.short-sha }}
|
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=${{ (github.event_name == 'release' || github.event_name == 'workflow_dispatch') && format('v{0}', env.RELEASE_TAG) || needs.setup.outputs.short-sha }}
|
||||||
NEXT_PUBLIC_API_BASE_URL=${{ env.NEXT_PUBLIC_API_BASE_URL }}
|
|
||||||
push: true
|
push: true
|
||||||
platforms: ${{ matrix.platform }}
|
platforms: ${{ matrix.platform }}
|
||||||
tags: |
|
tags: |
|
||||||
@@ -163,7 +160,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -172,9 +169,10 @@ jobs:
|
|||||||
registry-1.docker.io:443
|
registry-1.docker.io:443
|
||||||
auth.docker.io:443
|
auth.docker.io:443
|
||||||
production.cloudflare.docker.com:443
|
production.cloudflare.docker.com:443
|
||||||
|
production.cloudfront.docker.com:443
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
@@ -203,7 +201,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install regctl
|
- name: Install regctl
|
||||||
if: always()
|
if: always()
|
||||||
uses: regclient/actions/regctl-installer@da9319db8e44e8b062b3a147e1dfb2f574d41a03 # main
|
uses: regclient/actions/regctl-installer@9a2d4216180dbb3e2dccfa60d2dd4afd98e42ec5 # main
|
||||||
|
|
||||||
- name: Cleanup intermediate architecture tags
|
- name: Cleanup intermediate architecture tags
|
||||||
if: always()
|
if: always()
|
||||||
@@ -224,7 +222,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -260,27 +258,3 @@ jobs:
|
|||||||
payload-file-path: "./.github/scripts/slack-messages/container-release-completed.json"
|
payload-file-path: "./.github/scripts/slack-messages/container-release-completed.json"
|
||||||
step-outcome: ${{ steps.outcome.outputs.outcome }}
|
step-outcome: ${{ steps.outcome.outputs.outcome }}
|
||||||
update-ts: ${{ needs.notify-release-started.outputs.message-ts }}
|
update-ts: ${{ needs.notify-release-started.outputs.message-ts }}
|
||||||
|
|
||||||
trigger-deployment:
|
|
||||||
needs: [setup, container-build-push]
|
|
||||||
if: always() && github.event_name == 'push' && needs.setup.result == 'success' && needs.container-build-push.result == 'success'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 5
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Harden Runner
|
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
|
||||||
with:
|
|
||||||
egress-policy: block
|
|
||||||
allowed-endpoints: >
|
|
||||||
api.github.com:443
|
|
||||||
|
|
||||||
- name: Trigger UI deployment
|
|
||||||
uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
|
||||||
repository: ${{ secrets.CLOUD_DISPATCH }}
|
|
||||||
event-type: ui-prowler-deployment
|
|
||||||
client-payload: '{"sha": "${{ github.sha }}", "short_sha": "${{ needs.setup.outputs.short-sha }}"}'
|
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- 'master'
|
- 'master'
|
||||||
- 'v5.*'
|
- 'v5.*'
|
||||||
paths:
|
|
||||||
- 'ui/**'
|
|
||||||
- '.github/workflows/ui-container-checks.yml'
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
@@ -36,7 +33,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -50,7 +47,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Check if Dockerfile changed
|
- name: Check if Dockerfile changed
|
||||||
id: dockerfile-changed
|
id: dockerfile-changed
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: ui/Dockerfile
|
files: ui/Dockerfile
|
||||||
|
|
||||||
@@ -72,7 +69,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -100,11 +97,12 @@ jobs:
|
|||||||
|
|
||||||
- name: Check for UI changes
|
- name: Check for UI changes
|
||||||
id: check-changes
|
id: check-changes
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: ui/**
|
files: ui/**
|
||||||
files_ignore: |
|
files_ignore: |
|
||||||
ui/CHANGELOG.md
|
ui/CHANGELOG.md
|
||||||
|
ui/changelog.d/**
|
||||||
ui/README.md
|
ui/README.md
|
||||||
ui/AGENTS.md
|
ui/AGENTS.md
|
||||||
|
|
||||||
@@ -114,7 +112,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build UI container
|
- name: Build UI container
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||||
with:
|
with:
|
||||||
context: ${{ env.UI_WORKING_DIR }}
|
context: ${{ env.UI_WORKING_DIR }}
|
||||||
target: prod
|
target: prod
|
||||||
@@ -132,5 +130,5 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
image-name: ${{ env.IMAGE_NAME }}
|
image-name: ${{ env.IMAGE_NAME }}
|
||||||
image-tag: ${{ github.sha }}
|
image-tag: ${{ github.sha }}
|
||||||
fail-on-critical: 'false'
|
fail-on-critical: 'true'
|
||||||
severity: 'CRITICAL'
|
severity: 'CRITICAL'
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ on:
|
|||||||
- '.github/test-impact.yml'
|
- '.github/test-impact.yml'
|
||||||
- 'ui/**'
|
- 'ui/**'
|
||||||
- 'api/**' # API changes can affect UI E2E
|
- 'api/**' # API changes can affect UI E2E
|
||||||
|
- '!ui/CHANGELOG.md'
|
||||||
|
- '!api/CHANGELOG.md'
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
@@ -40,7 +42,8 @@ jobs:
|
|||||||
AUTH_SECRET: 'fallback-ci-secret-for-testing'
|
AUTH_SECRET: 'fallback-ci-secret-for-testing'
|
||||||
AUTH_TRUST_HOST: true
|
AUTH_TRUST_HOST: true
|
||||||
NEXTAUTH_URL: 'http://localhost:3000'
|
NEXTAUTH_URL: 'http://localhost:3000'
|
||||||
NEXT_PUBLIC_API_BASE_URL: 'http://localhost:8080/api/v1'
|
AUTH_URL: 'http://localhost:3000'
|
||||||
|
UI_API_BASE_URL: 'http://localhost:8080/api/v1'
|
||||||
E2E_ADMIN_USER: ${{ secrets.E2E_ADMIN_USER }}
|
E2E_ADMIN_USER: ${{ secrets.E2E_ADMIN_USER }}
|
||||||
E2E_ADMIN_PASSWORD: ${{ secrets.E2E_ADMIN_PASSWORD }}
|
E2E_ADMIN_PASSWORD: ${{ secrets.E2E_ADMIN_PASSWORD }}
|
||||||
E2E_AWS_PROVIDER_ACCOUNT_ID: ${{ secrets.E2E_AWS_PROVIDER_ACCOUNT_ID }}
|
E2E_AWS_PROVIDER_ACCOUNT_ID: ${{ secrets.E2E_AWS_PROVIDER_ACCOUNT_ID }}
|
||||||
@@ -77,6 +80,14 @@ jobs:
|
|||||||
E2E_ALIBABACLOUD_ACCESS_KEY_ID: ${{ secrets.E2E_ALIBABACLOUD_ACCESS_KEY_ID }}
|
E2E_ALIBABACLOUD_ACCESS_KEY_ID: ${{ secrets.E2E_ALIBABACLOUD_ACCESS_KEY_ID }}
|
||||||
E2E_ALIBABACLOUD_ACCESS_KEY_SECRET: ${{ secrets.E2E_ALIBABACLOUD_ACCESS_KEY_SECRET }}
|
E2E_ALIBABACLOUD_ACCESS_KEY_SECRET: ${{ secrets.E2E_ALIBABACLOUD_ACCESS_KEY_SECRET }}
|
||||||
E2E_ALIBABACLOUD_ROLE_ARN: ${{ secrets.E2E_ALIBABACLOUD_ROLE_ARN }}
|
E2E_ALIBABACLOUD_ROLE_ARN: ${{ secrets.E2E_ALIBABACLOUD_ROLE_ARN }}
|
||||||
|
E2E_OKTA_DOMAIN: ${{ secrets.E2E_OKTA_DOMAIN }}
|
||||||
|
E2E_OKTA_CLIENT_ID: ${{ secrets.E2E_OKTA_CLIENT_ID }}
|
||||||
|
E2E_OKTA_BASE64_PRIVATE_KEY: ${{ secrets.E2E_OKTA_BASE64_PRIVATE_KEY }}
|
||||||
|
E2E_GOOGLEWORKSPACE_CUSTOMER_ID: ${{ secrets.E2E_GOOGLEWORKSPACE_CUSTOMER_ID }}
|
||||||
|
E2E_GOOGLEWORKSPACE_SERVICE_ACCOUNT_JSON: ${{ secrets.E2E_GOOGLEWORKSPACE_SERVICE_ACCOUNT_JSON }}
|
||||||
|
E2E_GOOGLEWORKSPACE_DELEGATED_USER: ${{ secrets.E2E_GOOGLEWORKSPACE_DELEGATED_USER }}
|
||||||
|
E2E_VERCEL_TEAM_ID: ${{ secrets.E2E_VERCEL_TEAM_ID }}
|
||||||
|
E2E_VERCEL_API_TOKEN: ${{ secrets.E2E_VERCEL_API_TOKEN }}
|
||||||
# Pass E2E paths from impact analysis
|
# Pass E2E paths from impact analysis
|
||||||
E2E_TEST_PATHS: ${{ needs.impact-analysis.outputs.ui-e2e }}
|
E2E_TEST_PATHS: ${{ needs.impact-analysis.outputs.ui-e2e }}
|
||||||
RUN_ALL_TESTS: ${{ needs.impact-analysis.outputs.run-all }}
|
RUN_ALL_TESTS: ${{ needs.impact-analysis.outputs.run-all }}
|
||||||
@@ -85,7 +96,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -134,7 +145,17 @@ jobs:
|
|||||||
# docker-compose.yml references prowlercloud/prowler-api:latest from the registry,
|
# docker-compose.yml references prowlercloud/prowler-api:latest from the registry,
|
||||||
# which lags behind PR changes; build locally so E2E exercises the API image
|
# which lags behind PR changes; build locally so E2E exercises the API image
|
||||||
# produced by this PR.
|
# produced by this PR.
|
||||||
run: docker build -t prowlercloud/prowler-api:latest ./api
|
#
|
||||||
|
# The image installs the SDK from git@master (api/uv.lock), so a PR changing BOTH the SDK
|
||||||
|
# and the API would run against the OLD SDK and crash on startup. Overlay the checkout's
|
||||||
|
# SDK source so both run together. New SDK dependencies still need an api/uv.lock bump.
|
||||||
|
run: |
|
||||||
|
docker build -t prowlercloud/prowler-api:pr-base ./api
|
||||||
|
docker build -t prowlercloud/prowler-api:latest -f - prowler <<'DOCKERFILE'
|
||||||
|
FROM prowlercloud/prowler-api:pr-base
|
||||||
|
RUN rm -rf /home/prowler/.venv/lib/python3.12/site-packages/prowler
|
||||||
|
COPY --chown=prowler:prowler . /home/prowler/.venv/lib/python3.12/site-packages/prowler
|
||||||
|
DOCKERFILE
|
||||||
|
|
||||||
- name: Start API services
|
- name: Start API services
|
||||||
run: |
|
run: |
|
||||||
@@ -147,7 +168,7 @@ jobs:
|
|||||||
timeout=150
|
timeout=150
|
||||||
elapsed=0
|
elapsed=0
|
||||||
while [ $elapsed -lt $timeout ]; do
|
while [ $elapsed -lt $timeout ]; do
|
||||||
if curl -s ${NEXT_PUBLIC_API_BASE_URL}/docs >/dev/null 2>&1; then
|
if curl -s ${UI_API_BASE_URL}/docs >/dev/null 2>&1; then
|
||||||
echo "Prowler API is ready!"
|
echo "Prowler API is ready!"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
@@ -172,7 +193,7 @@ jobs:
|
|||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: '24.13.0'
|
node-version-file: 'ui/.nvmrc'
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||||
@@ -184,7 +205,7 @@ jobs:
|
|||||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Setup pnpm and Next.js cache
|
- name: Setup pnpm and Next.js cache
|
||||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
${{ env.STORE_PATH }}
|
${{ env.STORE_PATH }}
|
||||||
@@ -204,7 +225,7 @@ jobs:
|
|||||||
run: pnpm run build
|
run: pnpm run build
|
||||||
|
|
||||||
- name: Cache Playwright browsers
|
- name: Cache Playwright browsers
|
||||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
id: playwright-cache
|
id: playwright-cache
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/ms-playwright
|
path: ~/.cache/ms-playwright
|
||||||
@@ -295,7 +316,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
@@ -15,12 +15,6 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- 'master'
|
- 'master'
|
||||||
- 'v5.*'
|
- 'v5.*'
|
||||||
paths:
|
|
||||||
- 'ui/package.json'
|
|
||||||
- 'ui/pnpm-lock.yaml'
|
|
||||||
- '.github/workflows/ui-security.yml'
|
|
||||||
- '.github/actions/osv-scanner/**'
|
|
||||||
- '.github/scripts/osv-scan.sh'
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
@@ -30,7 +24,6 @@ permissions: {}
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ui-security-scans:
|
ui-security-scans:
|
||||||
if: github.repository == 'prowler-cloud/prowler'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
permissions:
|
permissions:
|
||||||
@@ -39,7 +32,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
@@ -59,7 +52,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Check for UI dependency changes
|
- name: Check for UI dependency changes
|
||||||
id: check-changes
|
id: check-changes
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
ui/package.json
|
ui/package.json
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ concurrency:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
UI_WORKING_DIR: ./ui
|
UI_WORKING_DIR: ./ui
|
||||||
NODE_VERSION: "24.13.0"
|
|
||||||
|
|
||||||
permissions: {}
|
permissions: {}
|
||||||
|
|
||||||
@@ -32,14 +31,18 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||||
with:
|
with:
|
||||||
egress-policy: block
|
egress-policy: block
|
||||||
allowed-endpoints: >
|
allowed-endpoints: >
|
||||||
github.com:443
|
github.com:443
|
||||||
registry.npmjs.org:443
|
registry.npmjs.org:443
|
||||||
|
nodejs.org:443
|
||||||
fonts.googleapis.com:443
|
fonts.googleapis.com:443
|
||||||
fonts.gstatic.com:443
|
fonts.gstatic.com:443
|
||||||
|
api.iconify.design:443
|
||||||
|
api.simplesvg.com:443
|
||||||
|
api.unisvg.com:443
|
||||||
api.github.com:443
|
api.github.com:443
|
||||||
release-assets.githubusercontent.com:443
|
release-assets.githubusercontent.com:443
|
||||||
cdn.playwright.dev:443
|
cdn.playwright.dev:443
|
||||||
@@ -54,20 +57,21 @@ jobs:
|
|||||||
|
|
||||||
- name: Check for UI changes
|
- name: Check for UI changes
|
||||||
id: check-changes
|
id: check-changes
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
ui/**
|
ui/**
|
||||||
.github/workflows/ui-tests.yml
|
.github/workflows/ui-tests.yml
|
||||||
files_ignore: |
|
files_ignore: |
|
||||||
ui/CHANGELOG.md
|
ui/CHANGELOG.md
|
||||||
|
ui/changelog.d/**
|
||||||
ui/README.md
|
ui/README.md
|
||||||
ui/AGENTS.md
|
ui/AGENTS.md
|
||||||
|
|
||||||
- name: Get changed source files for targeted tests
|
- name: Get changed source files for targeted tests
|
||||||
id: changed-source
|
id: changed-source
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
ui/**/*.ts
|
ui/**/*.ts
|
||||||
@@ -83,7 +87,7 @@ jobs:
|
|||||||
- name: Check for critical path changes (run all tests)
|
- name: Check for critical path changes (run all tests)
|
||||||
id: critical-changes
|
id: critical-changes
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
ui/lib/**
|
ui/lib/**
|
||||||
@@ -93,11 +97,11 @@ jobs:
|
|||||||
ui/vitest.config.ts
|
ui/vitest.config.ts
|
||||||
ui/vitest.setup.ts
|
ui/vitest.setup.ts
|
||||||
|
|
||||||
- name: Setup Node.js ${{ env.NODE_VERSION }}
|
- name: Setup Node.js
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
node-version-file: 'ui/.nvmrc'
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
@@ -113,7 +117,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Setup pnpm and Next.js cache
|
- name: Setup pnpm and Next.js cache
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
${{ env.STORE_PATH }}
|
${{ env.STORE_PATH }}
|
||||||
@@ -132,6 +136,10 @@ jobs:
|
|||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
run: pnpm run healthcheck
|
run: pnpm run healthcheck
|
||||||
|
|
||||||
|
- name: Check product-tour alignment
|
||||||
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
|
run: pnpm run tour:check
|
||||||
|
|
||||||
- name: Run pnpm audit
|
- name: Run pnpm audit
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
run: pnpm run audit
|
run: pnpm run audit
|
||||||
@@ -162,7 +170,7 @@ jobs:
|
|||||||
- name: Cache Playwright browsers
|
- name: Cache Playwright browsers
|
||||||
if: steps.check-changes.outputs.any_changed == 'true'
|
if: steps.check-changes.outputs.any_changed == 'true'
|
||||||
id: playwright-cache
|
id: playwright-cache
|
||||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/ms-playwright
|
path: ~/.cache/ms-playwright
|
||||||
key: ${{ runner.os }}-playwright-chromium-${{ hashFiles('ui/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-playwright-chromium-${{ hashFiles('ui/pnpm-lock.yaml') }}
|
||||||
|
|||||||
+4
-1
@@ -60,7 +60,6 @@ htmlcov/
|
|||||||
**/mcp-config.json
|
**/mcp-config.json
|
||||||
**/mcpServers.json
|
**/mcpServers.json
|
||||||
.mcp/
|
.mcp/
|
||||||
.mcp.json
|
|
||||||
|
|
||||||
# AI Coding Assistants - Cursor
|
# AI Coding Assistants - Cursor
|
||||||
.cursorignore
|
.cursorignore
|
||||||
@@ -170,3 +169,7 @@ GEMINI.md
|
|||||||
|
|
||||||
# Claude Code
|
# Claude Code
|
||||||
.claude/*
|
.claude/*
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
docker-compose.override.yml
|
||||||
|
docker-compose-dev.override.yml
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "markdownlint/style/prettier",
|
||||||
|
"first-line-h1": false,
|
||||||
|
"no-duplicate-heading": {
|
||||||
|
"siblings_only": true
|
||||||
|
},
|
||||||
|
"no-inline-html": false,
|
||||||
|
"line-length": false,
|
||||||
|
"no-bare-urls": false
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
node_modules/
|
||||||
|
ui/node_modules/
|
||||||
|
.git/
|
||||||
|
.venv/
|
||||||
|
**/.venv/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
htmlcov/
|
||||||
|
.next/
|
||||||
|
ui/.next/
|
||||||
|
ui/out/
|
||||||
|
contrib/
|
||||||
|
|
||||||
|
# Auto-generated content (keepachangelog format legitimately repeats section headings).
|
||||||
|
# Revisit with the team — see beads task on markdownlint rule triage.
|
||||||
|
**/CHANGELOG.md
|
||||||
+65
-15
@@ -7,6 +7,10 @@
|
|||||||
# P50 — dependency validation
|
# P50 — dependency validation
|
||||||
|
|
||||||
default_install_hook_types: [pre-commit]
|
default_install_hook_types: [pre-commit]
|
||||||
|
# Hooks run on commit only by default;
|
||||||
|
# NOTE: default_stages does NOT override a hook's manifest stages, so fixers shipping pre-push in their
|
||||||
|
# manifest need an explicit stages: ["pre-commit"] below to stay off push.
|
||||||
|
default_stages: [pre-commit]
|
||||||
|
|
||||||
repos:
|
repos:
|
||||||
## GENERAL (prek built-in — no external repo needed)
|
## GENERAL (prek built-in — no external repo needed)
|
||||||
@@ -21,13 +25,16 @@ repos:
|
|||||||
- id: check-json
|
- id: check-json
|
||||||
priority: 10
|
priority: 10
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
|
stages: ["pre-commit"]
|
||||||
priority: 0
|
priority: 0
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
|
stages: ["pre-commit"]
|
||||||
priority: 0
|
priority: 0
|
||||||
- id: no-commit-to-branch
|
- id: no-commit-to-branch
|
||||||
priority: 10
|
priority: 10
|
||||||
- id: pretty-format-json
|
- id: pretty-format-json
|
||||||
args: ["--autofix", --no-sort-keys, --no-ensure-ascii]
|
args: ["--autofix", --no-sort-keys, --no-ensure-ascii]
|
||||||
|
stages: ["pre-commit"]
|
||||||
priority: 10
|
priority: 10
|
||||||
|
|
||||||
## TOML
|
## TOML
|
||||||
@@ -49,6 +56,14 @@ repos:
|
|||||||
files: ^\.github/(workflows|actions)/.+\.ya?ml$|^\.github/dependabot\.ya?ml$
|
files: ^\.github/(workflows|actions)/.+\.ya?ml$|^\.github/dependabot\.ya?ml$
|
||||||
priority: 30
|
priority: 30
|
||||||
|
|
||||||
|
## RENOVATE
|
||||||
|
- repo: https://github.com/renovatebot/pre-commit-hooks
|
||||||
|
rev: 43.150.0
|
||||||
|
hooks:
|
||||||
|
- id: renovate-config-validator
|
||||||
|
files: ^\.github/renovate\.json$
|
||||||
|
priority: 10
|
||||||
|
|
||||||
## BASH
|
## BASH
|
||||||
- repo: https://github.com/koalaman/shellcheck-precommit
|
- repo: https://github.com/koalaman/shellcheck-precommit
|
||||||
rev: v0.11.0
|
rev: v0.11.0
|
||||||
@@ -57,13 +72,13 @@ repos:
|
|||||||
exclude: contrib
|
exclude: contrib
|
||||||
priority: 30
|
priority: 30
|
||||||
|
|
||||||
## PYTHON — SDK (prowler/, tests/, dashboard/, util/, scripts/)
|
## PYTHON — SDK (prowler/, tests/, dashboard/, util/, scripts/, docs/scripts/)
|
||||||
- repo: https://github.com/myint/autoflake
|
- repo: https://github.com/myint/autoflake
|
||||||
rev: v2.3.3
|
rev: v2.3.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: autoflake
|
- id: autoflake
|
||||||
name: "SDK - autoflake"
|
name: "SDK - autoflake"
|
||||||
files: { glob: ["{prowler,tests,dashboard,util,scripts}/**/*.py"] }
|
files: { glob: ["{prowler,tests,dashboard,util,scripts,docs/scripts}/**/*.py"] }
|
||||||
args: ["--in-place", "--remove-all-unused-imports", "--remove-unused-variable"]
|
args: ["--in-place", "--remove-all-unused-imports", "--remove-unused-variable"]
|
||||||
priority: 20
|
priority: 20
|
||||||
|
|
||||||
@@ -72,8 +87,9 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: isort
|
||||||
name: "SDK - isort"
|
name: "SDK - isort"
|
||||||
files: { glob: ["{prowler,tests,dashboard,util,scripts}/**/*.py"] }
|
files: { glob: ["{prowler,tests,dashboard,util,scripts,docs/scripts}/**/*.py"] }
|
||||||
args: ["--profile", "black"]
|
args: ["--profile", "black"]
|
||||||
|
stages: ["pre-commit"]
|
||||||
priority: 20
|
priority: 20
|
||||||
|
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
@@ -81,7 +97,7 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
name: "SDK - black"
|
name: "SDK - black"
|
||||||
files: { glob: ["{prowler,tests,dashboard,util,scripts}/**/*.py"] }
|
files: { glob: ["{prowler,tests,dashboard,util,scripts,docs/scripts}/**/*.py"] }
|
||||||
priority: 20
|
priority: 20
|
||||||
|
|
||||||
- repo: https://github.com/pycqa/flake8
|
- repo: https://github.com/pycqa/flake8
|
||||||
@@ -89,22 +105,49 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
name: "SDK - flake8"
|
name: "SDK - flake8"
|
||||||
files: { glob: ["{prowler,tests,dashboard,util,scripts}/**/*.py"] }
|
files: { glob: ["{prowler,tests,dashboard,util,scripts,docs/scripts}/**/*.py"] }
|
||||||
args: ["--ignore=E266,W503,E203,E501,W605"]
|
args: ["--ignore=E266,W503,E203,E501,W605"]
|
||||||
priority: 30
|
priority: 30
|
||||||
|
|
||||||
## PYTHON — API + MCP Server (ruff)
|
## PYTHON — API + MCP Server (ruff)
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
# Run ruff through `uv run` against each project so prek uses the exact ruff
|
||||||
rev: v0.15.11
|
# version pinned in that project's uv.lock — the same version GitHub Actions
|
||||||
|
# runs via `uv run ruff`. This removes the drift between the local hooks and
|
||||||
|
# CI. api/ and mcp_server/ are separate uv projects, so they need separate
|
||||||
|
# hooks (each `uv run --project` resolves its own pinned ruff + config).
|
||||||
|
- repo: local
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff-check-api
|
||||||
name: "API + MCP - ruff check"
|
name: "API - ruff check"
|
||||||
files: { glob: ["{api,mcp_server}/**/*.py"] }
|
entry: uv run --project ./api ruff check --fix
|
||||||
args: ["--fix"]
|
language: system
|
||||||
|
files: { glob: ["api/**/*.py"] }
|
||||||
priority: 30
|
priority: 30
|
||||||
- id: ruff-format
|
- id: ruff-format-api
|
||||||
name: "API + MCP - ruff format"
|
name: "API - ruff format"
|
||||||
files: { glob: ["{api,mcp_server}/**/*.py"] }
|
entry: uv run --project ./api ruff format
|
||||||
|
language: system
|
||||||
|
files: { glob: ["api/**/*.py"] }
|
||||||
|
priority: 20
|
||||||
|
- id: ruff-check-mcp
|
||||||
|
name: "MCP - ruff check"
|
||||||
|
entry: uv run --project ./mcp_server ruff check --fix
|
||||||
|
language: system
|
||||||
|
files: { glob: ["mcp_server/**/*.py"] }
|
||||||
|
priority: 30
|
||||||
|
- id: ruff-format-mcp
|
||||||
|
name: "MCP - ruff format"
|
||||||
|
entry: uv run --project ./mcp_server ruff format
|
||||||
|
language: system
|
||||||
|
files: { glob: ["mcp_server/**/*.py"] }
|
||||||
|
priority: 20
|
||||||
|
|
||||||
|
- id: generate-provider-cards
|
||||||
|
name: "Docs - regenerate provider cards snippet"
|
||||||
|
entry: python3 docs/scripts/generate_provider_cards.py
|
||||||
|
language: system
|
||||||
|
files: { glob: ["docs/user-guide/providers/**/getting-started-*.mdx", "docs/scripts/generate_provider_cards.py", "docs/snippets/provider-cards.mdx", "api/src/backend/api/models.py"] }
|
||||||
|
pass_filenames: false
|
||||||
priority: 20
|
priority: 20
|
||||||
|
|
||||||
## PYTHON — uv (API + SDK)
|
## PYTHON — uv (API + SDK)
|
||||||
@@ -125,6 +168,13 @@ repos:
|
|||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
priority: 50
|
priority: 50
|
||||||
|
|
||||||
|
## MARKDOWN
|
||||||
|
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||||
|
rev: v0.45.0
|
||||||
|
hooks:
|
||||||
|
- id: markdownlint
|
||||||
|
priority: 30
|
||||||
|
|
||||||
## CONTAINERS
|
## CONTAINERS
|
||||||
- repo: https://github.com/hadolint/hadolint
|
- repo: https://github.com/hadolint/hadolint
|
||||||
rev: v2.14.0
|
rev: v2.14.0
|
||||||
@@ -141,7 +191,7 @@ repos:
|
|||||||
entry: pylint --disable=W,C,R,E -j 0 -rn -sn
|
entry: pylint --disable=W,C,R,E -j 0 -rn -sn
|
||||||
language: system
|
language: system
|
||||||
types: [python]
|
types: [python]
|
||||||
files: { glob: ["{prowler,tests,dashboard,util,scripts}/**/*.py"] }
|
files: { glob: ["{prowler,tests,dashboard,util,scripts,docs/scripts}/**/*.py"] }
|
||||||
priority: 30
|
priority: 30
|
||||||
|
|
||||||
- id: trufflehog
|
- id: trufflehog
|
||||||
|
|||||||
+123
@@ -0,0 +1,123 @@
|
|||||||
|
# Trivy ignore file for prowlercloud/prowler SDK container image.
|
||||||
|
# Each entry below documents (a) the affected package and why it ships in the
|
||||||
|
# image, (b) why the CVE is not exploitable in Prowler's runtime, and (c) the
|
||||||
|
# upstream fix status. Entries carry an expiry so they auto-force re-review.
|
||||||
|
# Entries are scoped per-package so suppressions cannot drift onto unrelated
|
||||||
|
# packages that may be assigned the same CVE in the future.
|
||||||
|
#
|
||||||
|
# Scanned by: .github/actions/trivy-scan via .github/workflows/sdk-container-checks.yml
|
||||||
|
|
||||||
|
# CVE-2026-42496 — perl-archive-tar path traversal via crafted symlinks.
|
||||||
|
# CVE-2026-8376 — perl heap buffer overflow when compiling regex.
|
||||||
|
# Packages: perl, perl-base, perl-modules-5.36, libperl5.36.
|
||||||
|
# Why ignored: perl-base is part of Debian's "Essential: yes" set; it cannot be
|
||||||
|
# removed without breaking dpkg. The Prowler SDK does not invoke perl at runtime;
|
||||||
|
# neither vulnerable code path (Archive::Tar parsing or regex compilation of
|
||||||
|
# attacker-controlled input) is reachable from Prowler. No Debian bookworm fix
|
||||||
|
# is available yet.
|
||||||
|
CVE-2026-42496 pkg:perl exp:2026-08-15
|
||||||
|
CVE-2026-42496 pkg:perl-base exp:2026-08-15
|
||||||
|
CVE-2026-42496 pkg:perl-modules-5.36 exp:2026-08-15
|
||||||
|
CVE-2026-42496 pkg:libperl5.36 exp:2026-08-15
|
||||||
|
CVE-2026-8376 pkg:perl exp:2026-08-15
|
||||||
|
CVE-2026-8376 pkg:perl-base exp:2026-08-15
|
||||||
|
CVE-2026-8376 pkg:perl-modules-5.36 exp:2026-08-15
|
||||||
|
CVE-2026-8376 pkg:libperl5.36 exp:2026-08-15
|
||||||
|
|
||||||
|
# CVE-2026-13221 - Perl regex trie overflow.
|
||||||
|
# Packages: perl, perl-base, perl-modules-5.36, libperl5.36.
|
||||||
|
# Why ignored: upstream confirms Perl 5.36.0 is not affected; the regression
|
||||||
|
# was introduced after this version. Debian currently marks bookworm as
|
||||||
|
# vulnerable, which causes Trivy to report a false positive.
|
||||||
|
# Ref: https://github.com/Perl/perl5/issues/23388
|
||||||
|
CVE-2026-13221 pkg:perl exp:2026-08-15
|
||||||
|
CVE-2026-13221 pkg:perl-base exp:2026-08-15
|
||||||
|
CVE-2026-13221 pkg:perl-modules-5.36 exp:2026-08-15
|
||||||
|
CVE-2026-13221 pkg:libperl5.36 exp:2026-08-15
|
||||||
|
|
||||||
|
# CVE-2026-57433 — Perl Storable signed integer overflow when deserializing a
|
||||||
|
# crafted SX_HOOK record (retrieve_hook_common passes a wrapped negative count
|
||||||
|
# to av_extend).
|
||||||
|
# Packages: perl, perl-base, perl-modules-5.36, libperl5.36.
|
||||||
|
# Why ignored: perl-base is part of Debian's "Essential: yes" set; it cannot be
|
||||||
|
# removed without breaking dpkg. Prowler does not invoke perl at runtime and
|
||||||
|
# never calls Storable's thaw/retrieve on attacker-controlled blobs, so the
|
||||||
|
# vulnerable deserialization path is unreachable. Fixed upstream in
|
||||||
|
# Storable 3.41; no Debian bookworm fix is available yet.
|
||||||
|
CVE-2026-57433 pkg:perl exp:2026-08-15
|
||||||
|
CVE-2026-57433 pkg:perl-base exp:2026-08-15
|
||||||
|
CVE-2026-57433 pkg:perl-modules-5.36 exp:2026-08-15
|
||||||
|
CVE-2026-57433 pkg:libperl5.36 exp:2026-08-15
|
||||||
|
|
||||||
|
# CVE-2025-7458 — SQLite integer overflow.
|
||||||
|
# Package: libsqlite3-0.
|
||||||
|
# Why ignored: transitive dependency of CPython's stdlib sqlite3 module. The
|
||||||
|
# Prowler SDK does not open user-supplied SQLite databases; SQLite usage is
|
||||||
|
# internal and bounded. No Debian bookworm fix is available.
|
||||||
|
CVE-2025-7458 pkg:libsqlite3-0 exp:2026-08-15
|
||||||
|
|
||||||
|
# CVE-2026-43185 — Linux kernel ksmbd signedness bug.
|
||||||
|
# Package: linux-libc-dev.
|
||||||
|
# Why ignored: linux-libc-dev ships kernel headers for build-time compilation,
|
||||||
|
# not a running kernel. Containers execute against the host kernel, so these
|
||||||
|
# headers are inert at runtime. The upstream fix landed in kernel 7.0-rc2 and
|
||||||
|
# has not been backported to Debian's 6.1 LTS line.
|
||||||
|
CVE-2026-43185 pkg:linux-libc-dev exp:2026-08-15
|
||||||
|
|
||||||
|
# CVE-2023-45853 — zlib MiniZip integer overflow / heap overflow in
|
||||||
|
# zipOpenNewFileInZip4_64.
|
||||||
|
# Packages: zlib1g, zlib1g-dev.
|
||||||
|
# Why ignored: Debian Security Tracker status for bookworm is <ignored>, with
|
||||||
|
# the published rationale "contrib/minizip not built and src:zlib not producing
|
||||||
|
# binary packages" — i.e. the vulnerable symbol is not present in the libz.so
|
||||||
|
# shipped by Debian. Real-not-affected, not unpatched. Upstream fix is in
|
||||||
|
# zlib 1.3.1, available in Debian trixie (13); migrating the base image would
|
||||||
|
# clear it fully.
|
||||||
|
# Ref: https://security-tracker.debian.org/tracker/CVE-2023-45853
|
||||||
|
CVE-2023-45853 pkg:zlib1g exp:2026-08-15
|
||||||
|
CVE-2023-45853 pkg:zlib1g-dev exp:2026-08-15
|
||||||
|
|
||||||
|
# CVE-2026-55200 — libssh2 out-of-bounds write in ssh2_transport_read() due to
|
||||||
|
# an unchecked packet_length field in transport.c (heap corruption, possible RCE).
|
||||||
|
# Package: libssh2-1.
|
||||||
|
# Why ignored: libssh2-1 is pulled in only as a transitive dependency of libcurl4
|
||||||
|
# (installed in the SDK Dockerfile for the networking/PowerShell stack). The
|
||||||
|
# vulnerable path is reached exclusively when libssh2 acts as an SSH/SCP/SFTP
|
||||||
|
# client parsing transport packets from a server. Prowler never uses libcurl's
|
||||||
|
# SSH/SCP/SFTP transports; it talks to cloud provider HTTPS endpoints only, so the
|
||||||
|
# affected code is unreachable at runtime. Fixed upstream in libssh2 commit
|
||||||
|
# 97acf3df (PR #2052); no Debian bookworm fix is available yet.
|
||||||
|
# Ref: https://security-tracker.debian.org/tracker/CVE-2026-55200
|
||||||
|
CVE-2026-55200 pkg:libssh2-1 exp:2026-08-15
|
||||||
|
|
||||||
|
# --- API container image (api/Dockerfile) ---
|
||||||
|
# The entries below are specific to the Prowler API image, which ships
|
||||||
|
# PowerShell and additional build tooling on top of the same bookworm base.
|
||||||
|
|
||||||
|
# CVE-2026-7210 — CPython/Expat hash-flooding denial of service in
|
||||||
|
# `xml.parsers.expat` and `xml.etree.ElementTree`.
|
||||||
|
# Packages: the Debian system Python 3.11 (python3.11*, libpython3.11*).
|
||||||
|
# Why ignored: the API runs under the Python 3.12 interpreter shipped in its
|
||||||
|
# `.venv`; the system `python3.11` is only present because `python3-dev` is
|
||||||
|
# pulled in to compile native extensions (xmlsec, lxml) and is never executed
|
||||||
|
# at runtime. The vulnerable path requires parsing attacker-controlled XML with
|
||||||
|
# the affected interpreter, which Prowler does not do with the system Python.
|
||||||
|
# Full mitigation also needs libexpat >= 2.8.0; no Debian bookworm fix yet.
|
||||||
|
CVE-2026-7210 pkg:python3.11 exp:2026-08-15
|
||||||
|
CVE-2026-7210 pkg:python3.11-dev exp:2026-08-15
|
||||||
|
CVE-2026-7210 pkg:python3.11-minimal exp:2026-08-15
|
||||||
|
CVE-2026-7210 pkg:libpython3.11 exp:2026-08-15
|
||||||
|
CVE-2026-7210 pkg:libpython3.11-dev exp:2026-08-15
|
||||||
|
CVE-2026-7210 pkg:libpython3.11-minimal exp:2026-08-15
|
||||||
|
CVE-2026-7210 pkg:libpython3.11-stdlib exp:2026-08-15
|
||||||
|
|
||||||
|
# CVE-2026-33278 — Unbound DNSSEC validator use-after-free (DoS, possible RCE).
|
||||||
|
# CVE-2026-42960 — Unbound DNS cache poisoning via promiscuous additional records.
|
||||||
|
# Package: libunbound8.
|
||||||
|
# Why ignored: libunbound8 is a transitive apt dependency of the TLS/networking
|
||||||
|
# stack (GnuTLS DANE support); only the shared library ships in the image. Both
|
||||||
|
# vulnerabilities require operating a live Unbound recursive DNSSEC validator
|
||||||
|
# that processes attacker-influenced DNS responses. Prowler never starts an
|
||||||
|
# Unbound resolver, so neither code path is reachable. No Debian bookworm fix yet.
|
||||||
|
CVE-2026-33278 pkg:libunbound8 exp:2026-08-15
|
||||||
|
CVE-2026-42960 pkg:libunbound8 exp:2026-08-15
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
.envrc
|
.envrc
|
||||||
ui/.env.local
|
ui/.env.local
|
||||||
|
openspec/
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
Use these skills for detailed patterns on-demand:
|
Use these skills for detailed patterns on-demand:
|
||||||
|
|
||||||
### Generic Skills (Any Project)
|
### Generic Skills (Any Project)
|
||||||
|
|
||||||
| Skill | Description | URL |
|
| Skill | Description | URL |
|
||||||
|-------|-------------|-----|
|
|-------|-------------|-----|
|
||||||
| `typescript` | Const types, flat interfaces, utility types | [SKILL.md](skills/typescript/SKILL.md) |
|
| `typescript` | Const types, flat interfaces, utility types | [SKILL.md](skills/typescript/SKILL.md) |
|
||||||
@@ -28,6 +29,7 @@ Use these skills for detailed patterns on-demand:
|
|||||||
| `tdd` | Test-Driven Development workflow | [SKILL.md](skills/tdd/SKILL.md) |
|
| `tdd` | Test-Driven Development workflow | [SKILL.md](skills/tdd/SKILL.md) |
|
||||||
|
|
||||||
### Prowler-Specific Skills
|
### Prowler-Specific Skills
|
||||||
|
|
||||||
| Skill | Description | URL |
|
| Skill | Description | URL |
|
||||||
|-------|-------------|-----|
|
|-------|-------------|-----|
|
||||||
| `prowler` | Project overview, component navigation | [SKILL.md](skills/prowler/SKILL.md) |
|
| `prowler` | Project overview, component navigation | [SKILL.md](skills/prowler/SKILL.md) |
|
||||||
@@ -49,6 +51,7 @@ Use these skills for detailed patterns on-demand:
|
|||||||
| `django-migration-psql` | Django migration best practices for PostgreSQL | [SKILL.md](skills/django-migration-psql/SKILL.md) |
|
| `django-migration-psql` | Django migration best practices for PostgreSQL | [SKILL.md](skills/django-migration-psql/SKILL.md) |
|
||||||
| `postgresql-indexing` | PostgreSQL indexing, EXPLAIN, monitoring, maintenance | [SKILL.md](skills/postgresql-indexing/SKILL.md) |
|
| `postgresql-indexing` | PostgreSQL indexing, EXPLAIN, monitoring, maintenance | [SKILL.md](skills/postgresql-indexing/SKILL.md) |
|
||||||
| `prowler-attack-paths-query` | Create Attack Paths openCypher queries | [SKILL.md](skills/prowler-attack-paths-query/SKILL.md) |
|
| `prowler-attack-paths-query` | Create Attack Paths openCypher queries | [SKILL.md](skills/prowler-attack-paths-query/SKILL.md) |
|
||||||
|
| `prowler-tour` | Keep product-tour definitions aligned with the UI | [SKILL.md](skills/prowler-tour/SKILL.md) |
|
||||||
| `gh-aw` | GitHub Agentic Workflows (gh-aw) | [SKILL.md](skills/gh-aw/SKILL.md) |
|
| `gh-aw` | GitHub Agentic Workflows (gh-aw) | [SKILL.md](skills/gh-aw/SKILL.md) |
|
||||||
| `skill-creator` | Create new AI agent skills | [SKILL.md](skills/skill-creator/SKILL.md) |
|
| `skill-creator` | Create new AI agent skills | [SKILL.md](skills/skill-creator/SKILL.md) |
|
||||||
|
|
||||||
@@ -59,16 +62,19 @@ When performing these actions, ALWAYS invoke the corresponding skill FIRST:
|
|||||||
| Action | Skill |
|
| Action | Skill |
|
||||||
|--------|-------|
|
|--------|-------|
|
||||||
| Add changelog entry for a PR or feature | `prowler-changelog` |
|
| Add changelog entry for a PR or feature | `prowler-changelog` |
|
||||||
|
| Adding ConfigRequirements guardrails to compliance requirements | `prowler-compliance` |
|
||||||
| Adding DRF pagination or permissions | `django-drf` |
|
| Adding DRF pagination or permissions | `django-drf` |
|
||||||
| Adding a compliance output formatter (per-provider class + table dispatcher) | `prowler-compliance` |
|
| Adding a compliance output formatter (per-provider class + table dispatcher) | `prowler-compliance` |
|
||||||
| Adding indexes or constraints to database tables | `django-migration-psql` |
|
| Adding indexes or constraints to database tables | `django-migration-psql` |
|
||||||
| Adding new providers | `prowler-provider` |
|
| Adding new providers | `prowler-provider` |
|
||||||
| Adding privilege escalation detection queries | `prowler-attack-paths-query` |
|
| Adding privilege escalation detection queries | `prowler-attack-paths-query` |
|
||||||
| Adding services to existing providers | `prowler-provider` |
|
| Adding services to existing providers | `prowler-provider` |
|
||||||
|
| Adding, updating, or removing a tour definition (*.tour.ts) | `prowler-tour` |
|
||||||
| After creating/modifying a skill | `skill-sync` |
|
| After creating/modifying a skill | `skill-sync` |
|
||||||
| App Router / Server Actions | `nextjs-16` |
|
| App Router / Server Actions | `nextjs-16` |
|
||||||
| Auditing check-to-requirement mappings as a cloud auditor | `prowler-compliance` |
|
| Auditing check-to-requirement mappings as a cloud auditor | `prowler-compliance` |
|
||||||
| Building AI chat features | `ai-sdk-5` |
|
| Building AI chat features | `ai-sdk-5` |
|
||||||
|
| Changing button labels or section headings on a tour-covered page | `prowler-tour` |
|
||||||
| Committing changes | `prowler-commit` |
|
| Committing changes | `prowler-commit` |
|
||||||
| Configuring MCP servers in agentic workflows | `gh-aw` |
|
| Configuring MCP servers in agentic workflows | `gh-aw` |
|
||||||
| Create PR that requires changelog entry | `prowler-changelog` |
|
| Create PR that requires changelog entry | `prowler-changelog` |
|
||||||
@@ -79,6 +85,7 @@ When performing these actions, ALWAYS invoke the corresponding skill FIRST:
|
|||||||
| Creating ViewSets, serializers, or filters in api/ | `django-drf` |
|
| Creating ViewSets, serializers, or filters in api/ | `django-drf` |
|
||||||
| Creating Zod schemas | `zod-4` |
|
| Creating Zod schemas | `zod-4` |
|
||||||
| Creating a git commit | `prowler-commit` |
|
| Creating a git commit | `prowler-commit` |
|
||||||
|
| Creating a universal (multi-provider) compliance framework | `prowler-compliance` |
|
||||||
| Creating new checks | `prowler-sdk-check` |
|
| Creating new checks | `prowler-sdk-check` |
|
||||||
| Creating new skills | `skill-creator` |
|
| Creating new skills | `skill-creator` |
|
||||||
| Creating or reviewing Django migrations | `django-migration-psql` |
|
| Creating or reviewing Django migrations | `django-migration-psql` |
|
||||||
@@ -87,6 +94,7 @@ When performing these actions, ALWAYS invoke the corresponding skill FIRST:
|
|||||||
| Creating/updating compliance frameworks | `prowler-compliance` |
|
| Creating/updating compliance frameworks | `prowler-compliance` |
|
||||||
| Debug why a GitHub Actions job is failing | `prowler-ci` |
|
| Debug why a GitHub Actions job is failing | `prowler-ci` |
|
||||||
| Debugging gh-aw compilation errors | `gh-aw` |
|
| Debugging gh-aw compilation errors | `gh-aw` |
|
||||||
|
| Editing a UI file containing data-tour-id attributes | `prowler-tour` |
|
||||||
| Fill .github/pull_request_template.md (Context/Description/Steps to review/Checklist) | `prowler-pr` |
|
| Fill .github/pull_request_template.md (Context/Description/Steps to review/Checklist) | `prowler-pr` |
|
||||||
| Fixing bug | `tdd` |
|
| Fixing bug | `tdd` |
|
||||||
| Fixing compliance JSON bugs (duplicate IDs, empty Section, stale refs) | `prowler-compliance` |
|
| Fixing compliance JSON bugs (duplicate IDs, empty Section, stale refs) | `prowler-compliance` |
|
||||||
@@ -103,9 +111,12 @@ When performing these actions, ALWAYS invoke the corresponding skill FIRST:
|
|||||||
| Modifying gh-aw workflow frontmatter or safe-outputs | `gh-aw` |
|
| Modifying gh-aw workflow frontmatter or safe-outputs | `gh-aw` |
|
||||||
| Refactoring code | `tdd` |
|
| Refactoring code | `tdd` |
|
||||||
| Regenerate AGENTS.md Auto-invoke tables (sync.sh) | `skill-sync` |
|
| Regenerate AGENTS.md Auto-invoke tables (sync.sh) | `skill-sync` |
|
||||||
|
| Renaming or removing a data-tour-id attribute value | `prowler-tour` |
|
||||||
|
| Restructuring routes or layouts covered by a tour | `prowler-tour` |
|
||||||
| Review PR requirements: template, title conventions, changelog gate | `prowler-pr` |
|
| Review PR requirements: template, title conventions, changelog gate | `prowler-pr` |
|
||||||
| Review changelog format and conventions | `prowler-changelog` |
|
| Review changelog format and conventions | `prowler-changelog` |
|
||||||
| Reviewing JSON:API compliance | `jsonapi` |
|
| Reviewing JSON:API compliance | `jsonapi` |
|
||||||
|
| Reviewing Prowler UI components | `prowler-ui` |
|
||||||
| Reviewing compliance framework PRs | `prowler-compliance-review` |
|
| Reviewing compliance framework PRs | `prowler-compliance-review` |
|
||||||
| Running makemigrations or pgmakemigrations | `django-migration-psql` |
|
| Running makemigrations or pgmakemigrations | `django-migration-psql` |
|
||||||
| Syncing compliance framework with upstream catalog | `prowler-compliance` |
|
| Syncing compliance framework with upstream catalog | `prowler-compliance` |
|
||||||
|
|||||||
+4
-3
@@ -1,4 +1,4 @@
|
|||||||
# Do you want to learn on how to...
|
# Do you want to learn on how to
|
||||||
|
|
||||||
- [Contribute with your code or fixes to Prowler](https://docs.prowler.com/developer-guide/introduction)
|
- [Contribute with your code or fixes to Prowler](https://docs.prowler.com/developer-guide/introduction)
|
||||||
- [Create a new provider](https://docs.prowler.com/developer-guide/provider)
|
- [Create a new provider](https://docs.prowler.com/developer-guide/provider)
|
||||||
@@ -32,5 +32,6 @@ Provider-specific developer notes:
|
|||||||
|
|
||||||
Want some swag as appreciation for your contribution?
|
Want some swag as appreciation for your contribution?
|
||||||
|
|
||||||
# Prowler Developer Guide
|
## Prowler Developer Guide
|
||||||
https://goto.prowler.com/devguide
|
|
||||||
|
<https://goto.prowler.com/devguide>
|
||||||
|
|||||||
+23
-9
@@ -1,12 +1,14 @@
|
|||||||
FROM python:3.12.11-slim-bookworm@sha256:519591d6871b7bc437060736b9f7456b8731f1499a57e22e6c285135ae657bf7 AS build
|
FROM python:3.12.13-slim-bookworm@sha256:8a7e7cc04fd3e2bd787f7f24e22d5d119aa590d429b50c95dfe12b3abe52f48b AS build
|
||||||
|
|
||||||
LABEL maintainer="https://github.com/prowler-cloud/prowler"
|
LABEL maintainer="https://github.com/prowler-cloud/prowler"
|
||||||
LABEL org.opencontainers.image.source="https://github.com/prowler-cloud/prowler"
|
LABEL org.opencontainers.image.source="https://github.com/prowler-cloud/prowler"
|
||||||
|
|
||||||
ARG POWERSHELL_VERSION=7.5.0
|
ARG POWERSHELL_VERSION=7.5.0
|
||||||
ENV POWERSHELL_VERSION=${POWERSHELL_VERSION}
|
ENV POWERSHELL_VERSION=${POWERSHELL_VERSION}
|
||||||
|
# Opt out of PowerShell telemetry (Application Insights -> dc.services.visualstudio.com)
|
||||||
|
ENV POWERSHELL_TELEMETRY_OPTOUT=1
|
||||||
|
|
||||||
ARG TRIVY_VERSION=0.70.0
|
ARG TRIVY_VERSION=0.71.2
|
||||||
ENV TRIVY_VERSION=${TRIVY_VERSION}
|
ENV TRIVY_VERSION=${TRIVY_VERSION}
|
||||||
|
|
||||||
ARG ZIZMOR_VERSION=1.24.1
|
ARG ZIZMOR_VERSION=1.24.1
|
||||||
@@ -76,11 +78,11 @@ USER prowler
|
|||||||
WORKDIR /home/prowler
|
WORKDIR /home/prowler
|
||||||
|
|
||||||
# Copy necessary files
|
# Copy necessary files
|
||||||
COPY prowler/ /home/prowler/prowler/
|
COPY --chown=prowler:prowler prowler/ /home/prowler/prowler/
|
||||||
COPY dashboard/ /home/prowler/dashboard/
|
COPY --chown=prowler:prowler dashboard/ /home/prowler/dashboard/
|
||||||
COPY pyproject.toml uv.lock /home/prowler/
|
COPY --chown=prowler:prowler pyproject.toml uv.lock /home/prowler/
|
||||||
COPY README.md /home/prowler/
|
COPY --chown=prowler:prowler README.md /home/prowler/
|
||||||
COPY prowler/providers/m365/lib/powershell/m365_powershell.py /home/prowler/prowler/providers/m365/lib/powershell/m365_powershell.py
|
COPY --chown=prowler:prowler prowler/providers/m365/lib/powershell/m365_powershell.py /home/prowler/prowler/providers/m365/lib/powershell/m365_powershell.py
|
||||||
|
|
||||||
# Install Python dependencies
|
# Install Python dependencies
|
||||||
ENV HOME='/home/prowler'
|
ENV HOME='/home/prowler'
|
||||||
@@ -89,15 +91,27 @@ ENV PATH="${HOME}/.local/bin:${PATH}"
|
|||||||
RUN pip install --no-cache-dir --upgrade pip && \
|
RUN pip install --no-cache-dir --upgrade pip && \
|
||||||
pip install --no-cache-dir uv==0.11.14
|
pip install --no-cache-dir uv==0.11.14
|
||||||
|
|
||||||
RUN uv sync --compile-bytecode && \
|
RUN uv sync --locked --compile-bytecode && \
|
||||||
rm -rf ~/.cache/uv
|
rm -rf ~/.cache/uv
|
||||||
|
|
||||||
# Install PowerShell modules
|
# Install PowerShell modules
|
||||||
RUN .venv/bin/python prowler/providers/m365/lib/powershell/m365_powershell.py
|
RUN .venv/bin/python prowler/providers/m365/lib/powershell/m365_powershell.py
|
||||||
|
|
||||||
|
USER root
|
||||||
|
|
||||||
|
# Remove build-only packages from the final image after Python dependencies are installed.
|
||||||
|
RUN apt-get purge -y --auto-remove \
|
||||||
|
build-essential \
|
||||||
|
pkg-config \
|
||||||
|
libzstd-dev \
|
||||||
|
zlib1g-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
USER prowler
|
||||||
|
|
||||||
# Remove deprecated dash dependencies
|
# Remove deprecated dash dependencies
|
||||||
RUN pip uninstall dash-html-components -y && \
|
RUN pip uninstall dash-html-components -y && \
|
||||||
pip uninstall dash-core-components -y
|
pip uninstall dash-core-components -y
|
||||||
|
|
||||||
USER prowler
|
USER prowler
|
||||||
ENTRYPOINT [".venv/bin/prowler"]
|
ENTRYPOINT ["/home/prowler/.venv/bin/prowler"]
|
||||||
|
|||||||
@@ -1,5 +1,34 @@
|
|||||||
.DEFAULT_GOAL:=help
|
.DEFAULT_GOAL:=help
|
||||||
|
|
||||||
|
DEV_LOCAL := ./scripts/development/dev-local.sh
|
||||||
|
|
||||||
|
.PHONY: dev dev-setup dev-attach dev-launch dev-stop dev-clean dev-wipe dev-status
|
||||||
|
|
||||||
|
##@ Local Development
|
||||||
|
dev: ## Start local API, worker, and database logs
|
||||||
|
$(DEV_LOCAL) all
|
||||||
|
|
||||||
|
dev-setup: ## Bootstrap local dependencies, migrations, and fixtures
|
||||||
|
$(DEV_LOCAL) setup
|
||||||
|
|
||||||
|
dev-attach: ## Attach to the local tmux development session
|
||||||
|
$(DEV_LOCAL) attach
|
||||||
|
|
||||||
|
dev-launch: ## Start the local stack on fixed ports and attach
|
||||||
|
$(DEV_LOCAL) launch
|
||||||
|
|
||||||
|
dev-stop: ## Stop the local tmux session and containers
|
||||||
|
$(DEV_LOCAL) kill
|
||||||
|
|
||||||
|
dev-clean: ## Remove stopped local development containers
|
||||||
|
$(DEV_LOCAL) clean
|
||||||
|
|
||||||
|
dev-wipe: ## Stop everything and delete local development data
|
||||||
|
$(DEV_LOCAL) wipe
|
||||||
|
|
||||||
|
dev-status: ## Show local development container status
|
||||||
|
$(DEV_LOCAL) status
|
||||||
|
|
||||||
##@ Testing
|
##@ Testing
|
||||||
test: ## Test with pytest
|
test: ## Test with pytest
|
||||||
rm -rf .coverage && \
|
rm -rf .coverage && \
|
||||||
@@ -16,18 +45,41 @@ coverage-html: ## Show Test Coverage
|
|||||||
coverage html && \
|
coverage html && \
|
||||||
open htmlcov/index.html
|
open htmlcov/index.html
|
||||||
|
|
||||||
##@ Linting
|
##@ Code Quality
|
||||||
format: ## Format Code
|
# `make` is the single entrypoint and mirrors CI exactly (uv run + same flags):
|
||||||
@echo "Running black..."
|
# SDK (prowler/, util/) -> flake8 + black + pylint
|
||||||
black .
|
# API & MCP server -> ruff (rules live in each project's pyproject.toml)
|
||||||
|
# `format` applies fixes (incl. ruff's import/upgrade autofixes); `lint` only
|
||||||
|
# verifies and is what CI gates on.
|
||||||
|
.PHONY: format format-sdk format-api format-mcp lint lint-sdk lint-api lint-mcp
|
||||||
|
|
||||||
lint: ## Lint Code
|
format: format-sdk format-api format-mcp ## Format & autofix all components (SDK, API, MCP)
|
||||||
@echo "Running flake8..."
|
|
||||||
flake8 . --ignore=E266,W503,E203,E501,W605,E128 --exclude .venv,contrib
|
lint: lint-sdk lint-api lint-mcp ## Lint all components (SDK, API, MCP) — mirrors CI
|
||||||
@echo "Running black... "
|
|
||||||
black --check .
|
format-sdk: ## Format SDK code (black)
|
||||||
@echo "Running pylint..."
|
uv run black --exclude "\.venv|api|ui|skills|mcp_server" .
|
||||||
pylint --disable=W,C,R,E -j 0 prowler util
|
|
||||||
|
lint-sdk: ## Lint SDK code (flake8, black --check, pylint)
|
||||||
|
uv run flake8 . --ignore=E266,W503,E203,E501,W605,E128 --exclude .venv,contrib,ui,api,skills,mcp_server
|
||||||
|
uv run black --exclude "\.venv|api|ui|skills|mcp_server" --check .
|
||||||
|
uv run pylint --disable=W,C,R,E -j 0 -rn -sn prowler/
|
||||||
|
|
||||||
|
format-api: ## Format & autofix API code (ruff)
|
||||||
|
cd api && uv run ruff check . --exclude contrib --fix
|
||||||
|
cd api && uv run ruff format . --exclude contrib
|
||||||
|
|
||||||
|
lint-api: ## Lint API code (ruff check + format --check)
|
||||||
|
cd api && uv run ruff check . --exclude contrib
|
||||||
|
cd api && uv run ruff format --check . --exclude contrib
|
||||||
|
|
||||||
|
format-mcp: ## Format & autofix MCP server code (ruff)
|
||||||
|
cd mcp_server && uv run ruff check . --fix
|
||||||
|
cd mcp_server && uv run ruff format .
|
||||||
|
|
||||||
|
lint-mcp: ## Lint MCP server code (ruff check + format --check)
|
||||||
|
cd mcp_server && uv run ruff check .
|
||||||
|
cd mcp_server && uv run ruff format --check .
|
||||||
|
|
||||||
##@ PyPI
|
##@ PyPI
|
||||||
pypi-clean: ## Delete the distribution files
|
pypi-clean: ## Delete the distribution files
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img align="center" src="https://github.com/prowler-cloud/prowler/blob/master/docs/img/prowler-logo-black.png#gh-light-mode-only" width="50%" height="50%">
|
<img align="center" alt="Prowler logo" src="https://github.com/prowler-cloud/prowler/blob/master/docs/img/prowler-logo-black.png#gh-light-mode-only" width="50%" height="50%">
|
||||||
<img align="center" src="https://github.com/prowler-cloud/prowler/blob/master/docs/img/prowler-logo-white.png#gh-dark-mode-only" width="50%" height="50%">
|
<img align="center" alt="Prowler logo" src="https://github.com/prowler-cloud/prowler/blob/master/docs/img/prowler-logo-white.png#gh-dark-mode-only" width="50%" height="50%">
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<b><i>Prowler</b> is the Open Cloud Security Platform trusted by thousands to automate security and compliance in any cloud environment. With hundreds of ready-to-use checks and compliance frameworks, Prowler delivers real-time, customizable monitoring and seamless integrations, making cloud security simple, scalable, and cost-effective for organizations of any size.
|
<b><i>Prowler</b> is the Open Cloud Security Platform trusted by thousands to automate security and compliance in any cloud environment. With thousands of ready-to-use checks and compliance frameworks, Prowler delivers real-time, customizable monitoring and seamless integrations, making cloud security simple, scalable, and cost-effective for organizations of any size.
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<b>Secure ANY cloud at AI Speed at <a href="https://prowler.com">prowler.com</i></b>
|
<b>The Agentic Cloud Defender</i></b>
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://cloud.prowler.com/sign-up">Try Prowler Cloud</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
@@ -21,9 +24,9 @@
|
|||||||
<a href="https://pypi.python.org/pypi/prowler/"><img alt="Python Version" src="https://img.shields.io/pypi/pyversions/prowler.svg"></a>
|
<a href="https://pypi.python.org/pypi/prowler/"><img alt="Python Version" src="https://img.shields.io/pypi/pyversions/prowler.svg"></a>
|
||||||
<a href="https://pypistats.org/packages/prowler"><img alt="PyPI Downloads" src="https://img.shields.io/pypi/dw/prowler.svg?label=downloads"></a>
|
<a href="https://pypistats.org/packages/prowler"><img alt="PyPI Downloads" src="https://img.shields.io/pypi/dw/prowler.svg?label=downloads"></a>
|
||||||
<a href="https://hub.docker.com/r/toniblyx/prowler"><img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/toniblyx/prowler"></a>
|
<a href="https://hub.docker.com/r/toniblyx/prowler"><img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/toniblyx/prowler"></a>
|
||||||
<a href="https://gallery.ecr.aws/prowler-cloud/prowler"><img width="120" height=19" alt="AWS ECR Gallery" src="https://user-images.githubusercontent.com/3985464/151531396-b6535a68-c907-44eb-95a1-a09508178616.png"></a>
|
<a href="https://gallery.ecr.aws/prowler-cloud/prowler"><img width="120" height="19" alt="AWS ECR Gallery" src="https://user-images.githubusercontent.com/3985464/151531396-b6535a68-c907-44eb-95a1-a09508178616.png"></a>
|
||||||
<a href="https://codecov.io/gh/prowler-cloud/prowler"><img src="https://codecov.io/gh/prowler-cloud/prowler/graph/badge.svg?token=OflBGsdpDl"/></a>
|
<a href="https://codecov.io/gh/prowler-cloud/prowler"><img alt="Codecov coverage" src="https://codecov.io/gh/prowler-cloud/prowler/graph/badge.svg?token=OflBGsdpDl"/></a>
|
||||||
<a href="https://insights.linuxfoundation.org/project/prowler-cloud-prowler"><img src="https://insights.linuxfoundation.org/api/badge/health-score?project=prowler-cloud-prowler"/></a>
|
<a href="https://insights.linuxfoundation.org/project/prowler-cloud-prowler"><img alt="Linux Foundation insights health score" src="https://insights.linuxfoundation.org/api/badge/health-score?project=prowler-cloud-prowler"/></a>
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/prowler-cloud/prowler/releases"><img alt="Version" src="https://img.shields.io/github/v/release/prowler-cloud/prowler"></a>
|
<a href="https://github.com/prowler-cloud/prowler/releases"><img alt="Version" src="https://img.shields.io/github/v/release/prowler-cloud/prowler"></a>
|
||||||
@@ -36,12 +39,12 @@
|
|||||||
</p>
|
</p>
|
||||||
<hr>
|
<hr>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img align="center" src="/docs/img/prowler-cloud.gif" width="100%" height="100%">
|
<img align="center" alt="Prowler Cloud demo" src="/docs/img/prowler-cloud.gif" width="100%" height="100%">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
# Description
|
# Description
|
||||||
|
|
||||||
**Prowler** is the world’s most widely used _Open-Source Cloud Security Platform_ that automates security and compliance across **any cloud environment**. With hundreds of ready-to-use security checks, remediation guidance, and compliance frameworks, Prowler is built to _“Secure ANY Cloud at AI Speed”_. Prowler delivers **AI-driven**, **customizable**, and **easy-to-use** assessments, dashboards, reports, and integrations, making cloud security **simple**, **scalable**, and **cost-effective** for organizations of any size.
|
**Prowler** is the world’s most widely used _Open-Source Cloud Security Platform_ that automates security and compliance across **any cloud environment**. With thousands of ready-to-use security checks, remediation guidance, and compliance frameworks, Prowler is built to _“Secure ANY Cloud at AI Speed”_. Prowler delivers **AI-driven**, **customizable**, and **easy-to-use** assessments, dashboards, reports, and integrations, making cloud security **simple**, **scalable**, and **cost-effective** for organizations of any size.
|
||||||
|
|
||||||
Prowler includes hundreds of built-in controls to ensure compliance with standards and frameworks, including:
|
Prowler includes hundreds of built-in controls to ensure compliance with standards and frameworks, including:
|
||||||
|
|
||||||
@@ -54,16 +57,16 @@ Prowler includes hundreds of built-in controls to ensure compliance with standar
|
|||||||
- **National Security Standards:** ENS (Spanish National Security Scheme) and KISA ISMS-P (Korean)
|
- **National Security Standards:** ENS (Spanish National Security Scheme) and KISA ISMS-P (Korean)
|
||||||
- **Custom Security Frameworks:** Tailored to your needs
|
- **Custom Security Frameworks:** Tailored to your needs
|
||||||
|
|
||||||
## Prowler App / Prowler Cloud
|
## Prowler Cloud & Prowler Local Server
|
||||||
|
|
||||||
Prowler App / [Prowler Cloud](https://cloud.prowler.com/) is a web-based application that simplifies running Prowler across your cloud provider accounts. It provides a user-friendly interface to visualize the results and streamline your security assessments.
|
[Prowler Cloud](https://cloud.prowler.com/sign-up) and Prowler Local Server, its self-hosted open-source version, are web applications that simplify running Prowler across your cloud provider accounts. They provide a user-friendly interface to visualize the results and streamline your security assessments.
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
>For more details, refer to the [Prowler App Documentation](https://docs.prowler.com/projects/prowler-open-source/en/latest/#prowler-app-installation)
|
>For more details, refer to the [Prowler Local Server documentation](https://docs.prowler.com/getting-started/installation/prowler-app)
|
||||||
|
|
||||||
## Prowler CLI
|
## Prowler CLI
|
||||||
|
|
||||||
@@ -73,26 +76,45 @@ prowler <provider>
|
|||||||

|

|
||||||
|
|
||||||
|
|
||||||
## Prowler Dashboard
|
## Prowler Local Dashboard
|
||||||
|
|
||||||
```console
|
```console
|
||||||
prowler dashboard
|
prowler dashboard
|
||||||
```
|
```
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## Attack Paths
|
## Attack Paths
|
||||||
|
|
||||||
Attack Paths automatically extends every completed AWS scan with a Neo4j graph that combines Cartography's cloud inventory with Prowler findings. The feature runs in the API worker after each scan and therefore requires:
|
Attack Paths automatically extends every completed AWS scan with a graph that combines Cartography's cloud inventory with Prowler findings. The feature runs in the API worker after each scan.
|
||||||
|
|
||||||
- An accessible Neo4j instance (the Docker Compose files already ships a `neo4j` service).
|
Two graph backends are supported as the long-lived sink:
|
||||||
- The following environment variables so Django and Celery can connect:
|
|
||||||
|
|
||||||
| Variable | Description | Default |
|
- **Neo4j** (default; the Docker Compose files already ship a `neo4j` service).
|
||||||
| --- | --- | --- |
|
- **Amazon Neptune** (cloud-managed; opt-in).
|
||||||
| `NEO4J_HOST` | Hostname used by the API containers. | `neo4j` |
|
|
||||||
| `NEO4J_PORT` | Bolt port exposed by Neo4j. | `7687` |
|
Select the sink with `ATTACK_PATHS_SINK_DATABASE` (`neo4j` or `neptune`; default `neo4j`).
|
||||||
| `NEO4J_USER` / `NEO4J_PASSWORD` | Credentials with rights to create per-tenant databases. | `neo4j` / `neo4j_password` |
|
|
||||||
|
> Note: Cartography ingestion always uses a temporary Neo4j database, regardless of the configured sink. The `NEO4J_*` variables below must remain set even when `ATTACK_PATHS_SINK_DATABASE=neptune`.
|
||||||
|
|
||||||
|
### Neo4j sink
|
||||||
|
|
||||||
|
| Variable | Description | Default |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `NEO4J_HOST` | Hostname used by the API containers. | `neo4j` |
|
||||||
|
| `NEO4J_PORT` | Bolt port exposed by Neo4j. | `7687` |
|
||||||
|
| `NEO4J_USER` / `NEO4J_PASSWORD` | Credentials with rights to create per-tenant databases. | `neo4j` / `neo4j_password` |
|
||||||
|
|
||||||
|
### Neptune sink
|
||||||
|
|
||||||
|
| Variable | Description | Default |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `NEPTUNE_WRITER_ENDPOINT` | Bolt host for the Neptune writer instance. Required when sink is `neptune`. | _empty_ |
|
||||||
|
| `NEPTUNE_READER_ENDPOINT` | Optional reader endpoint for read-only queries. Falls back to the writer when unset. | _empty_ |
|
||||||
|
| `NEPTUNE_PORT` | Bolt port exposed by Neptune. | `8182` |
|
||||||
|
| `AWS_REGION` | Region the Neptune cluster lives in. Required when sink is `neptune`. | _empty_ |
|
||||||
|
|
||||||
|
Neptune authenticates with SigV4 using the standard boto3 credential chain. The worker's IAM role (or `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`) supplies the credentials. There is no Neptune password variable.
|
||||||
|
|
||||||
Every AWS provider scan will enqueue an Attack Paths ingestion job automatically. Other cloud providers will be added in future iterations.
|
Every AWS provider scan will enqueue an Attack Paths ingestion job automatically. Other cloud providers will be added in future iterations.
|
||||||
|
|
||||||
@@ -102,27 +124,31 @@ Every AWS provider scan will enqueue an Attack Paths ingestion job automatically
|
|||||||
> For the most accurate and up-to-date information about checks, services, frameworks, and categories, visit [**Prowler Hub**](https://hub.prowler.com).
|
> For the most accurate and up-to-date information about checks, services, frameworks, and categories, visit [**Prowler Hub**](https://hub.prowler.com).
|
||||||
|
|
||||||
|
|
||||||
| Provider | Checks | Services | [Compliance Frameworks](https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/compliance/) | [Categories](https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/misc/#categories) | Support | Interface |
|
| Provider | Checks | Services | [Compliance Frameworks](https://docs.prowler.com/user-guide/compliance/tutorials/compliance) | [Categories](https://docs.prowler.com/user-guide/cli/tutorials/misc#categories) | Support | Interface |
|
||||||
|---|---|---|---|---|---|---|
|
|---|---|---|---|---|---|---|
|
||||||
| AWS | 600 | 84 | 44 | 18 | Official | UI, API, CLI |
|
| AWS | 621 | 86 | 47 | 19 | Official | UI, API, CLI |
|
||||||
| Azure | 167 | 22 | 19 | 16 | Official | UI, API, CLI |
|
| Azure | 191 | 22 | 21 | 16 | Official | UI, API, CLI |
|
||||||
| GCP | 102 | 18 | 17 | 12 | Official | UI, API, CLI |
|
| GCP | 109 | 20 | 19 | 12 | Official | UI, API, CLI |
|
||||||
| Kubernetes | 83 | 7 | 7 | 11 | Official | UI, API, CLI |
|
| Kubernetes | 92 | 7 | 8 | 11 | Official | UI, API, CLI |
|
||||||
| GitHub | 24 | 3 | 1 | 5 | Official | UI, API, CLI |
|
| GitHub | 24 | 3 | 2 | 5 | Official | UI, API, CLI |
|
||||||
| M365 | 102 | 10 | 4 | 10 | Official | UI, API, CLI |
|
| M365 | 111 | 10 | 6 | 10 | Official | UI, API, CLI |
|
||||||
| OCI | 51 | 14 | 4 | 10 | Official | UI, API, CLI |
|
| OCI | 52 | 14 | 5 | 10 | Official | UI, API, CLI |
|
||||||
| Alibaba Cloud | 63 | 9 | 4 | 9 | Official | UI, API, CLI |
|
| Alibaba Cloud | 63 | 9 | 6 | 9 | Official | UI, API, CLI |
|
||||||
| Cloudflare | 29 | 3 | 0 | 5 | Official | UI, API, CLI |
|
| Cloudflare | 29 | 3 | 2 | 5 | Official | UI, API, CLI |
|
||||||
| IaC | [See `trivy` docs.](https://trivy.dev/latest/docs/coverage/iac/) | N/A | N/A | N/A | Official | UI, API, CLI |
|
| IaC | [See `trivy` docs.](https://trivy.dev/latest/docs/coverage/iac/) | N/A | N/A | N/A | Official | UI, API, CLI |
|
||||||
| MongoDB Atlas | 10 | 3 | 0 | 8 | Official | UI, API, CLI |
|
| MongoDB Atlas | 10 | 3 | 1 | 8 | Official | UI, API, CLI |
|
||||||
| LLM | [See `promptfoo` docs.](https://www.promptfoo.dev/docs/red-team/plugins/) | N/A | N/A | N/A | Official | CLI |
|
| LLM | [See `promptfoo` docs.](https://www.promptfoo.dev/docs/red-team/plugins/) | N/A | N/A | N/A | Official | CLI |
|
||||||
| Image | N/A | N/A | N/A | N/A | Official | CLI, API |
|
| Image | N/A | N/A | N/A | N/A | Official | UI, API, CLI |
|
||||||
| Google Workspace | 39 | 5 | 2 | 5 | Official | UI, API, CLI |
|
| Google Workspace | 65 | 11 | 3 | 6 | Official | UI, API, CLI |
|
||||||
| OpenStack | 34 | 5 | 0 | 9 | Official | UI, API, CLI |
|
| OpenStack | 34 | 5 | 1 | 9 | Official | UI, API, CLI |
|
||||||
| Vercel | 26 | 6 | 0 | 8 | Official | UI, API, CLI |
|
| Vercel | 26 | 6 | 1 | 8 | Official | UI, API, CLI |
|
||||||
| Okta | 1 | 1 | 0 | 1 | Official | CLI |
|
| Okta | 29 | 8 | 2 | 2 | Official | UI, API, CLI |
|
||||||
| Scaleway [Contact us](https://prowler.com/contact) | 1 | 1 | 0 | 1 | Unofficial | CLI |
|
| Linode [Contact us](https://prowler.com/contact) | 10 | 3 | 1 | 4 | Unofficial | CLI |
|
||||||
| NHN | 6 | 2 | 1 | 0 | Unofficial | CLI |
|
| Huawei Cloud [Contact us](https://prowler.com/contact) | 25 | 10 | 1 | 6 | Unofficial | CLI |
|
||||||
|
| E2E Networks [Contact us](https://prowler.com/contact) | 27 | 6 | 0 | 2 | Unofficial | CLI |
|
||||||
|
| Scaleway [Contact us](https://prowler.com/contact) | 1 | 1 | 1 | 1 | Unofficial | CLI |
|
||||||
|
| StackIT [Contact us](https://prowler.com/contact) | 7 | 2 | 1 | 3 | Unofficial | CLI |
|
||||||
|
| NHN | 6 | 2 | 2 | 0 | Unofficial | CLI |
|
||||||
|
|
||||||
> [!Note]
|
> [!Note]
|
||||||
> The numbers in the table are updated periodically.
|
> The numbers in the table are updated periodically.
|
||||||
@@ -138,19 +164,21 @@ Every AWS provider scan will enqueue an Attack Paths ingestion job automatically
|
|||||||
|
|
||||||
# 💻 Installation
|
# 💻 Installation
|
||||||
|
|
||||||
## Prowler App
|
## Prowler Local Server
|
||||||
|
|
||||||
Prowler App offers flexible installation methods tailored to various environments:
|
Prowler Local Server offers flexible installation methods tailored to various environments:
|
||||||
|
|
||||||
> For detailed instructions on using Prowler App, refer to the [Prowler App Usage Guide](https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/prowler-app/).
|
> For detailed instructions on using Prowler Local Server, refer to the [usage guide](https://docs.prowler.com/user-guide/tutorials/prowler-app).
|
||||||
|
|
||||||
### Docker Compose
|
### Docker Compose
|
||||||
|
|
||||||
**Requirements**
|
#### Requirements
|
||||||
|
|
||||||
* `Docker Compose` installed: https://docs.docker.com/compose/install/.
|
- `Docker Compose` installed: https://docs.docker.com/compose/install/.
|
||||||
|
|
||||||
**Commands**
|
#### Commands
|
||||||
|
|
||||||
|
_macOS/Linux:_
|
||||||
|
|
||||||
``` console
|
``` console
|
||||||
VERSION=$(curl -s https://api.github.com/repos/prowler-cloud/prowler/releases/latest | jq -r .tag_name)
|
VERSION=$(curl -s https://api.github.com/repos/prowler-cloud/prowler/releases/latest | jq -r .tag_name)
|
||||||
@@ -160,10 +188,20 @@ curl -sLO "https://raw.githubusercontent.com/prowler-cloud/prowler/refs/tags/${V
|
|||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
|
_Windows PowerShell:_
|
||||||
|
|
||||||
|
``` powershell
|
||||||
|
$VERSION = (Invoke-RestMethod -Uri "https://api.github.com/repos/prowler-cloud/prowler/releases/latest").tag_name
|
||||||
|
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/prowler-cloud/prowler/refs/tags/$VERSION/docker-compose.yml" -OutFile "docker-compose.yml"
|
||||||
|
# Environment variables can be customized in the .env file. Using default values in production environments is not recommended.
|
||||||
|
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/prowler-cloud/prowler/refs/tags/$VERSION/.env" -OutFile ".env"
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> 🔒 For a secure setup, the API auto-generates a unique key pair, `DJANGO_TOKEN_SIGNING_KEY` and `DJANGO_TOKEN_VERIFYING_KEY`, and stores it in `~/.config/prowler-api` (non-container) or the bound Docker volume in `_data/api` (container). Never commit or reuse static/default keys. To rotate keys, delete the stored key files and restart the API.
|
> 🔒 For a secure setup, the API auto-generates a unique key pair, `DJANGO_TOKEN_SIGNING_KEY` and `DJANGO_TOKEN_VERIFYING_KEY`, and stores it in `~/.config/prowler-api` (non-container) or the bound Docker volume in `_data/api` (container). Never commit or reuse static/default keys. To rotate keys, delete the stored key files and restart the API.
|
||||||
|
|
||||||
Once configured, access the Prowler App at http://localhost:3000. Sign up using your email and password to get started.
|
Once configured, access Prowler Local Server at http://localhost:3000. Sign up using your email and password to get started.
|
||||||
|
|
||||||
### Common Issues with Docker Pull Installation
|
### Common Issues with Docker Pull Installation
|
||||||
|
|
||||||
@@ -175,14 +213,14 @@ You can find more information in the [Troubleshooting](./docs/troubleshooting.md
|
|||||||
|
|
||||||
### From GitHub
|
### From GitHub
|
||||||
|
|
||||||
**Requirements**
|
#### Requirements
|
||||||
|
|
||||||
* `git` installed.
|
- `git` installed.
|
||||||
* `uv` installed: [uv installation](https://docs.astral.sh/uv/getting-started/installation/).
|
- `uv` installed: [uv installation](https://docs.astral.sh/uv/getting-started/installation/).
|
||||||
* `pnpm` installed: [pnpm installation](https://pnpm.io/installation).
|
- `pnpm` installed: [pnpm installation](https://pnpm.io/installation).
|
||||||
* `Docker Compose` installed: https://docs.docker.com/compose/install/.
|
- `Docker Compose` installed: https://docs.docker.com/compose/install/.
|
||||||
|
|
||||||
**Commands to run the API**
|
#### Commands to run the API
|
||||||
|
|
||||||
``` console
|
``` console
|
||||||
git clone https://github.com/prowler-cloud/prowler
|
git clone https://github.com/prowler-cloud/prowler
|
||||||
@@ -199,7 +237,7 @@ gunicorn -c config/guniconf.py config.wsgi:application
|
|||||||
|
|
||||||
> After completing the setup, access the API documentation at http://localhost:8080/api/v1/docs.
|
> After completing the setup, access the API documentation at http://localhost:8080/api/v1/docs.
|
||||||
|
|
||||||
**Commands to run the API Worker**
|
#### Commands to run the API Worker
|
||||||
|
|
||||||
``` console
|
``` console
|
||||||
git clone https://github.com/prowler-cloud/prowler
|
git clone https://github.com/prowler-cloud/prowler
|
||||||
@@ -212,7 +250,7 @@ cd src/backend
|
|||||||
python -m celery -A config.celery worker -l info -E
|
python -m celery -A config.celery worker -l info -E
|
||||||
```
|
```
|
||||||
|
|
||||||
**Commands to run the API Scheduler**
|
#### Commands to run the API Scheduler
|
||||||
|
|
||||||
``` console
|
``` console
|
||||||
git clone https://github.com/prowler-cloud/prowler
|
git clone https://github.com/prowler-cloud/prowler
|
||||||
@@ -225,7 +263,7 @@ cd src/backend
|
|||||||
python -m celery -A config.celery beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler
|
python -m celery -A config.celery beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler
|
||||||
```
|
```
|
||||||
|
|
||||||
**Commands to run the UI**
|
#### Commands to run the UI
|
||||||
|
|
||||||
``` console
|
``` console
|
||||||
git clone https://github.com/prowler-cloud/prowler
|
git clone https://github.com/prowler-cloud/prowler
|
||||||
@@ -235,9 +273,9 @@ pnpm run build
|
|||||||
pnpm start
|
pnpm start
|
||||||
```
|
```
|
||||||
|
|
||||||
> Once configured, access the Prowler App at http://localhost:3000. Sign up using your email and password to get started.
|
> Once configured, access Prowler Local Server at http://localhost:3000. Sign up using your email and password to get started.
|
||||||
|
|
||||||
**Pre-commit Hooks Setup**
|
#### Pre-commit Hooks Setup
|
||||||
|
|
||||||
Some pre-commit hooks require tools installed on your system:
|
Some pre-commit hooks require tools installed on your system:
|
||||||
|
|
||||||
@@ -253,18 +291,18 @@ Prowler CLI is available as a project in [PyPI](https://pypi.org/project/prowler
|
|||||||
pip install prowler
|
pip install prowler
|
||||||
prowler -v
|
prowler -v
|
||||||
```
|
```
|
||||||
>For further guidance, refer to [https://docs.prowler.com](https://docs.prowler.com/projects/prowler-open-source/en/latest/#prowler-cli-installation)
|
>For further guidance, refer to [https://docs.prowler.com](https://docs.prowler.com/getting-started/installation/prowler-cli)
|
||||||
|
|
||||||
### Containers
|
### Containers
|
||||||
|
|
||||||
**Available Versions of Prowler CLI**
|
#### Available Versions of Prowler CLI
|
||||||
|
|
||||||
The following versions of Prowler CLI are available, depending on your requirements:
|
The following versions of Prowler CLI are available, depending on your requirements:
|
||||||
|
|
||||||
- `latest`: Synchronizes with the `master` branch. Note that this version is not stable.
|
- `latest`: Synchronizes with the `master` branch. Note that this version is not stable.
|
||||||
- `v4-latest`: Synchronizes with the `v4` branch. Note that this version is not stable.
|
- `v4-latest`: Synchronizes with the `v4` branch. Note that this version is not stable.
|
||||||
- `v3-latest`: Synchronizes with the `v3` branch. Note that this version is not stable.
|
- `v3-latest`: Synchronizes with the `v3` branch. Note that this version is not stable.
|
||||||
- `<x.y.z>` (release): Stable releases corresponding to specific versions. You can find the complete list of releases [here](https://github.com/prowler-cloud/prowler/releases).
|
- `<x.y.z>` (release): Stable releases corresponding to specific versions. See the [complete list of Prowler releases](https://github.com/prowler-cloud/prowler/releases).
|
||||||
- `stable`: Always points to the latest release.
|
- `stable`: Always points to the latest release.
|
||||||
- `v4-stable`: Always points to the latest release for v4.
|
- `v4-stable`: Always points to the latest release for v4.
|
||||||
- `v3-stable`: Always points to the latest release for v3.
|
- `v3-stable`: Always points to the latest release for v3.
|
||||||
@@ -273,7 +311,7 @@ The container images are available here:
|
|||||||
- Prowler CLI:
|
- Prowler CLI:
|
||||||
- [DockerHub](https://hub.docker.com/r/prowlercloud/prowler/tags)
|
- [DockerHub](https://hub.docker.com/r/prowlercloud/prowler/tags)
|
||||||
- [AWS Public ECR](https://gallery.ecr.aws/prowler-cloud/prowler)
|
- [AWS Public ECR](https://gallery.ecr.aws/prowler-cloud/prowler)
|
||||||
- Prowler App:
|
- Prowler Local Server:
|
||||||
- [DockerHub - Prowler UI](https://hub.docker.com/r/prowlercloud/prowler-ui/tags)
|
- [DockerHub - Prowler UI](https://hub.docker.com/r/prowlercloud/prowler-ui/tags)
|
||||||
- [DockerHub - Prowler API](https://hub.docker.com/r/prowlercloud/prowler-api/tags)
|
- [DockerHub - Prowler API](https://hub.docker.com/r/prowlercloud/prowler-api/tags)
|
||||||
|
|
||||||
@@ -293,7 +331,7 @@ python prowler-cli.py -v
|
|||||||
|
|
||||||
# 🛡️ GitHub Action
|
# 🛡️ GitHub Action
|
||||||
|
|
||||||
The official **Prowler GitHub Action** runs Prowler scans in your GitHub workflows using the official [`prowlercloud/prowler`](https://hub.docker.com/r/prowlercloud/prowler) Docker image. Scans run on any [supported provider](https://docs.prowler.com/user-guide/providers/), with optional [`--push-to-cloud`](https://docs.prowler.com/user-guide/tutorials/prowler-app-import-findings) to send findings to Prowler Cloud and optional SARIF upload so findings show up in the repo's **Security → Code scanning** tab and as inline PR annotations.
|
The official **Prowler GitHub Action** runs Prowler scans in your GitHub workflows using the official [`prowlercloud/prowler`](https://hub.docker.com/r/prowlercloud/prowler) Docker image. Scans run on any [supported provider](https://docs.prowler.com/user-guide/providers/), with optional [`--push-to-cloud`](https://docs.prowler.com/user-guide/tutorials/prowler-import-findings) to send findings to Prowler Cloud and optional SARIF upload so findings show up in the repo's **Security → Code scanning** tab and as inline PR annotations.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
name: Prowler IaC Scan
|
name: Prowler IaC Scan
|
||||||
@@ -323,22 +361,60 @@ Full configuration, per-provider authentication, and SARIF examples: [Prowler Gi
|
|||||||
|
|
||||||
# ✏️ High level architecture
|
# ✏️ High level architecture
|
||||||
|
|
||||||
## Prowler App
|
## Prowler Local Server
|
||||||
**Prowler App** is composed of four key components:
|
**Prowler Local Server** is composed of four key components:
|
||||||
|
|
||||||
- **Prowler UI**: A web-based interface, built with Next.js, providing a user-friendly experience for executing Prowler scans and visualizing results.
|
- **Prowler UI**: A web-based interface, built with Next.js, providing a user-friendly experience for executing Prowler scans and visualizing results.
|
||||||
- **Prowler API**: A backend service, developed with Django REST Framework, responsible for running Prowler scans and storing the generated results.
|
- **Prowler API**: A backend service, developed with Django REST Framework, responsible for running Prowler scans and storing the generated results.
|
||||||
- **Prowler SDK**: A Python SDK designed to extend the functionality of the Prowler CLI for advanced capabilities.
|
- **Prowler SDK**: A Python SDK designed to extend the functionality of the Prowler CLI for advanced capabilities.
|
||||||
- **Prowler MCP Server**: A Model Context Protocol server that provides AI tools for Lighthouse, the AI-powered security assistant. This is a critical dependency for Lighthouse functionality.
|
- **Prowler MCP Server**: A Model Context Protocol server that provides AI tools for Lighthouse, the AI-powered security assistant. This is a critical dependency for Lighthouse functionality.
|
||||||
|
|
||||||

|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
user([User / Security Team])
|
||||||
|
cli([Prowler CLI])
|
||||||
|
|
||||||
<!-- Diagram source: docs/images/products/prowler-app-architecture.mmd — edit there, re-render at https://mermaid.live, and replace the PNG. -->
|
subgraph APP["Prowler Local Server"]
|
||||||
|
ui["Prowler UI<br/>(Next.js)"]
|
||||||
|
api["Prowler API<br/>(Django REST Framework)"]
|
||||||
|
worker["API Worker<br/>(Celery)"]
|
||||||
|
beat["API Scheduler<br/>(Celery Beat)"]
|
||||||
|
mcp["Prowler MCP Server<br/>(Lighthouse AI tools)"]
|
||||||
|
end
|
||||||
|
|
||||||
|
sdk["Prowler SDK<br/>(Python)"]
|
||||||
|
|
||||||
|
subgraph DATA["Data Layer"]
|
||||||
|
pg[("PostgreSQL")]
|
||||||
|
valkey[("Valkey / Redis")]
|
||||||
|
neo4j[("Neo4j")]
|
||||||
|
end
|
||||||
|
|
||||||
|
providers["Providers"]
|
||||||
|
|
||||||
|
user --> ui
|
||||||
|
user --> cli
|
||||||
|
ui -->|REST| api
|
||||||
|
ui -->|MCP HTTP| mcp
|
||||||
|
mcp -->|REST| api
|
||||||
|
api --> pg
|
||||||
|
api --> valkey
|
||||||
|
beat -->|enqueue jobs| valkey
|
||||||
|
valkey -->|dispatch| worker
|
||||||
|
worker --> pg
|
||||||
|
worker -->|Attack Paths| neo4j
|
||||||
|
worker -->|invokes| sdk
|
||||||
|
cli --> sdk
|
||||||
|
|
||||||
|
sdk --> providers
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- Diagram source: docs/images/products/prowler-app-architecture.mmd — keep this inline block, the docs page getting-started/products/prowler-app.mdx, and the .mmd file in sync. -->
|
||||||
|
|
||||||
|
|
||||||
## Prowler CLI
|
## Prowler CLI
|
||||||
|
|
||||||
**Running Prowler**
|
### Running Prowler
|
||||||
|
|
||||||
Prowler can be executed across various environments, offering flexibility to meet your needs. It can be run from:
|
Prowler can be executed across various environments, offering flexibility to meet your needs. It can be run from:
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -22,7 +22,7 @@ inputs:
|
|||||||
required: false
|
required: false
|
||||||
default: json-ocsf
|
default: json-ocsf
|
||||||
push-to-cloud:
|
push-to-cloud:
|
||||||
description: Push scan findings to Prowler Cloud. Requires the PROWLER_CLOUD_API_KEY environment variable. See https://docs.prowler.com/user-guide/tutorials/prowler-app-import-findings#using-the-cli
|
description: Push scan findings to Prowler Cloud. Requires the PROWLER_CLOUD_API_KEY environment variable. See https://docs.prowler.com/user-guide/tutorials/prowler-import-findings#using-the-cli
|
||||||
required: false
|
required: false
|
||||||
default: "false"
|
default: "false"
|
||||||
flags:
|
flags:
|
||||||
@@ -299,7 +299,7 @@ runs:
|
|||||||
echo ""
|
echo ""
|
||||||
echo "**Get started in 3 steps:**"
|
echo "**Get started in 3 steps:**"
|
||||||
echo "1. Create an account at [cloud.prowler.com](https://cloud.prowler.com)"
|
echo "1. Create an account at [cloud.prowler.com](https://cloud.prowler.com)"
|
||||||
echo "2. Generate a Prowler Cloud API key ([docs](https://docs.prowler.com/user-guide/tutorials/prowler-app-import-findings#using-the-cli))"
|
echo "2. Generate a Prowler Cloud API key ([docs](https://docs.prowler.com/user-guide/tutorials/prowler-import-findings#using-the-cli))"
|
||||||
echo "3. Add \`PROWLER_CLOUD_API_KEY\` to your GitHub secrets and set \`push-to-cloud: true\` on this action"
|
echo "3. Add \`PROWLER_CLOUD_API_KEY\` to your GitHub secrets and set \`push-to-cloud: true\` on this action"
|
||||||
echo ""
|
echo ""
|
||||||
echo "See [prowler.com/pricing](https://prowler.com/pricing) for plan details."
|
echo "See [prowler.com/pricing](https://prowler.com/pricing) for plan details."
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ DJANGO_THROTTLE_TOKEN_OBTAIN=50/minute
|
|||||||
# Decide whether to allow Django manage database table partitions
|
# Decide whether to allow Django manage database table partitions
|
||||||
DJANGO_MANAGE_DB_PARTITIONS=[True|False]
|
DJANGO_MANAGE_DB_PARTITIONS=[True|False]
|
||||||
DJANGO_CELERY_DEADLOCK_ATTEMPTS=5
|
DJANGO_CELERY_DEADLOCK_ATTEMPTS=5
|
||||||
|
# Optional: bound Celery's prefork pool size. Unset → Celery uses os.cpu_count().
|
||||||
|
# Useful on Kubernetes nodes with many CPUs where unbounded prefork balloons memory.
|
||||||
|
# DJANGO_CELERY_WORKER_CONCURRENCY=4
|
||||||
DJANGO_BROKER_VISIBILITY_TIMEOUT=86400
|
DJANGO_BROKER_VISIBILITY_TIMEOUT=86400
|
||||||
DJANGO_SENTRY_DSN=
|
DJANGO_SENTRY_DSN=
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -10,7 +10,7 @@
|
|||||||
> - [`jsonapi`](../skills/jsonapi/SKILL.md) - Strict JSON:API v1.1 spec compliance
|
> - [`jsonapi`](../skills/jsonapi/SKILL.md) - Strict JSON:API v1.1 spec compliance
|
||||||
> - [`pytest`](../skills/pytest/SKILL.md) - Generic pytest patterns
|
> - [`pytest`](../skills/pytest/SKILL.md) - Generic pytest patterns
|
||||||
|
|
||||||
### Auto-invoke Skills
|
## Auto-invoke Skills
|
||||||
|
|
||||||
When performing these actions, ALWAYS invoke the corresponding skill FIRST:
|
When performing these actions, ALWAYS invoke the corresponding skill FIRST:
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ When performing these actions, ALWAYS invoke the corresponding skill FIRST:
|
|||||||
## DECISION TREES
|
## DECISION TREES
|
||||||
|
|
||||||
### Serializer Selection
|
### Serializer Selection
|
||||||
```
|
```text
|
||||||
Read → <Model>Serializer
|
Read → <Model>Serializer
|
||||||
Create → <Model>CreateSerializer
|
Create → <Model>CreateSerializer
|
||||||
Update → <Model>UpdateSerializer
|
Update → <Model>UpdateSerializer
|
||||||
@@ -89,7 +89,7 @@ Nested read → <Model>IncludeSerializer
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Task vs View
|
### Task vs View
|
||||||
```
|
```text
|
||||||
< 100ms → View
|
< 100ms → View
|
||||||
> 100ms or external API → Celery task
|
> 100ms or external API → Celery task
|
||||||
Needs retry → Celery task
|
Needs retry → Celery task
|
||||||
@@ -105,7 +105,7 @@ Django 5.1.x | DRF 3.15.x | djangorestframework-jsonapi 7.x | Celery 5.4.x | Pos
|
|||||||
|
|
||||||
## PROJECT STRUCTURE
|
## PROJECT STRUCTURE
|
||||||
|
|
||||||
```
|
```text
|
||||||
api/src/backend/
|
api/src/backend/
|
||||||
├── api/ # Main Django app
|
├── api/ # Main Django app
|
||||||
│ ├── v1/ # API version 1 (views, serializers, urls)
|
│ ├── v1/ # API version 1 (views, serializers, urls)
|
||||||
|
|||||||
+254
-1
@@ -2,6 +2,260 @@
|
|||||||
|
|
||||||
All notable changes to the **Prowler API** are documented in this file.
|
All notable changes to the **Prowler API** are documented in this file.
|
||||||
|
|
||||||
|
<!-- changelog: release notes start -->
|
||||||
|
|
||||||
|
## [1.37.0] (Prowler v5.36.0)
|
||||||
|
|
||||||
|
### 🔄 Changed
|
||||||
|
|
||||||
|
- OCI provider secrets no longer require `region`; legacy `region` input is accepted for backwards compatibility but ignored before storing or scanning [(#11741)](https://github.com/prowler-cloud/prowler/pull/11741)
|
||||||
|
- Compliance overview ingest now runs in a single transaction per scan with a configurable `COPY` batch size (`DJANGO_COMPLIANCE_COPY_BATCH_SIZE`, default 2000), reducing write pressure on the database [(#11875)](https://github.com/prowler-cloud/prowler/pull/11875)
|
||||||
|
|
||||||
|
### 🐞 Fixed
|
||||||
|
|
||||||
|
- Scan findings now recover resources missing from the in-memory cache after resource pre-resolution, preventing valid findings from being skipped [(#12002)](https://github.com/prowler-cloud/prowler/pull/12002)
|
||||||
|
- Tenant-wide integrations that are not attached to any provider, such as Jira, are now visible and manageable by roles with `manage_integrations` and without unlimited visibility [(#12060)](https://github.com/prowler-cloud/prowler/pull/12060)
|
||||||
|
- Output generation now removes the scan's temporary output directory before writing, so a re-run of the task for the same scan (e.g. broker redelivery after a worker is killed mid-run) no longer appends to the previous run's files and duplicates finding rows in the exported CSV and other outputs [(#12097)](https://github.com/prowler-cloud/prowler/pull/12097)
|
||||||
|
|
||||||
|
### 🔐 Security
|
||||||
|
|
||||||
|
- Integration responses no longer disclose providers outside the visibility of the role, including the resources sideloaded through `?include=providers` [(#12060)](https://github.com/prowler-cloud/prowler/pull/12060)
|
||||||
|
- Integration connection checks, Jira issue type lookups and Jira dispatches now resolve the integration through the provider visibility of the role instead of the whole tenant [(#12060)](https://github.com/prowler-cloud/prowler/pull/12060)
|
||||||
|
- Roles without unlimited visibility can no longer attach an integration to providers they cannot see, nor edit or delete an integration bound to them [(#12060)](https://github.com/prowler-cloud/prowler/pull/12060)
|
||||||
|
- Kubernetes kubeconfig validation now rejects legacy `auth-provider.config.cmd-path` command authentication in Prowler Cloud/API [(#12091)](https://github.com/prowler-cloud/prowler/pull/12091)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [1.36.0] (Prowler v5.35.0)
|
||||||
|
|
||||||
|
### 🐞 Fixed
|
||||||
|
|
||||||
|
- `attack-paths-scan-perform` Celery tasks now use the configurable long-task time limits instead of the six-hour defaults [(#12009)](https://github.com/prowler-cloud/prowler/pull/12009)
|
||||||
|
- Attack Paths scans handle provider deletion races cleanly, detect stale tasks after 16 hours, use backend-specific graph synchronization batches, and report exhausted Neptune write retries with the original database error [(#12019)](https://github.com/prowler-cloud/prowler/pull/12019)
|
||||||
|
|
||||||
|
### 🔐 Security
|
||||||
|
|
||||||
|
- Jira integration credentials only accept bare Atlassian site names containing letters, numbers, and hyphens [(#12012)](https://github.com/prowler-cloud/prowler/pull/12012)
|
||||||
|
- Social account linking requires a verified matching email from both the identity provider and the existing user account without sending account connection notifications [(#12013)](https://github.com/prowler-cloud/prowler/pull/12013)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [1.35.0] (Prowler v5.34.0)
|
||||||
|
|
||||||
|
### 🐞 Fixed
|
||||||
|
|
||||||
|
- `rls_transaction` now falls back directly to the primary DB for connection-level mid-query read replica failures via `execute_wrapper`, reducing non-streaming read crashes during replica recovery [(#10379)](https://github.com/prowler-cloud/prowler/pull/10379)
|
||||||
|
- RBAC permission gates now combine permissions from every role assigned to a user in the active tenant [(#11979)](https://github.com/prowler-cloud/prowler/pull/11979)
|
||||||
|
- `attack-paths-cleanup-stale-scans` now retries worker pings and checks recent scan activity before failing scans and removing temporary databases [(#11986)](https://github.com/prowler-cloud/prowler/pull/11986)
|
||||||
|
|
||||||
|
### 🔐 Security
|
||||||
|
|
||||||
|
- User role relationship updates are limited to the active tenant to preserve role assignments in other tenants [(#11903)](https://github.com/prowler-cloud/prowler/pull/11903)
|
||||||
|
- `api` container image removes the unused Debian `libxml2` runtime package and scopes the `CVE-2026-13221` Trivy exception to unaffected Perl 5.36 packages [(#11991)](https://github.com/prowler-cloud/prowler/pull/11991)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [1.34.2] (Prowler v5.33.2)
|
||||||
|
|
||||||
|
### 🐞 Fixed
|
||||||
|
|
||||||
|
- Attack Paths graph mutations now retry transient Neptune concurrency and deadline failures, while Neo4j mutations use managed transaction retries [(#11968)](https://github.com/prowler-cloud/prowler/pull/11968)
|
||||||
|
- Attack Paths scans now use bounded child node identifiers for normalized list values in Neo4j and Neptune, preventing Neo4j RANGE index key size failures [(#11969)](https://github.com/prowler-cloud/prowler/pull/11969)
|
||||||
|
- `scan-summary` aggregation now upserts summaries in deterministic conflict-key order, preventing PostgreSQL deadlocks during concurrent reaggregation [(#11971)](https://github.com/prowler-cloud/prowler/pull/11971)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [1.34.1] (Prowler v5.33.1)
|
||||||
|
|
||||||
|
### 🐞 Fixed
|
||||||
|
|
||||||
|
- Session tokens are rejected after account password updates [(#11914)](https://github.com/prowler-cloud/prowler/pull/11914)
|
||||||
|
- Jira dispatch task results now surface user-facing Jira failure messages [(#11925)](https://github.com/prowler-cloud/prowler/pull/11925)
|
||||||
|
- AWS Attack Paths privilege escalation queries no longer fail on Neo4j with `Aggregation column contains implicit grouping expressions` [(#11939)](https://github.com/prowler-cloud/prowler/pull/11939)
|
||||||
|
|
||||||
|
### 🔐 Security
|
||||||
|
|
||||||
|
- OpenAI-compatible Lighthouse provider base URLs are restricted before connection checks [(#11940)](https://github.com/prowler-cloud/prowler/pull/11940)
|
||||||
|
- `LIGHTHOUSE_AI_OPENAI_COMPATIBLE_ALLOWED_HOSTS` environment variable to allow internal hosts as OpenAI-compatible Lighthouse AI base URLs [(#11942)](https://github.com/prowler-cloud/prowler/pull/11942)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [1.34.0] (Prowler v5.33.0)
|
||||||
|
|
||||||
|
### 🚀 Added
|
||||||
|
|
||||||
|
- Compliance PDF reports no longer require provider credentials: findings are enriched from the provider metadata stored in the database, so reports generate even after the provider secret is deleted or its credentials become invalid [(#11845)](https://github.com/prowler-cloud/prowler/pull/11845)
|
||||||
|
|
||||||
|
### 🐞 Fixed
|
||||||
|
|
||||||
|
- Provider scans now queue behind active provider scans instead of dispatching concurrently, and resource failed-finding counters retry database conflicts with stable row locking [(#11848)](https://github.com/prowler-cloud/prowler/pull/11848)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [1.33.1] (Prowler v5.32.1)
|
||||||
|
|
||||||
|
### 🐞 Fixed
|
||||||
|
|
||||||
|
- Attack Paths: Scan rows now have database defaults for `is_migrated` and `sink_backend` so `scan-perform-scheduled` inserts survive deploy skew [(#11826)](https://github.com/prowler-cloud/prowler/pull/11826)
|
||||||
|
- Invited users now keep their invitation context when completing authentication with Google, GitHub, or SAML, so the invitation is accepted during login [(#11752)](https://github.com/prowler-cloud/prowler/pull/11752)
|
||||||
|
|
||||||
|
### 🔐 Security
|
||||||
|
|
||||||
|
- User profile updates now allow users to update their own account while requiring user-management permissions to update other users in the same tenant [(#11792)](https://github.com/prowler-cloud/prowler/pull/11792)
|
||||||
|
- Kubernetes provider credentials now reject kubeconfigs using `exec` authentication in Prowler Cloud, preventing user-supplied commands from running on Cloud workers [(#11753)](https://github.com/prowler-cloud/prowler/pull/11753)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [1.33.0] (Prowler v5.32.0)
|
||||||
|
|
||||||
|
### 🚀 Added
|
||||||
|
|
||||||
|
- Timestamp precision support for `/api/v1/findings` `inserted_at` and `updated_at` filters [(#11754)](https://github.com/prowler-cloud/prowler/pull/11754)
|
||||||
|
|
||||||
|
### 🔄 Changed
|
||||||
|
|
||||||
|
- Attack Paths: AWS Neptune is now supported as a persistent sink database, selectable via `ATTACK_PATHS_SINK_DATABASE=neptune` (default `neo4j`), Cartography's (bumped to 0.138.1) per-scan ingest database stays on Neo4j [(#11524)](https://github.com/prowler-cloud/prowler/pull/11524)
|
||||||
|
- Attack Paths: Scan task now checks the ingest Neo4j database and configured graph sink before starting graph ingestion [(#11743)](https://github.com/prowler-cloud/prowler/pull/11743)
|
||||||
|
- Disable PowerShell telemetry in the API container image [(#11746)](https://github.com/prowler-cloud/prowler/pull/11746)
|
||||||
|
|
||||||
|
### 🐞 Fixed
|
||||||
|
|
||||||
|
- Attack Paths: Provider graph cleanup now deletes Neo4j and Neptune relationships in directed batches before deleting nodes [(#11755)](https://github.com/prowler-cloud/prowler/pull/11755)
|
||||||
|
- `scan-perform` no longer reports an error when a provider is deleted during a running scan [(#11696)](https://github.com/prowler-cloud/prowler/pull/11696)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [1.32.1] (Prowler v5.31.1)
|
||||||
|
|
||||||
|
### 🐞 Fixed
|
||||||
|
|
||||||
|
- API key auth no longer mutates `TenantAPIKey.objects` during admin DB lookups [(#11686)](https://github.com/prowler-cloud/prowler/pull/11686)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [1.32.0] (Prowler v5.31.0)
|
||||||
|
|
||||||
|
### 🚀 Added
|
||||||
|
|
||||||
|
- Provider group filters for API endpoints that support cloud provider filtering, including exact and `__in` variants [(#11573)](https://github.com/prowler-cloud/prowler/pull/11573)
|
||||||
|
- Provider filters for `GET /api/v1/compliance-overviews`, `/metadata`, and `/requirements`, using latest completed scans per matching provider [(#11587)](https://github.com/prowler-cloud/prowler/pull/11587)
|
||||||
|
- Server-Sent Events (SSE) infrastructure for the API: a base viewset, a tenant-aware channel manager, and channel-name helpers backed by `django-eventstream` over Valkey Pub/Sub and served through the Gunicorn ASGI worker, so feature endpoints can stream events to clients over a single long-lived connection [(#11556)](https://github.com/prowler-cloud/prowler/pull/11556)
|
||||||
|
- `DJANGO_CELERY_WORKER_CONCURRENCY` to configure Celery workers concurrency. Unset for default behaviour [(#11075)](https://github.com/prowler-cloud/prowler/pull/11075)
|
||||||
|
|
||||||
|
### 🔄 Changed
|
||||||
|
|
||||||
|
- Gunicorn worker timeout raised from the 30s default to 120s, so long-running requests are no longer killed prematurely [(#11631)](https://github.com/prowler-cloud/prowler/pull/11631)
|
||||||
|
- Sentry now drops ASGI's `RequestAborted` errors from health-check probe disconnects on `/health/live` [(#11632)](https://github.com/prowler-cloud/prowler/pull/11632)
|
||||||
|
- Gunicorn keep-alive timeout now exceeds the load balancer idle timeout, stopping 502s from reused connections [(#11647)](https://github.com/prowler-cloud/prowler/pull/11647)
|
||||||
|
- API runs under the Uvicorn worker so keep-alive outlives the load balancer idle timeout, fixing Gunicorn's intermittent 502s [(#11663)](https://github.com/prowler-cloud/prowler/pull/11663)
|
||||||
|
- SAML logins no longer wipe a user's roles when the IdP does not send the `userType` attribute; existing roles are kept, and when `userType` names a role that does not exist it is now created with read-only access (visibility over all providers, no management permissions) instead of no permissions at all [(#11520)](https://github.com/prowler-cloud/prowler/pull/11520)
|
||||||
|
|
||||||
|
### 🐞 Fixed
|
||||||
|
|
||||||
|
- Database connections no longer leak under the ASGI worker, which previously exhausted the read replica's connection slots and caused 500s on read endpoints [(#11640)](https://github.com/prowler-cloud/prowler/pull/11640)
|
||||||
|
|
||||||
|
### 🔐 Security
|
||||||
|
|
||||||
|
- `aiohttp` to 3.14.0 and `idna` to 3.15, patching known CVEs [(#11596)](https://github.com/prowler-cloud/prowler/pull/11596)
|
||||||
|
- Container base image to `python:3.12.13-slim-bookworm` and `trivy` to 0.71.0, patching OS and Go module CVEs [(#11596)](https://github.com/prowler-cloud/prowler/pull/11596)
|
||||||
|
- `trivy` binary bumped to 0.71.0 patching embedded `golang.org/x/crypto`, `golang.org/x/net`, and Go `stdlib` CVEs [(#11592)](https://github.com/prowler-cloud/prowler/pull/11592)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [1.31.3] (Prowler v5.30.3)
|
||||||
|
|
||||||
|
### 🔐 Security
|
||||||
|
|
||||||
|
- SAML logins now link to an existing account only when the asserted email domain matches the ACS endpoint and the user is already a member of that domain's tenant, fixing a cross-tenant account takeover [(GHSA-h8m9-jgf8-vwvp)](https://github.com/prowler-cloud/prowler/security/advisories/GHSA-h8m9-jgf8-vwvp)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [1.31.2] (Prowler v5.30.2)
|
||||||
|
|
||||||
|
### 🔄 Changed
|
||||||
|
|
||||||
|
- `scan-compliance-overviews` task now streams the findings aggregation and the requirement-row writes so it runs faster and its peak memory no longer grows with the number of regions and frameworks [(#11591)](https://github.com/prowler-cloud/prowler/pull/11591)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [1.31.1] (Prowler v5.30.1)
|
||||||
|
|
||||||
|
### 🐞 Fixed
|
||||||
|
|
||||||
|
- `compliance-overviews/attributes` now resolves the provider from the scan, so multi-provider universal frameworks (e.g. CSA CCM) return the check IDs of the scan's provider and Azure/GCP requirement details show their findings instead of appearing empty [(#11546)](https://github.com/prowler-cloud/prowler/pull/11546)
|
||||||
|
- Attack Paths: `drop_subgraph` now deletes relationships first and then nodes in batches, using less memory on Neo4j when clearing a dense provider graph [(#11557)](https://github.com/prowler-cloud/prowler/pull/11557)
|
||||||
|
- OCI scans now use API key credentials with the configured region instead of falling back to `/home/prowler/.oci/config` [(#11558)](https://github.com/prowler-cloud/prowler/pull/11558)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [1.31.0] (Prowler v5.30.0)
|
||||||
|
|
||||||
|
### 🚀 Added
|
||||||
|
|
||||||
|
- Opt-in automatic recovery of allowlisted idempotent background tasks whose worker died during a deploy or crash: when enabled via `DJANGO_TASK_RECOVERY_ENABLED` (off by default), stuck summary and deletion tasks are detected and re-run instead of staying pending forever (scan and Jira tasks are excluded), with a `reconcile_orphan_tasks` management command for on-demand recovery [(#11416)](https://github.com/prowler-cloud/prowler/pull/11416)
|
||||||
|
- DORA compliance framework support [(#11131)](https://github.com/prowler-cloud/prowler/pull/11131)
|
||||||
|
- Label Postgres connections with `application_name="<component>:<alias>"` (component injected per process via `DJANGO_APP_COMPONENT`) so connections are attributable by component in `pg_stat_activity` [(#11494)](https://github.com/prowler-cloud/prowler/pull/11494)
|
||||||
|
- DISA Okta IDaaS STIG V1R2 compliance framework export support for the Okta provider [(#11428)](https://github.com/prowler-cloud/prowler/pull/11428)
|
||||||
|
|
||||||
|
### 🔄 Changed
|
||||||
|
|
||||||
|
- Allowlisted idempotent background tasks are no longer lost when a worker is stopped or crashes mid-task; tasks with external side effects are marked terminal instead of blindly re-running [(#11416)](https://github.com/prowler-cloud/prowler/pull/11416)
|
||||||
|
|
||||||
|
### 🐞 Fixed
|
||||||
|
|
||||||
|
- Workers now shut down gracefully on deploy or restart, finishing or re-queueing in-flight tasks instead of being force-killed and leaving them stuck [(#11416)](https://github.com/prowler-cloud/prowler/pull/11416)
|
||||||
|
- Resource `name` is now stored and refreshed on every scan, so resources no longer keep an empty name [(#11476)](https://github.com/prowler-cloud/prowler/pull/11476)
|
||||||
|
- Compliance catalog now warms in background during startup. `compliance-overviews/attributes` returns `503` while warming, so the first request after a deploy no longer trips the API timeout [(#11530)](https://github.com/prowler-cloud/prowler/pull/11530)
|
||||||
|
|
||||||
|
### 🔐 Security
|
||||||
|
|
||||||
|
- `dulwich` from 0.23.0 to 1.2.5 and `pyjwt` from 2.12.1 to 2.13.0, patching `GHSA-897w-fcg9-f6xj` (arbitrary file write) and `PYSEC-2026-179` (HMAC/JWK key confusion) [(#11499)](https://github.com/prowler-cloud/prowler/pull/11499)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [1.30.3] (Prowler v5.29.3)
|
||||||
|
|
||||||
|
### 🐞 Fixed
|
||||||
|
|
||||||
|
- API startup no longer crashes when Neo4j is unreachable, as the Neo4j driver now connects lazily on first use rather than during app initialization [(#11491)](https://github.com/prowler-cloud/prowler/pull/11491)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [1.30.1] (Prowler v5.29.1)
|
||||||
|
|
||||||
|
### 🐞 Fixed
|
||||||
|
|
||||||
|
- `GET /api/v1/findings` N+1 query loading `resources__tags` when listing findings [(#11420)](https://github.com/prowler-cloud/prowler/pull/11420)
|
||||||
|
- Clean up the scan tmp output directory when `scan-report` fails so partial files do not accumulate and fill the worker disk (`No space left on device`) [(#11421)](https://github.com/prowler-cloud/prowler/pull/11421)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [1.30.0] (Prowler v5.29.0)
|
||||||
|
|
||||||
|
### 🔄 Changed
|
||||||
|
|
||||||
|
- Scan finding ingestion: bulk-resolve `Resource`/`ResourceTag` rows, replace per-mapping `SELECT FOR UPDATE` with deferred `ResourceTagMapping.bulk_create(ignore_conflicts=True)`, wrap each micro-batch in a single `rls_transaction`, and raise `SCAN_DB_BATCH_SIZE` to 1000 [(#11249)](https://github.com/prowler-cloud/prowler/pull/11249)
|
||||||
|
- Faster `GET /api/v1/finding-groups/latest` aggregation on tenants where one recent scan holds most findings [(#11380)](https://github.com/prowler-cloud/prowler/pull/11380)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [1.29.1] (Prowler v5.28.1)
|
||||||
|
|
||||||
|
### 🐞 Fixed
|
||||||
|
|
||||||
|
- `finding-groups` slow response with finding-level filters such as `region`; check title and description are now read from the daily summaries, which drops sorting by `check_title` [(#11326)](https://github.com/prowler-cloud/prowler/pull/11326)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [1.29.0] (Prowler v5.28.0)
|
||||||
|
|
||||||
|
### 🚀 Added
|
||||||
|
|
||||||
|
- `okta` provider support [(#11184)](https://github.com/prowler-cloud/prowler/pull/11184)
|
||||||
|
- `resource.metadata` attribute included in `/api/v1/findings?include=resources` [(#11187)](https://github.com/prowler-cloud/prowler/pull/11187)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [1.28.0] (Prowler v5.27.0)
|
## [1.28.0] (Prowler v5.27.0)
|
||||||
|
|
||||||
### 🚀 Added
|
### 🚀 Added
|
||||||
@@ -20,7 +274,6 @@ All notable changes to the **Prowler API** are documented in this file.
|
|||||||
- `perform_scan_task` and `perform_scheduled_scan_task` now short-circuit with a warning and `return None` when the target provider no longer exists, instead of letting `handle_provider_deletion` raise `ProviderDeletedException`. `perform_scheduled_scan_task` also removes any orphan `PeriodicTask` it finds so beat stops re-firing scans for deleted providers. Prevents queued messages for deleted providers from being recorded as `FAILURE` [(#11185)](https://github.com/prowler-cloud/prowler/pull/11185)
|
- `perform_scan_task` and `perform_scheduled_scan_task` now short-circuit with a warning and `return None` when the target provider no longer exists, instead of letting `handle_provider_deletion` raise `ProviderDeletedException`. `perform_scheduled_scan_task` also removes any orphan `PeriodicTask` it finds so beat stops re-firing scans for deleted providers. Prevents queued messages for deleted providers from being recorded as `FAILURE` [(#11185)](https://github.com/prowler-cloud/prowler/pull/11185)
|
||||||
- Attack Paths: `BEDROCK-001` and `BEDROCK-002` now target roles trusting `bedrock-agentcore.amazonaws.com` instead of `bedrock.amazonaws.com`, eliminating false positives against regular Bedrock service roles (Agents, Knowledge Bases, model invocation) [(#11141)](https://github.com/prowler-cloud/prowler/pull/11141)
|
- Attack Paths: `BEDROCK-001` and `BEDROCK-002` now target roles trusting `bedrock-agentcore.amazonaws.com` instead of `bedrock.amazonaws.com`, eliminating false positives against regular Bedrock service roles (Agents, Knowledge Bases, model invocation) [(#11141)](https://github.com/prowler-cloud/prowler/pull/11141)
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [1.27.1] (Prowler v5.26.1)
|
## [1.27.1] (Prowler v5.26.1)
|
||||||
|
|||||||
+29
-7
@@ -1,11 +1,13 @@
|
|||||||
FROM python:3.12.10-slim-bookworm@sha256:fd95fa221297a88e1cf49c55ec1828edd7c5a428187e67b5d1805692d11588db AS build
|
FROM python:3.12.13-slim-bookworm@sha256:8a7e7cc04fd3e2bd787f7f24e22d5d119aa590d429b50c95dfe12b3abe52f48b AS build
|
||||||
|
|
||||||
LABEL maintainer="https://github.com/prowler-cloud/api"
|
LABEL maintainer="https://github.com/prowler-cloud/api"
|
||||||
|
|
||||||
ARG POWERSHELL_VERSION=7.5.0
|
ARG POWERSHELL_VERSION=7.5.0
|
||||||
ENV POWERSHELL_VERSION=${POWERSHELL_VERSION}
|
ENV POWERSHELL_VERSION=${POWERSHELL_VERSION}
|
||||||
|
# Opt out of PowerShell telemetry (Application Insights -> dc.services.visualstudio.com)
|
||||||
|
ENV POWERSHELL_TELEMETRY_OPTOUT=1
|
||||||
|
|
||||||
ARG TRIVY_VERSION=0.70.0
|
ARG TRIVY_VERSION=0.71.2
|
||||||
ENV TRIVY_VERSION=${TRIVY_VERSION}
|
ENV TRIVY_VERSION=${TRIVY_VERSION}
|
||||||
|
|
||||||
ARG ZIZMOR_VERSION=1.24.1
|
ARG ZIZMOR_VERSION=1.24.1
|
||||||
@@ -89,7 +91,7 @@ WORKDIR /home/prowler
|
|||||||
# Ensure output directory exists
|
# Ensure output directory exists
|
||||||
RUN mkdir -p /tmp/prowler_api_output
|
RUN mkdir -p /tmp/prowler_api_output
|
||||||
|
|
||||||
COPY pyproject.toml uv.lock ./
|
COPY --chown=prowler:prowler pyproject.toml uv.lock ./
|
||||||
|
|
||||||
RUN pip install --no-cache-dir --upgrade pip && \
|
RUN pip install --no-cache-dir --upgrade pip && \
|
||||||
pip install --no-cache-dir uv==0.11.14
|
pip install --no-cache-dir uv==0.11.14
|
||||||
@@ -97,13 +99,33 @@ RUN pip install --no-cache-dir --upgrade pip && \
|
|||||||
ENV PATH="/home/prowler/.local/bin:$PATH"
|
ENV PATH="/home/prowler/.local/bin:$PATH"
|
||||||
|
|
||||||
# Add `--no-install-project` to avoid installing the current project as a package
|
# Add `--no-install-project` to avoid installing the current project as a package
|
||||||
RUN uv sync --no-install-project && \
|
RUN uv sync --locked --no-install-project && \
|
||||||
rm -rf ~/.cache/uv
|
rm -rf ~/.cache/uv
|
||||||
|
|
||||||
RUN .venv/bin/python .venv/lib/python3.12/site-packages/prowler/providers/m365/lib/powershell/m365_powershell.py
|
# Invoked as a module so the base image's Python minor version is not baked
|
||||||
|
# into a site-packages path.
|
||||||
|
RUN .venv/bin/python -m prowler.providers.m365.lib.powershell.m365_powershell
|
||||||
|
|
||||||
COPY src/backend/ ./backend/
|
USER root
|
||||||
COPY docker-entrypoint.sh ./docker-entrypoint.sh
|
|
||||||
|
# Remove build-only packages from the final image after Python dependencies are installed.
|
||||||
|
RUN apt-get purge -y --auto-remove \
|
||||||
|
gcc \
|
||||||
|
g++ \
|
||||||
|
make \
|
||||||
|
libxml2-dev \
|
||||||
|
libxmlsec1-dev \
|
||||||
|
libxmlsec1-openssl \
|
||||||
|
pkg-config \
|
||||||
|
libtool \
|
||||||
|
libxslt1-dev \
|
||||||
|
python3-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
USER prowler
|
||||||
|
|
||||||
|
COPY --chown=prowler:prowler src/backend/ ./backend/
|
||||||
|
COPY --chown=prowler:prowler docker-entrypoint.sh ./docker-entrypoint.sh
|
||||||
|
|
||||||
WORKDIR /home/prowler/backend
|
WORKDIR /home/prowler/backend
|
||||||
|
|
||||||
|
|||||||
+65
-29
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
This repository contains the JSON API and Task Runner components for Prowler, which facilitate a complete backend that interacts with the Prowler SDK and is used by the Prowler UI.
|
This repository contains the JSON API and Task Runner components for Prowler, which facilitate a complete backend that interacts with the Prowler SDK and is used by the Prowler UI.
|
||||||
|
|
||||||
# Components
|
## Components
|
||||||
The Prowler API is composed of the following components:
|
The Prowler API is composed of the following components:
|
||||||
|
|
||||||
- The JSON API, which is an API built with Django Rest Framework.
|
- The JSON API, which is an API built with Django Rest Framework.
|
||||||
@@ -10,13 +10,13 @@ The Prowler API is composed of the following components:
|
|||||||
- The PostgreSQL database, which is used to store the data.
|
- The PostgreSQL database, which is used to store the data.
|
||||||
- The Valkey database, which is an in-memory database which is used as a message broker for the Celery workers.
|
- The Valkey database, which is an in-memory database which is used as a message broker for the Celery workers.
|
||||||
|
|
||||||
## Note about Valkey
|
### Note about Valkey
|
||||||
|
|
||||||
[Valkey](https://valkey.io/) is an open source (BSD) high performance key/value datastore.
|
[Valkey](https://valkey.io/) is an open source (BSD) high performance key/value datastore.
|
||||||
|
|
||||||
Valkey exposes a Redis 7.2 compliant API. Any service that exposes the Redis API can be used with Prowler API.
|
Valkey exposes a Redis 7.2 compliant API. Any service that exposes the Redis API can be used with Prowler API.
|
||||||
|
|
||||||
# Modify environment variables
|
## Modify environment variables
|
||||||
|
|
||||||
Under the root path of the project, you can find a file called `.env`. This file shows all the environment variables that the project uses. You should review it and set the values for the variables you want to change.
|
Under the root path of the project, you can find a file called `.env`. This file shows all the environment variables that the project uses. You should review it and set the values for the variables you want to change.
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ If you don’t set `DJANGO_TOKEN_SIGNING_KEY` or `DJANGO_TOKEN_VERIFYING_KEY`, t
|
|||||||
|
|
||||||
**Important note**: Every Prowler version (or repository branches and tags) could have different variables set in its `.env` file. Please use the `.env` file that corresponds with each version.
|
**Important note**: Every Prowler version (or repository branches and tags) could have different variables set in its `.env` file. Please use the `.env` file that corresponds with each version.
|
||||||
|
|
||||||
## Local deployment
|
### Local deployment
|
||||||
Keep in mind if you export the `.env` file to use it with local deployment that you will have to do it within the context of the virtual environment, not before. Otherwise, variables will not be loaded properly.
|
Keep in mind if you export the `.env` file to use it with local deployment that you will have to do it within the context of the virtual environment, not before. Otherwise, variables will not be loaded properly.
|
||||||
|
|
||||||
To do this, you can run:
|
To do this, you can run:
|
||||||
@@ -34,12 +34,12 @@ set -a
|
|||||||
source .env
|
source .env
|
||||||
```
|
```
|
||||||
|
|
||||||
# 🚀 Production deployment
|
## 🚀 Production deployment
|
||||||
## Docker deployment
|
### Docker deployment
|
||||||
|
|
||||||
This method requires `docker` and `docker compose`.
|
This method requires `docker` and `docker compose`.
|
||||||
|
|
||||||
### Clone the repository
|
#### Clone the repository
|
||||||
|
|
||||||
```console
|
```console
|
||||||
# HTTPS
|
# HTTPS
|
||||||
@@ -50,13 +50,13 @@ git clone git@github.com:prowler-cloud/api.git
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Build the base image
|
#### Build the base image
|
||||||
|
|
||||||
```console
|
```console
|
||||||
docker compose --profile prod build
|
docker compose --profile prod build
|
||||||
```
|
```
|
||||||
|
|
||||||
### Run the production service
|
#### Run the production service
|
||||||
|
|
||||||
This command will start the Django production server and the Celery worker and also the Valkey and PostgreSQL databases.
|
This command will start the Django production server and the Celery worker and also the Valkey and PostgreSQL databases.
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ You can access the server in `http://localhost:8080`.
|
|||||||
|
|
||||||
> **NOTE:** notice how the port is different. When developing using docker, the port will be `8080` to prevent conflicts.
|
> **NOTE:** notice how the port is different. When developing using docker, the port will be `8080` to prevent conflicts.
|
||||||
|
|
||||||
### View the Production Server Logs
|
#### View the Production Server Logs
|
||||||
|
|
||||||
To view the logs for any component (e.g., Django, Celery worker), you can use the following command with a wildcard. This command will follow logs for any container that matches the specified pattern:
|
To view the logs for any component (e.g., Django, Celery worker), you can use the following command with a wildcard. This command will follow logs for any container that matches the specified pattern:
|
||||||
|
|
||||||
@@ -133,13 +133,13 @@ gunicorn -c config/guniconf.py config.wsgi:application
|
|||||||
|
|
||||||
> By default, the Gunicorn server will try to use as many workers as your machine can handle. You can manually change that in the `src/backend/config/guniconf.py` file.
|
> By default, the Gunicorn server will try to use as many workers as your machine can handle. You can manually change that in the `src/backend/config/guniconf.py` file.
|
||||||
|
|
||||||
# 🧪 Development guide
|
## 🧪 Development guide
|
||||||
|
|
||||||
## Local deployment
|
### Local deployment
|
||||||
|
|
||||||
To use this method, you'll need to set up a Python virtual environment (version ">=3.11,<3.13") and keep dependencies updated. Additionally, ensure that `uv` and `docker compose` are installed.
|
To use this method, you'll need to set up a Python virtual environment (version ">=3.11,<3.13") and keep dependencies updated. Additionally, ensure that `uv` and `docker compose` are installed.
|
||||||
|
|
||||||
### Clone the repository
|
#### Clone the repository
|
||||||
|
|
||||||
```console
|
```console
|
||||||
# HTTPS
|
# HTTPS
|
||||||
@@ -150,7 +150,7 @@ git clone git@github.com:prowler-cloud/api.git
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Start the PostgreSQL Database and Valkey
|
#### Start the PostgreSQL Database and Valkey
|
||||||
|
|
||||||
The PostgreSQL database (version 16.3) and Valkey (version 7) are required for the development environment. To make development easier, we have provided a `docker-compose` file that will start these components for you.
|
The PostgreSQL database (version 16.3) and Valkey (version 7) are required for the development environment. To make development easier, we have provided a `docker-compose` file that will start these components for you.
|
||||||
|
|
||||||
@@ -161,7 +161,7 @@ The PostgreSQL database (version 16.3) and Valkey (version 7) are required for t
|
|||||||
docker compose up postgres valkey -d
|
docker compose up postgres valkey -d
|
||||||
```
|
```
|
||||||
|
|
||||||
### Install the Python dependencies
|
#### Install the Python dependencies
|
||||||
|
|
||||||
> You must have uv installed
|
> You must have uv installed
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ docker compose up postgres valkey -d
|
|||||||
uv sync
|
uv sync
|
||||||
```
|
```
|
||||||
|
|
||||||
### Apply migrations
|
#### Apply migrations
|
||||||
|
|
||||||
For migrations, you need to force the `admin` database router. Assuming you have the correct environment variables and Python virtual environment, run:
|
For migrations, you need to force the `admin` database router. Assuming you have the correct environment variables and Python virtual environment, run:
|
||||||
|
|
||||||
@@ -178,7 +178,7 @@ cd src/backend
|
|||||||
python manage.py migrate --database admin
|
python manage.py migrate --database admin
|
||||||
```
|
```
|
||||||
|
|
||||||
### Run the Django development server
|
#### Run the Django development server
|
||||||
|
|
||||||
```console
|
```console
|
||||||
cd src/backend
|
cd src/backend
|
||||||
@@ -188,7 +188,7 @@ python manage.py runserver
|
|||||||
You can access the server in `http://localhost:8000`.
|
You can access the server in `http://localhost:8000`.
|
||||||
All changes in the code will be automatically reloaded in the server.
|
All changes in the code will be automatically reloaded in the server.
|
||||||
|
|
||||||
### Run the Celery worker
|
#### Run the Celery worker
|
||||||
|
|
||||||
```console
|
```console
|
||||||
python -m celery -A config.celery worker -l info -E
|
python -m celery -A config.celery worker -l info -E
|
||||||
@@ -196,11 +196,47 @@ python -m celery -A config.celery worker -l info -E
|
|||||||
|
|
||||||
The Celery worker does not detect and reload changes in the code, so you need to restart it manually when you make changes.
|
The Celery worker does not detect and reload changes in the code, so you need to restart it manually when you make changes.
|
||||||
|
|
||||||
## Docker deployment
|
### Makefile-Assisted Local Deployment
|
||||||
|
|
||||||
|
This method is an additional local development workflow. It does not replace the manual local deployment or the Docker deployment described in this guide.
|
||||||
|
|
||||||
|
PostgreSQL, Valkey, and Neo4j run with Docker Compose, while Django and the Celery worker run natively through `uv`. Additionally, this workflow creates a `tmux` session with panes for the API, worker, and PostgreSQL logs.
|
||||||
|
|
||||||
|
Before using this method, ensure `docker compose`, `tmux`, and `uv` are installed.
|
||||||
|
|
||||||
|
This workflow is designed for macOS and should also work on Linux when Docker, `tmux`, and `uv` are available. Windows requires script changes before it can be supported.
|
||||||
|
|
||||||
|
From the repository root, run:
|
||||||
|
|
||||||
|
```console
|
||||||
|
make dev
|
||||||
|
```
|
||||||
|
|
||||||
|
The API will be available at:
|
||||||
|
|
||||||
|
```console
|
||||||
|
http://localhost:8080/api/v1
|
||||||
|
```
|
||||||
|
|
||||||
|
Use these commands to manage the local stack:
|
||||||
|
|
||||||
|
```console
|
||||||
|
make dev-setup # Bootstrap dependencies, migrations, and fixtures
|
||||||
|
make dev-attach # Attach to the tmux session
|
||||||
|
make dev-launch # Start the stack on fixed ports and attach
|
||||||
|
make dev-stop # Stop the tmux session and containers
|
||||||
|
make dev-clean # Remove stopped development containers
|
||||||
|
make dev-wipe # Stop everything and delete local development data
|
||||||
|
make dev-status # Show development container status
|
||||||
|
```
|
||||||
|
|
||||||
|
This workflow does not start the UI. Start it separately from the `ui/` directory when needed.
|
||||||
|
|
||||||
|
### Docker deployment
|
||||||
|
|
||||||
This method requires `docker` and `docker compose`.
|
This method requires `docker` and `docker compose`.
|
||||||
|
|
||||||
### Clone the repository
|
#### Clone the repository
|
||||||
|
|
||||||
```console
|
```console
|
||||||
# HTTPS
|
# HTTPS
|
||||||
@@ -211,13 +247,13 @@ git clone git@github.com:prowler-cloud/api.git
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Build the base image
|
#### Build the base image
|
||||||
|
|
||||||
```console
|
```console
|
||||||
docker compose --profile dev build
|
docker compose --profile dev build
|
||||||
```
|
```
|
||||||
|
|
||||||
### Run the development service
|
#### Run the development service
|
||||||
|
|
||||||
This command will start the Django development server and the Celery worker and also the Valkey and PostgreSQL databases.
|
This command will start the Django development server and the Celery worker and also the Valkey and PostgreSQL databases.
|
||||||
|
|
||||||
@@ -230,7 +266,7 @@ All changes in the code will be automatically reloaded in the server.
|
|||||||
|
|
||||||
> **NOTE:** notice how the port is different. When developing using docker, the port will be `8080` to prevent conflicts.
|
> **NOTE:** notice how the port is different. When developing using docker, the port will be `8080` to prevent conflicts.
|
||||||
|
|
||||||
### View the development server logs
|
#### View the development server logs
|
||||||
|
|
||||||
To view the logs for any component (e.g., Django, Celery worker), you can use the following command with a wildcard. This command will follow logs for any container that matches the specified pattern:
|
To view the logs for any component (e.g., Django, Celery worker), you can use the following command with a wildcard. This command will follow logs for any container that matches the specified pattern:
|
||||||
|
|
||||||
@@ -238,7 +274,7 @@ To view the logs for any component (e.g., Django, Celery worker), you can use th
|
|||||||
docker logs -f $(docker ps --format "{{.Names}}" | grep 'api-')
|
docker logs -f $(docker ps --format "{{.Names}}" | grep 'api-')
|
||||||
```
|
```
|
||||||
|
|
||||||
## Applying migrations
|
### Applying migrations
|
||||||
|
|
||||||
For migrations, you need to force the `admin` database router. Assuming you have the correct environment variables and Python virtual environment, run:
|
For migrations, you need to force the `admin` database router. Assuming you have the correct environment variables and Python virtual environment, run:
|
||||||
|
|
||||||
@@ -247,7 +283,7 @@ cd src/backend
|
|||||||
uv run python manage.py migrate --database admin
|
uv run python manage.py migrate --database admin
|
||||||
```
|
```
|
||||||
|
|
||||||
## Apply fixtures
|
### Apply fixtures
|
||||||
|
|
||||||
Fixtures are used to populate the database with initial development data.
|
Fixtures are used to populate the database with initial development data.
|
||||||
|
|
||||||
@@ -258,7 +294,7 @@ uv run python manage.py loaddata api/fixtures/0_dev_users.json --database admin
|
|||||||
|
|
||||||
> The default credentials are `dev@prowler.com:Thisisapassword123@` or `dev2@prowler.com:Thisisapassword123@`
|
> The default credentials are `dev@prowler.com:Thisisapassword123@` or `dev2@prowler.com:Thisisapassword123@`
|
||||||
|
|
||||||
## Run tests
|
### Run tests
|
||||||
|
|
||||||
Note that the tests will fail if you use the same `.env` file as the development environment.
|
Note that the tests will fail if you use the same `.env` file as the development environment.
|
||||||
|
|
||||||
@@ -269,7 +305,7 @@ cd src/backend
|
|||||||
uv run pytest
|
uv run pytest
|
||||||
```
|
```
|
||||||
|
|
||||||
# Custom commands
|
## Custom commands
|
||||||
|
|
||||||
Django provides a way to create custom commands that can be run from the command line.
|
Django provides a way to create custom commands that can be run from the command line.
|
||||||
|
|
||||||
@@ -281,7 +317,7 @@ To run a custom command, you need to be in the `prowler/api/src/backend` directo
|
|||||||
uv run python manage.py <command_name>
|
uv run python manage.py <command_name>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Generate dummy data
|
### Generate dummy data
|
||||||
|
|
||||||
```console
|
```console
|
||||||
python manage.py findings --tenant
|
python manage.py findings --tenant
|
||||||
@@ -298,7 +334,7 @@ This command creates, for a given tenant, a provider, scan and a set of findings
|
|||||||
>
|
>
|
||||||
> The last step is required to access the findings details, since the UI needs that to print all the information.
|
> The last step is required to access the findings details, since the UI needs that to print all the information.
|
||||||
|
|
||||||
### Example
|
#### Example
|
||||||
|
|
||||||
```console
|
```console
|
||||||
~/backend $ uv run python manage.py findings --tenant
|
~/backend $ uv run python manage.py findings --tenant
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Changelog fragments
|
||||||
|
|
||||||
|
Each PR adds one small file here instead of editing `CHANGELOG.md` directly, so concurrent PRs never conflict.
|
||||||
|
|
||||||
|
- Filename: `<slug>.<type>.md`, e.g. `my-new-check.added.md` (slug is free-form: letters, digits, `.`, `_`, `-`)
|
||||||
|
- `<type>` is one of: `added`, `changed`, `deprecated`, `removed`, `fixed`, `security`
|
||||||
|
- Content: one line with the changelog entry text, without the PR link and without a trailing period (the PR link is attached automatically at release time)
|
||||||
|
- A PR adds as many fragment files as entries it needs, freely mixing types (one file per entry); same-type entries just use different slugs
|
||||||
|
|
||||||
|
Fragments are compiled into `CHANGELOG.md` when a release is prepared. Full conventions: `skills/prowler-changelog/SKILL.md`.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Attack Paths IAM privilege-escalation queries no longer build an all-nodes × all-resource-items cartesian product, fixing runtime errors and timeouts on accounts with many IAM roles, users, or groups
|
||||||
@@ -21,13 +21,19 @@ apply_fixtures() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
start_dev_server() {
|
start_dev_server() {
|
||||||
echo "Starting the development server..."
|
echo "Starting the development server (Gunicorn ASGI, debug + reload)..."
|
||||||
uv run python manage.py runserver 0.0.0.0:"${DJANGO_PORT:-8080}"
|
# Same server/worker as prod (config.asgi via the native `asgi` worker), so
|
||||||
|
# SSE streams run on the event loop exactly as they do in production. DEBUG is
|
||||||
|
# on so guniconf's `reload = DEBUG` hot-reloads edited code (and flips
|
||||||
|
# `preload_app` off so reload actually takes).
|
||||||
|
export DJANGO_DEBUG="${DJANGO_DEBUG:-True}"
|
||||||
|
export DJANGO_BIND_ADDRESS="${DJANGO_BIND_ADDRESS:-0.0.0.0}"
|
||||||
|
exec uv run gunicorn -c config/guniconf.py config.asgi:application
|
||||||
}
|
}
|
||||||
|
|
||||||
start_prod_server() {
|
start_prod_server() {
|
||||||
echo "Starting the Gunicorn server..."
|
echo "Starting the Gunicorn server..."
|
||||||
uv run gunicorn -c config/guniconf.py config.wsgi:application
|
exec uv run gunicorn -c config/guniconf.py config.asgi:application
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve_worker_hostname() {
|
resolve_worker_hostname() {
|
||||||
@@ -47,7 +53,7 @@ resolve_worker_hostname() {
|
|||||||
|
|
||||||
start_worker() {
|
start_worker() {
|
||||||
echo "Starting the worker..."
|
echo "Starting the worker..."
|
||||||
uv run python -m celery -A config.celery worker \
|
exec uv run python -m celery -A config.celery worker \
|
||||||
-n "$(resolve_worker_hostname)" \
|
-n "$(resolve_worker_hostname)" \
|
||||||
-l "${DJANGO_LOGGING_LEVEL:-info}" \
|
-l "${DJANGO_LOGGING_LEVEL:-info}" \
|
||||||
-Q celery,scans,scan-reports,deletion,backfill,overview,integrations,compliance,attack-paths-scans \
|
-Q celery,scans,scan-reports,deletion,backfill,overview,integrations,compliance,attack-paths-scans \
|
||||||
@@ -56,7 +62,7 @@ start_worker() {
|
|||||||
|
|
||||||
start_worker_beat() {
|
start_worker_beat() {
|
||||||
echo "Starting the worker-beat..."
|
echo "Starting the worker-beat..."
|
||||||
uv run python -m celery -A config.celery beat -l "${DJANGO_LOGGING_LEVEL:-info}" --scheduler django_celery_beat.schedulers:DatabaseScheduler
|
exec uv run python -m celery -A config.celery beat -l "${DJANGO_LOGGING_LEVEL:-info}" --scheduler django_celery_beat.schedulers:DatabaseScheduler
|
||||||
}
|
}
|
||||||
|
|
||||||
manage_db_partitions() {
|
manage_db_partitions() {
|
||||||
@@ -68,6 +74,15 @@ manage_db_partitions() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Identify this process to Postgres (application_name=<component>:<alias>) so
|
||||||
|
# connections are attributable by component in pg_stat_activity. Web tiers
|
||||||
|
# report "api"; everything else uses the launch subcommand.
|
||||||
|
case "$1" in
|
||||||
|
prod|dev) DJANGO_APP_COMPONENT="api" ;;
|
||||||
|
*) DJANGO_APP_COMPONENT="$1" ;;
|
||||||
|
esac
|
||||||
|
export DJANGO_APP_COMPONENT
|
||||||
|
|
||||||
case "$1" in
|
case "$1" in
|
||||||
dev)
|
dev)
|
||||||
apply_migrations
|
apply_migrations
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
# Orphan Celery task recovery
|
||||||
|
|
||||||
|
When a worker is terminated mid-task (a deploy, an OOM kill, a node eviction), the
|
||||||
|
task it was running can be left non-terminal forever: the `TaskResult` stays
|
||||||
|
`STARTED` and nothing re-runs it. This page describes the mechanisms that detect and
|
||||||
|
recover allowlisted idempotent orphans so pending-task alerts do not fire. Scan tasks
|
||||||
|
are not auto-recovered (re-running a scan is not safe to do automatically); the
|
||||||
|
watchdog covers the summary/aggregation and deletion tasks.
|
||||||
|
|
||||||
|
## How recovery works
|
||||||
|
|
||||||
|
1. **Durable delivery.** The broker is configured so a task message is acknowledged
|
||||||
|
only after the task finishes (`task_acks_late`), one task is reserved at a time
|
||||||
|
(`worker_prefetch_multiplier = 1`), and an abruptly-lost worker re-queues its task
|
||||||
|
(`task_reject_on_worker_lost`). On `SIGTERM` the worker is given a soft-shutdown
|
||||||
|
window (`worker_soft_shutdown_timeout`) to finish or re-queue in-flight work
|
||||||
|
before it is force-killed. `scan-perform`, `scan-perform-scheduled` and
|
||||||
|
`integration-jira` opt out of redelivery with `acks_late=False`, so a crash drops
|
||||||
|
them rather than re-running and duplicating findings or Jira issues. Other
|
||||||
|
non-recovered side-effect tasks keep `acks_late=True`, so the broker can still
|
||||||
|
re-deliver them after a worker loss: the S3 upload rebuilds from worker-local files
|
||||||
|
that did not survive the crash and so no-ops, but Security Hub re-reads findings from
|
||||||
|
the DB and re-sends them to AWS.
|
||||||
|
|
||||||
|
2. **Periodic watchdog.** A Beat task, `reconcile-orphan-tasks`, runs every couple of
|
||||||
|
minutes (a `django_celery_beat` periodic task created by migration). For each
|
||||||
|
in-flight task result with an allowlisted idempotent task name, it pings the
|
||||||
|
worker recorded on the task's `TaskResult`:
|
||||||
|
- worker responds -> the task is still running, leave it alone;
|
||||||
|
- worker is gone (and the task started before a short grace window) -> it is a
|
||||||
|
real orphan: the stale task is revoked and marked terminal (clearing the
|
||||||
|
pending/started alert), and the task is re-enqueued from its stored name and
|
||||||
|
kwargs.
|
||||||
|
|
||||||
|
The re-run is safe because only tasks with proven idempotency are allowlisted: the
|
||||||
|
summary/aggregation tasks clear and re-write their own rows, and deletions are
|
||||||
|
idempotent. Scan tasks and external side effects are excluded: re-running a scan is
|
||||||
|
not safe to do automatically, Jira sends would create duplicate issues, the S3
|
||||||
|
upload rebuilds from worker-local files that do not survive a crash, and
|
||||||
|
report/Security Hub recovery is out of scope.
|
||||||
|
|
||||||
|
3. **Recovery cap.** A per-task Valkey counter limits how often the same task is
|
||||||
|
re-enqueued. After `--max-attempts` recoveries (default 3) the orphan is marked
|
||||||
|
terminal instead of re-enqueued, so a task that repeatedly kills its worker cannot
|
||||||
|
loop forever.
|
||||||
|
|
||||||
|
A Postgres advisory lock ensures that, even with multiple API/worker replicas, only
|
||||||
|
one reconciliation runs at a time; the others no-op.
|
||||||
|
|
||||||
|
## On-demand command
|
||||||
|
|
||||||
|
The same logic is available as a management command, useful right after a deploy or
|
||||||
|
for manual intervention:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python manage.py reconcile_orphan_tasks # recover now
|
||||||
|
python manage.py reconcile_orphan_tasks --dry-run # report orphans, change nothing
|
||||||
|
python manage.py reconcile_orphan_tasks --grace-minutes 5 --max-attempts 3
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
All settings have safe defaults; override via environment variables.
|
||||||
|
|
||||||
|
| Env var | Default | Purpose |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `DJANGO_CELERY_WORKER_PREFETCH_MULTIPLIER` | `1` | Tasks reserved per worker process. |
|
||||||
|
| `DJANGO_CELERY_WORKER_CONCURRENCY` | unset | Optional Celery prefork pool size. When unset, Celery uses its CPU-based default. Set this on worker containers to bound idle memory on hosts with many CPUs. |
|
||||||
|
| `DJANGO_CELERY_WORKER_SOFT_SHUTDOWN_TIMEOUT` | `60` | Seconds the worker drains/re-queues on `SIGTERM` before force-kill. |
|
||||||
|
| `DJANGO_CELERY_TASK_TIME_LIMIT` | `21600` (6h) | Hard limit for most tasks; connection checks are capped at 120s. |
|
||||||
|
| `DJANGO_CELERY_TASK_SOFT_TIME_LIMIT` | hard - 600 | Soft limit; raises `SoftTimeLimitExceeded` for cleanup. |
|
||||||
|
| `DJANGO_CELERY_LONG_TASK_TIME_LIMIT` | `172800` (48h) | Hard limit for scans and provider/tenant deletions, which can legitimately run for more than a day. |
|
||||||
|
| `DJANGO_CELERY_LONG_TASK_SOFT_TIME_LIMIT` | long hard - 600 | Soft limit for the long-running tasks above. |
|
||||||
|
| `DJANGO_TASK_RECOVERY_ENABLED` | `false` | Master switch for orphan-task recovery, disabled by default (opt-in); set to `true` to enable. When off, no orphan is detected, marked terminal, or re-enqueued (attack-paths stale cleanup still runs). |
|
||||||
|
| `DJANGO_TASK_RECOVERY_SUMMARIES_ENABLED` | `true` | Auto re-enqueue orphaned scan summary/aggregation tasks. |
|
||||||
|
| `DJANGO_TASK_RECOVERY_DELETIONS_ENABLED` | `true` | Auto re-enqueue orphaned provider/tenant deletion tasks. |
|
||||||
|
|
||||||
|
Recovery is opt-in: with the master flag off (the default) the sweep does nothing.
|
||||||
|
Once enabled, the per-group flags default to on, so every group recovers unless you
|
||||||
|
turn one off; a task whose group flag is off is marked terminal instead of
|
||||||
|
re-enqueued.
|
||||||
|
|
||||||
|
Turning recovery off only disables this watchdog sweep; it does not change Celery's
|
||||||
|
broker-level redelivery (`task_acks_late`/`task_reject_on_worker_lost`), which still
|
||||||
|
re-delivers tasks that keep `acks_late=True` on worker loss, independently of this flag.
|
||||||
|
|
||||||
|
`task_acks_late` and `task_reject_on_worker_lost` are enabled in `config/celery.py`.
|
||||||
|
|
||||||
|
## Deployment requirement
|
||||||
|
|
||||||
|
Two conditions must both hold for the soft shutdown to actually drain work:
|
||||||
|
|
||||||
|
1. **The worker must receive `SIGTERM`.** The container entrypoint `exec`s the
|
||||||
|
Celery process so it runs as PID 1; otherwise `SIGTERM` from `docker stop`/ECS
|
||||||
|
hits the entrypoint shell, never reaches Celery, and the worker is hard-killed
|
||||||
|
(SIGKILL) at the grace deadline without draining. Custom entrypoints must
|
||||||
|
preserve the `exec`.
|
||||||
|
2. **The orchestrator must give the worker enough time** before force-killing it.
|
||||||
|
Set the stop grace period to exceed `DJANGO_CELERY_WORKER_SOFT_SHUTDOWN_TIMEOUT`
|
||||||
|
plus a margin:
|
||||||
|
- **docker-compose:** `stop_grace_period` on the worker services (set to `120s`).
|
||||||
|
- **AWS ECS:** the worker container `stopTimeout` (configured in the deployment
|
||||||
|
repository).
|
||||||
|
|
||||||
|
If either condition is missing, long tasks are still recovered by the watchdog,
|
||||||
|
but they are cut mid-run on every deploy instead of draining.
|
||||||
+60
-22
@@ -14,7 +14,7 @@ dev = [
|
|||||||
"pytest-env==1.1.3",
|
"pytest-env==1.1.3",
|
||||||
"pytest-randomly==3.15.0",
|
"pytest-randomly==3.15.0",
|
||||||
"pytest-xdist==3.6.1",
|
"pytest-xdist==3.6.1",
|
||||||
"ruff==0.5.0",
|
"ruff==0.15.11",
|
||||||
"tqdm==4.67.1",
|
"tqdm==4.67.1",
|
||||||
"vulture==2.14",
|
"vulture==2.14",
|
||||||
"prek==0.3.9"
|
"prek==0.3.9"
|
||||||
@@ -41,7 +41,9 @@ dependencies = [
|
|||||||
"drf-spectacular==0.27.2",
|
"drf-spectacular==0.27.2",
|
||||||
"drf-spectacular-jsonapi==0.5.1",
|
"drf-spectacular-jsonapi==0.5.1",
|
||||||
"defusedxml==0.7.1",
|
"defusedxml==0.7.1",
|
||||||
"gunicorn==23.0.0",
|
"django-eventstream==5.3.3",
|
||||||
|
"gunicorn==26.0.0",
|
||||||
|
"uvloop==0.22.1",
|
||||||
"lxml==6.1.0",
|
"lxml==6.1.0",
|
||||||
"prowler @ git+https://github.com/prowler-cloud/prowler.git@master",
|
"prowler @ git+https://github.com/prowler-cloud/prowler.git@master",
|
||||||
"psycopg2-binary==2.9.9",
|
"psycopg2-binary==2.9.9",
|
||||||
@@ -56,11 +58,12 @@ dependencies = [
|
|||||||
"matplotlib (==3.10.8)",
|
"matplotlib (==3.10.8)",
|
||||||
"reportlab (==4.4.10)",
|
"reportlab (==4.4.10)",
|
||||||
"neo4j (==6.1.0)",
|
"neo4j (==6.1.0)",
|
||||||
"cartography (==0.135.0)",
|
"cartography (==0.138.1)",
|
||||||
"gevent (==25.9.1)",
|
"gevent (==25.9.1)",
|
||||||
"werkzeug (==3.1.7)",
|
"werkzeug (==3.1.7)",
|
||||||
"sqlparse (==0.5.5)",
|
"sqlparse (==0.5.5)",
|
||||||
"fonttools (==4.62.1)"
|
"fonttools (==4.62.1)",
|
||||||
|
"uvicorn-worker (==0.4.0)",
|
||||||
]
|
]
|
||||||
description = "Prowler's API (Django/DRF)"
|
description = "Prowler's API (Django/DRF)"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
@@ -68,7 +71,24 @@ name = "prowler-api"
|
|||||||
package-mode = false
|
package-mode = false
|
||||||
# Needed for the SDK compatibility
|
# Needed for the SDK compatibility
|
||||||
requires-python = ">=3.11,<3.13"
|
requires-python = ">=3.11,<3.13"
|
||||||
version = "1.28.0"
|
version = "1.38.0"
|
||||||
|
|
||||||
|
# Shared ruff baseline (kept in sync with mcp_server/pyproject.toml).
|
||||||
|
# target-version tracks this project's lowest supported Python.
|
||||||
|
[tool.ruff]
|
||||||
|
src = ["src"]
|
||||||
|
target-version = "py311"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
# Defaults (E4/E7/E9, F) plus import sorting, modern-syntax upgrades, and
|
||||||
|
# comprehension lints — all mechanically auto-fixable. flake8-bugbear (B) is a
|
||||||
|
# good next step but needs manual cleanup (e.g. B904 raise-from), so it is left
|
||||||
|
# out of the shared baseline for now.
|
||||||
|
extend-select = [
|
||||||
|
"I", # isort — import ordering (prek's isort hook covers only the SDK)
|
||||||
|
"UP", # pyupgrade — modern syntax for the min supported Python
|
||||||
|
"C4" # flake8-comprehensions
|
||||||
|
]
|
||||||
|
|
||||||
[tool.uv]
|
[tool.uv]
|
||||||
# Transitive pins matching master to avoid silent drift; bump deliberately.
|
# Transitive pins matching master to avoid silent drift; bump deliberately.
|
||||||
@@ -79,7 +99,7 @@ constraint-dependencies = [
|
|||||||
"aiobotocore==2.25.1",
|
"aiobotocore==2.25.1",
|
||||||
"aiofiles==24.1.0",
|
"aiofiles==24.1.0",
|
||||||
"aiohappyeyeballs==2.6.1",
|
"aiohappyeyeballs==2.6.1",
|
||||||
"aiohttp==3.13.5",
|
"aiohttp==3.14.0",
|
||||||
"aioitertools==0.13.0",
|
"aioitertools==0.13.0",
|
||||||
"aiosignal==1.4.0",
|
"aiosignal==1.4.0",
|
||||||
"alibabacloud-actiontrail20200706==2.4.1",
|
"alibabacloud-actiontrail20200706==2.4.1",
|
||||||
@@ -124,9 +144,8 @@ constraint-dependencies = [
|
|||||||
"astroid==3.2.4",
|
"astroid==3.2.4",
|
||||||
"async-timeout==5.0.1",
|
"async-timeout==5.0.1",
|
||||||
"attrs==25.4.0",
|
"attrs==25.4.0",
|
||||||
"authlib==1.6.9",
|
"authlib==1.6.12",
|
||||||
"autopep8==2.3.2",
|
"autopep8==2.3.2",
|
||||||
"awsipranges==0.3.3",
|
|
||||||
"azure-cli-core==2.83.0",
|
"azure-cli-core==2.83.0",
|
||||||
"azure-cli-telemetry==1.1.0",
|
"azure-cli-telemetry==1.1.0",
|
||||||
"azure-common==1.1.28",
|
"azure-common==1.1.28",
|
||||||
@@ -174,7 +193,7 @@ constraint-dependencies = [
|
|||||||
"blinker==1.9.0",
|
"blinker==1.9.0",
|
||||||
"boto3==1.40.61",
|
"boto3==1.40.61",
|
||||||
"botocore==1.40.61",
|
"botocore==1.40.61",
|
||||||
"cartography==0.135.0",
|
"cartography==0.138.1",
|
||||||
"celery==5.6.2",
|
"celery==5.6.2",
|
||||||
"certifi==2026.1.4",
|
"certifi==2026.1.4",
|
||||||
"cffi==2.0.0",
|
"cffi==2.0.0",
|
||||||
@@ -199,7 +218,6 @@ constraint-dependencies = [
|
|||||||
"debugpy==1.8.20",
|
"debugpy==1.8.20",
|
||||||
"decorator==5.2.1",
|
"decorator==5.2.1",
|
||||||
"defusedxml==0.7.1",
|
"defusedxml==0.7.1",
|
||||||
"detect-secrets==1.5.0",
|
|
||||||
"dill==0.4.1",
|
"dill==0.4.1",
|
||||||
"distro==1.9.0",
|
"distro==1.9.0",
|
||||||
"dj-rest-auth==7.0.1",
|
"dj-rest-auth==7.0.1",
|
||||||
@@ -209,6 +227,7 @@ constraint-dependencies = [
|
|||||||
"django-celery-results==2.6.0",
|
"django-celery-results==2.6.0",
|
||||||
"django-cors-headers==4.4.0",
|
"django-cors-headers==4.4.0",
|
||||||
"django-environ==0.11.2",
|
"django-environ==0.11.2",
|
||||||
|
"django-eventstream==5.3.3",
|
||||||
"django-filter==24.3",
|
"django-filter==24.3",
|
||||||
"django-guid==3.5.0",
|
"django-guid==3.5.0",
|
||||||
"django-postgres-extra==2.0.9",
|
"django-postgres-extra==2.0.9",
|
||||||
@@ -226,7 +245,7 @@ constraint-dependencies = [
|
|||||||
"drf-simple-apikey==2.2.1",
|
"drf-simple-apikey==2.2.1",
|
||||||
"drf-spectacular==0.27.2",
|
"drf-spectacular==0.27.2",
|
||||||
"drf-spectacular-jsonapi==0.5.1",
|
"drf-spectacular-jsonapi==0.5.1",
|
||||||
"dulwich==0.23.0",
|
"dulwich==1.2.5",
|
||||||
"duo-client==5.5.0",
|
"duo-client==5.5.0",
|
||||||
"durationpy==0.10",
|
"durationpy==0.10",
|
||||||
"email-validator==2.2.0",
|
"email-validator==2.2.0",
|
||||||
@@ -253,7 +272,7 @@ constraint-dependencies = [
|
|||||||
"grpc-google-iam-v1==0.14.3",
|
"grpc-google-iam-v1==0.14.3",
|
||||||
"grpcio==1.76.0",
|
"grpcio==1.76.0",
|
||||||
"grpcio-status==1.76.0",
|
"grpcio-status==1.76.0",
|
||||||
"gunicorn==23.0.0",
|
"gunicorn==26.0.0",
|
||||||
"h11==0.16.0",
|
"h11==0.16.0",
|
||||||
"h2==4.3.0",
|
"h2==4.3.0",
|
||||||
"hpack==4.1.0",
|
"hpack==4.1.0",
|
||||||
@@ -262,8 +281,8 @@ constraint-dependencies = [
|
|||||||
"httpx==0.28.1",
|
"httpx==0.28.1",
|
||||||
"humanfriendly==10.0",
|
"humanfriendly==10.0",
|
||||||
"hyperframe==6.1.0",
|
"hyperframe==6.1.0",
|
||||||
"iamdata==0.1.202602021",
|
"iamdata==0.1.202605131",
|
||||||
"idna==3.11",
|
"idna==3.15",
|
||||||
"importlib-metadata==8.7.1",
|
"importlib-metadata==8.7.1",
|
||||||
"inflection==0.5.1",
|
"inflection==0.5.1",
|
||||||
"iniconfig==2.3.0",
|
"iniconfig==2.3.0",
|
||||||
@@ -281,6 +300,7 @@ constraint-dependencies = [
|
|||||||
"jsonschema==4.23.0",
|
"jsonschema==4.23.0",
|
||||||
"jsonschema-specifications==2025.9.1",
|
"jsonschema-specifications==2025.9.1",
|
||||||
"keystoneauth1==5.13.0",
|
"keystoneauth1==5.13.0",
|
||||||
|
"kingfisher-bin==1.104.0",
|
||||||
"kiwisolver==1.4.9",
|
"kiwisolver==1.4.9",
|
||||||
"knack==0.11.0",
|
"knack==0.11.0",
|
||||||
"kombu==5.6.2",
|
"kombu==5.6.2",
|
||||||
@@ -315,7 +335,7 @@ constraint-dependencies = [
|
|||||||
"neo4j==6.1.0",
|
"neo4j==6.1.0",
|
||||||
"nest-asyncio==1.6.0",
|
"nest-asyncio==1.6.0",
|
||||||
"nltk==3.9.4",
|
"nltk==3.9.4",
|
||||||
"numpy==2.0.2",
|
"numpy==2.2.6",
|
||||||
"oauthlib==3.3.1",
|
"oauthlib==3.3.1",
|
||||||
"oci==2.169.0",
|
"oci==2.169.0",
|
||||||
"openai==1.109.1",
|
"openai==1.109.1",
|
||||||
@@ -344,7 +364,7 @@ constraint-dependencies = [
|
|||||||
"psutil==7.2.2",
|
"psutil==7.2.2",
|
||||||
"psycopg2-binary==2.9.9",
|
"psycopg2-binary==2.9.9",
|
||||||
"py-deviceid==0.1.1",
|
"py-deviceid==0.1.1",
|
||||||
"py-iam-expand==0.1.0",
|
"py-iam-expand==0.3.0",
|
||||||
"py-ocsf-models==0.8.1",
|
"py-ocsf-models==0.8.1",
|
||||||
"pyasn1==0.6.3",
|
"pyasn1==0.6.3",
|
||||||
"pyasn1-modules==0.4.2",
|
"pyasn1-modules==0.4.2",
|
||||||
@@ -354,7 +374,7 @@ constraint-dependencies = [
|
|||||||
"pydantic-core==2.41.5",
|
"pydantic-core==2.41.5",
|
||||||
"pygithub==2.8.0",
|
"pygithub==2.8.0",
|
||||||
"pygments==2.20.0",
|
"pygments==2.20.0",
|
||||||
"pyjwt==2.12.1",
|
"pyjwt==2.13.0",
|
||||||
"pylint==3.2.5",
|
"pylint==3.2.5",
|
||||||
"pymsalruntime==0.18.1",
|
"pymsalruntime==0.18.1",
|
||||||
"pynacl==1.6.2",
|
"pynacl==1.6.2",
|
||||||
@@ -390,7 +410,7 @@ constraint-dependencies = [
|
|||||||
"rpds-py==0.30.0",
|
"rpds-py==0.30.0",
|
||||||
"rsa==4.9.1",
|
"rsa==4.9.1",
|
||||||
"ruamel-yaml==0.19.1",
|
"ruamel-yaml==0.19.1",
|
||||||
"ruff==0.5.0",
|
"ruff==0.15.11",
|
||||||
"s3transfer==0.14.0",
|
"s3transfer==0.14.0",
|
||||||
"scaleway==2.10.3",
|
"scaleway==2.10.3",
|
||||||
"scaleway-core==2.10.3",
|
"scaleway-core==2.10.3",
|
||||||
@@ -420,12 +440,14 @@ constraint-dependencies = [
|
|||||||
"uritemplate==4.2.0",
|
"uritemplate==4.2.0",
|
||||||
"urllib3==2.7.0",
|
"urllib3==2.7.0",
|
||||||
"uuid6==2024.7.10",
|
"uuid6==2024.7.10",
|
||||||
|
"uvicorn==0.49.0",
|
||||||
|
"uvloop==0.22.1",
|
||||||
"vine==5.1.0",
|
"vine==5.1.0",
|
||||||
"vulture==2.14",
|
"vulture==2.14",
|
||||||
"wcwidth==0.5.3",
|
"wcwidth==0.5.3",
|
||||||
"websocket-client==1.9.0",
|
"websocket-client==1.9.0",
|
||||||
"werkzeug==3.1.7",
|
"werkzeug==3.1.7",
|
||||||
"workos==6.0.4",
|
"workos==6.0.8",
|
||||||
"wrapt==1.17.3",
|
"wrapt==1.17.3",
|
||||||
"xlsxwriter==3.2.9",
|
"xlsxwriter==3.2.9",
|
||||||
"xmlsec==1.3.17",
|
"xmlsec==1.3.17",
|
||||||
@@ -436,14 +458,30 @@ constraint-dependencies = [
|
|||||||
"zope-interface==8.2",
|
"zope-interface==8.2",
|
||||||
"zstd==1.5.7.3"
|
"zstd==1.5.7.3"
|
||||||
]
|
]
|
||||||
# prowler@master needs okta==3.4.2; cartography 0.135.0 declares okta<1.0.0 for an
|
# prowler@master needs okta==3.4.2, but cartography 0.138.1 requires okta<1.0.0.
|
||||||
# integration prowler does not import.
|
# Attack Paths does not ingest Okta today, so override the Cartography
|
||||||
|
# dependency to the Prowler pin.
|
||||||
|
#
|
||||||
|
# prowler@master needs azure-mgmt-containerservice==34.1.0, but cartography
|
||||||
|
# 0.138.1 requires azure-mgmt-containerservice>=41.0.0. Attack Paths does not
|
||||||
|
# ingest Azure today, so override the Cartography dependency to the Prowler pin.
|
||||||
#
|
#
|
||||||
# prowler@master hard-pins microsoft-kiota-abstractions==1.9.2 in [project.dependencies].
|
# prowler@master hard-pins microsoft-kiota-abstractions==1.9.2 in [project.dependencies].
|
||||||
# The microsoft-kiota-http security bump to 1.9.9 (GHSA-7j59-v9qr-6fq9) requires
|
# The microsoft-kiota-http security bump to 1.9.9 (GHSA-7j59-v9qr-6fq9) requires
|
||||||
# microsoft-kiota-abstractions>=1.9.9, which a constraint cannot satisfy against the
|
# microsoft-kiota-abstractions>=1.9.9, which a constraint cannot satisfy against the
|
||||||
# SDK's hard pin; override it to the patched, kiota-aligned version.
|
# SDK's hard pin; override it to the patched, kiota-aligned version.
|
||||||
|
#
|
||||||
|
# prowler@master hard-pins dulwich==0.23.0 and pyjwt==2.12.1 in [project.dependencies].
|
||||||
|
# dulwich 1.2.5 patches GHSA-897w-fcg9-f6xj (arbitrary file write) and pyjwt 2.13.0
|
||||||
|
# patches PYSEC-2026-179 (HMAC/JWK key-confusion); a constraint cannot satisfy these
|
||||||
|
# against the SDK's hard pins, so override them to the patched versions until the SDK
|
||||||
|
# bump propagates to the pinned master rev. pyjwt keeps the [crypto] extra because an
|
||||||
|
# override replaces the whole requirement; bare pyjwt would drop it from the consumers
|
||||||
|
# that request pyjwt[crypto] and leave cryptography (needed for RS256) only transitive.
|
||||||
override-dependencies = [
|
override-dependencies = [
|
||||||
"okta==3.4.2",
|
"okta==3.4.2",
|
||||||
"microsoft-kiota-abstractions==1.9.9"
|
"azure-mgmt-containerservice==34.1.0",
|
||||||
|
"microsoft-kiota-abstractions==1.9.9",
|
||||||
|
"dulwich==1.2.5",
|
||||||
|
"pyjwt[crypto]==2.13.0"
|
||||||
]
|
]
|
||||||
|
|||||||
+112
-26
@@ -1,9 +1,19 @@
|
|||||||
|
from allauth.account.models import EmailAddress
|
||||||
|
from allauth.core.exceptions import ImmediateHttpResponse
|
||||||
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
|
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
|
||||||
from django.db import transaction
|
|
||||||
|
|
||||||
from api.db_router import MainRouter
|
from api.db_router import MainRouter
|
||||||
from api.db_utils import rls_transaction
|
from api.db_utils import rls_transaction
|
||||||
from api.models import Membership, Role, Tenant, User, UserRoleRelationship
|
from api.models import (
|
||||||
|
Membership,
|
||||||
|
Role,
|
||||||
|
SAMLConfiguration,
|
||||||
|
Tenant,
|
||||||
|
User,
|
||||||
|
UserRoleRelationship,
|
||||||
|
)
|
||||||
|
from api.utils import accept_invitation_for_user
|
||||||
|
from django.db import transaction
|
||||||
|
from django.http import HttpResponseForbidden
|
||||||
|
|
||||||
|
|
||||||
class ProwlerSocialAccountAdapter(DefaultSocialAccountAdapter):
|
class ProwlerSocialAccountAdapter(DefaultSocialAccountAdapter):
|
||||||
@@ -14,14 +24,81 @@ class ProwlerSocialAccountAdapter(DefaultSocialAccountAdapter):
|
|||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_invitation_token(request):
|
||||||
|
for source_name in ("data", "POST"):
|
||||||
|
data = getattr(request, source_name, None) or {}
|
||||||
|
if not hasattr(data, "get"):
|
||||||
|
continue
|
||||||
|
invitation_token = data.get("invitation_token")
|
||||||
|
if invitation_token:
|
||||||
|
return invitation_token
|
||||||
|
|
||||||
|
wrapped_request = getattr(request, "_request", None)
|
||||||
|
if wrapped_request and wrapped_request is not request:
|
||||||
|
return ProwlerSocialAccountAdapter._get_invitation_token(wrapped_request)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def pre_social_login(self, request, sociallogin):
|
def pre_social_login(self, request, sociallogin):
|
||||||
# Link existing accounts with the same email address
|
# The provider account is already bound, so no email-based linking is needed.
|
||||||
email = sociallogin.account.extra_data.get("email")
|
if sociallogin.account.pk:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Prefer the normalized email populated by allauth. GitHub can return the
|
||||||
|
# primary email separately from the profile stored in extra_data.
|
||||||
|
email = sociallogin.user.email or sociallogin.account.extra_data.get("email")
|
||||||
if sociallogin.provider.id == "saml":
|
if sociallogin.provider.id == "saml":
|
||||||
|
# For SAML, the asserted NameID email cannot be trusted on its own:
|
||||||
|
# any tenant can claim any email domain in its SAML configuration. To
|
||||||
|
# prevent cross-tenant account takeover (GHSA-h8m9-jgf8-vwvp), only link
|
||||||
|
# the incoming SAML session to an existing account when (1) the email
|
||||||
|
# domain matches the tenant whose ACS endpoint is being used and (2) the
|
||||||
|
# existing user is already a member of that tenant.
|
||||||
email = sociallogin.user.email
|
email = sociallogin.user.email
|
||||||
|
if not email:
|
||||||
|
return
|
||||||
|
|
||||||
|
domain = email.rsplit("@", 1)[-1].lower()
|
||||||
|
resolver_match = getattr(request, "resolver_match", None)
|
||||||
|
organization_slug = (
|
||||||
|
(resolver_match.kwargs or {}).get("organization_slug", "")
|
||||||
|
if resolver_match
|
||||||
|
else ""
|
||||||
|
).lower()
|
||||||
|
# The ACS endpoint is scoped per email domain; reject mismatches so an
|
||||||
|
# attacker cannot replay an assertion through another tenant's endpoint.
|
||||||
|
if organization_slug != domain:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
saml_config = SAMLConfiguration.objects.using(MainRouter.admin_db).get(
|
||||||
|
email_domain=domain
|
||||||
|
)
|
||||||
|
except SAMLConfiguration.DoesNotExist:
|
||||||
|
return
|
||||||
|
|
||||||
|
existing_user = self.get_user_by_email(email)
|
||||||
|
if existing_user and existing_user.is_member_of_tenant(
|
||||||
|
str(saml_config.tenant_id)
|
||||||
|
):
|
||||||
|
sociallogin.connect(request, existing_user)
|
||||||
|
return
|
||||||
|
|
||||||
if email:
|
if email:
|
||||||
existing_user = self.get_user_by_email(email)
|
existing_user = self.get_user_by_email(email)
|
||||||
if existing_user:
|
if existing_user:
|
||||||
|
email_is_verified = EmailAddress.objects.filter(
|
||||||
|
user=existing_user,
|
||||||
|
email__iexact=email,
|
||||||
|
verified=True,
|
||||||
|
).exists()
|
||||||
|
provider_verified_email = any(
|
||||||
|
address.verified and address.email.casefold() == email.casefold()
|
||||||
|
for address in sociallogin.email_addresses
|
||||||
|
)
|
||||||
|
if not email_is_verified or not provider_verified_email:
|
||||||
|
raise ImmediateHttpResponse(HttpResponseForbidden())
|
||||||
sociallogin.connect(request, existing_user)
|
sociallogin.connect(request, existing_user)
|
||||||
|
|
||||||
def save_user(self, request, sociallogin, form=None):
|
def save_user(self, request, sociallogin, form=None):
|
||||||
@@ -42,29 +119,38 @@ class ProwlerSocialAccountAdapter(DefaultSocialAccountAdapter):
|
|||||||
user.name = social_account_name
|
user.name = social_account_name
|
||||||
user.save(using=MainRouter.admin_db)
|
user.save(using=MainRouter.admin_db)
|
||||||
|
|
||||||
tenant = Tenant.objects.using(MainRouter.admin_db).create(
|
invitation_token = self._get_invitation_token(request)
|
||||||
name=f"{user.email.split('@')[0]} default tenant"
|
if invitation_token:
|
||||||
)
|
invitation, _ = accept_invitation_for_user(
|
||||||
with rls_transaction(str(tenant.id)):
|
|
||||||
Membership.objects.using(MainRouter.admin_db).create(
|
|
||||||
user=user, tenant=tenant, role=Membership.RoleChoices.OWNER
|
|
||||||
)
|
|
||||||
role = Role.objects.using(MainRouter.admin_db).create(
|
|
||||||
name="admin",
|
|
||||||
tenant_id=tenant.id,
|
|
||||||
manage_users=True,
|
|
||||||
manage_account=True,
|
|
||||||
manage_billing=True,
|
|
||||||
manage_providers=True,
|
|
||||||
manage_integrations=True,
|
|
||||||
manage_scans=True,
|
|
||||||
unlimited_visibility=True,
|
|
||||||
)
|
|
||||||
UserRoleRelationship.objects.using(MainRouter.admin_db).create(
|
|
||||||
user=user,
|
user=user,
|
||||||
role=role,
|
invitation_token=invitation_token,
|
||||||
tenant_id=tenant.id,
|
|
||||||
)
|
)
|
||||||
|
request.prowler_invitation_token = invitation_token
|
||||||
|
request.prowler_invitation_tenant_id = str(invitation.tenant_id)
|
||||||
|
else:
|
||||||
|
tenant = Tenant.objects.using(MainRouter.admin_db).create(
|
||||||
|
name=f"{user.email.split('@')[0]} default tenant"
|
||||||
|
)
|
||||||
|
with rls_transaction(str(tenant.id)):
|
||||||
|
Membership.objects.using(MainRouter.admin_db).create(
|
||||||
|
user=user, tenant=tenant, role=Membership.RoleChoices.OWNER
|
||||||
|
)
|
||||||
|
role = Role.objects.using(MainRouter.admin_db).create(
|
||||||
|
name="admin",
|
||||||
|
tenant_id=tenant.id,
|
||||||
|
manage_users=True,
|
||||||
|
manage_account=True,
|
||||||
|
manage_billing=True,
|
||||||
|
manage_providers=True,
|
||||||
|
manage_integrations=True,
|
||||||
|
manage_scans=True,
|
||||||
|
unlimited_visibility=True,
|
||||||
|
)
|
||||||
|
UserRoleRelationship.objects.using(MainRouter.admin_db).create(
|
||||||
|
user=user,
|
||||||
|
role=role,
|
||||||
|
tenant_id=tenant.id,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
request.session["saml_user_created"] = str(user.id)
|
request.session["saml_user_created"] = str(user.id)
|
||||||
|
|
||||||
|
|||||||
@@ -28,9 +28,10 @@ class ApiConfig(AppConfig):
|
|||||||
name = "api"
|
name = "api"
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
from api import schema_extensions # noqa: F401
|
from api import (
|
||||||
from api import signals # noqa: F401
|
schema_extensions, # noqa: F401
|
||||||
from api.attack_paths import database as graph_database
|
signals, # noqa: F401
|
||||||
|
)
|
||||||
|
|
||||||
# Generate required cryptographic keys if not present, but only if:
|
# Generate required cryptographic keys if not present, but only if:
|
||||||
# `"manage.py" not in sys.argv[0]`: If an external server (e.g., Gunicorn) is running the app
|
# `"manage.py" not in sys.argv[0]`: If an external server (e.g., Gunicorn) is running the app
|
||||||
@@ -41,38 +42,6 @@ class ApiConfig(AppConfig):
|
|||||||
):
|
):
|
||||||
self._ensure_crypto_keys()
|
self._ensure_crypto_keys()
|
||||||
|
|
||||||
# Commands that don't need Neo4j
|
|
||||||
SKIP_NEO4J_DJANGO_COMMANDS = [
|
|
||||||
"makemigrations",
|
|
||||||
"migrate",
|
|
||||||
"pgpartition",
|
|
||||||
"check",
|
|
||||||
"help",
|
|
||||||
"showmigrations",
|
|
||||||
"check_and_fix_socialaccount_sites_migration",
|
|
||||||
]
|
|
||||||
|
|
||||||
# Skip eager Neo4j init for tests, some Django commands, and Celery (prefork pool: driver must stay lazy, no post_fork hook)
|
|
||||||
if getattr(settings, "TESTING", False) or (
|
|
||||||
len(sys.argv) > 1
|
|
||||||
and (
|
|
||||||
(
|
|
||||||
"manage.py" in sys.argv[0]
|
|
||||||
and sys.argv[1] in SKIP_NEO4J_DJANGO_COMMANDS
|
|
||||||
)
|
|
||||||
or "celery" in sys.argv[0]
|
|
||||||
)
|
|
||||||
):
|
|
||||||
logger.info(
|
|
||||||
"Skipping eager Neo4j init: tests, some Django commands, or Celery prefork pool (driver stays lazy)"
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
graph_database.init_driver()
|
|
||||||
|
|
||||||
# Neo4j driver is initialized at API startup (see api.attack_paths.database)
|
|
||||||
# It remains lazy for Celery workers and selected Django commands
|
|
||||||
|
|
||||||
def _ensure_crypto_keys(self):
|
def _ensure_crypto_keys(self):
|
||||||
"""
|
"""
|
||||||
Orchestrator method that ensures all required cryptographic keys are present.
|
Orchestrator method that ensures all required cryptographic keys are present.
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from api.attack_paths.queries import (
|
|||||||
get_query_by_id,
|
get_query_by_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"AttackPathsQueryDefinition",
|
"AttackPathsQueryDefinition",
|
||||||
"AttackPathsQueryParameterDefinition",
|
"AttackPathsQueryParameterDefinition",
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ Cypher sanitizer for custom (user-supplied) Attack Paths queries.
|
|||||||
Two responsibilities:
|
Two responsibilities:
|
||||||
|
|
||||||
1. **Validation** - reject queries containing SSRF or dangerous procedure
|
1. **Validation** - reject queries containing SSRF or dangerous procedure
|
||||||
patterns (defense-in-depth; the primary control is ``neo4j.READ_ACCESS``).
|
patterns (defense-in-depth; the primary control is `neo4j.READ_ACCESS`).
|
||||||
|
|
||||||
2. **Provider-scoped label injection** - inject a dynamic
|
2. **Provider-scoped label injection** - inject a dynamic
|
||||||
``_Provider_{uuid}`` label into every node pattern so the database can
|
`_Provider_{uuid}` label into every node pattern so the database can
|
||||||
use its native label index for provider isolation.
|
use its native label index for provider isolation.
|
||||||
|
|
||||||
Label-injection pipeline:
|
Label-injection pipeline:
|
||||||
@@ -22,18 +22,16 @@ Label-injection pipeline:
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
from tasks.jobs.attack_paths.config import get_provider_label
|
from tasks.jobs.attack_paths.config import get_provider_label
|
||||||
|
|
||||||
|
|
||||||
# Step 1 - String / comment protection
|
# Step 1 - String / comment protection
|
||||||
# Single combined regex: strings first, then line comments.
|
# Single combined regex: strings first, then line comments
|
||||||
# The regex engine finds the leftmost match, so a string like 'https://prowler.com'
|
# The regex engine finds the leftmost match, so a string like 'https://prowler.com'
|
||||||
# is consumed as a string before the // inside it can match as a comment.
|
# is consumed as a string before the // inside it can match as a comment
|
||||||
_PROTECTED_RE = re.compile(r"'(?:[^'\\]|\\.)*'|\"(?:[^\"\\]|\\.)*\"|//[^\n]*")
|
_PROTECTED_RE = re.compile(r"'(?:[^'\\]|\\.)*'|\"(?:[^\"\\]|\\.)*\"|//[^\n]*")
|
||||||
|
|
||||||
# Step 2 - Clause splitting
|
# Step 2 - Clause splitting
|
||||||
# OPTIONAL MATCH must come before MATCH to avoid partial matching.
|
# `OPTIONAL MATCH` must come before `MATCH` to avoid partial matching
|
||||||
_CLAUSE_RE = re.compile(
|
_CLAUSE_RE = re.compile(
|
||||||
r"\b(OPTIONAL\s+MATCH|MATCH|WHERE|RETURN|WITH|ORDER\s+BY"
|
r"\b(OPTIONAL\s+MATCH|MATCH|WHERE|RETURN|WITH|ORDER\s+BY"
|
||||||
r"|SKIP|LIMIT|UNION|UNWIND|CALL)\b",
|
r"|SKIP|LIMIT|UNION|UNWIND|CALL)\b",
|
||||||
@@ -41,10 +39,10 @@ _CLAUSE_RE = re.compile(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Pass A - Labeled node patterns (all segments)
|
# Pass A - Labeled node patterns (all segments)
|
||||||
# Matches node patterns that have at least one :Label.
|
# Matches node patterns that have at least one `:Label`
|
||||||
# (?<!\w)\( - open paren NOT preceded by a word char (excludes function calls).
|
# `(?<!\w)\(` - open paren NOT preceded by a word char, excludes function calls
|
||||||
# Group 1: optional variable + one or more :Label
|
# Group 1: optional variable + one or more `:Label`
|
||||||
# Group 2: optional {properties} + closing paren
|
# Group 2: optional `{`properties`}` + closing paren
|
||||||
_LABELED_NODE_RE = re.compile(
|
_LABELED_NODE_RE = re.compile(
|
||||||
r"(?<!\w)\("
|
r"(?<!\w)\("
|
||||||
r"("
|
r"("
|
||||||
@@ -57,9 +55,9 @@ _LABELED_NODE_RE = re.compile(
|
|||||||
r")"
|
r")"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Pass B - Bare node patterns (MATCH segments only)
|
# Pass B - Bare node patterns (`MATCH` segments only)
|
||||||
# Matches (identifier) or (identifier {properties}) without any :Label.
|
# Matches (identifier) or (identifier {properties}) without any `:Label`
|
||||||
# Only applied in MATCH/OPTIONAL MATCH segments.
|
# Only applied in `MATCH` / `OPTIONAL MATCH` segments
|
||||||
_BARE_NODE_RE = re.compile(
|
_BARE_NODE_RE = re.compile(
|
||||||
r"(?<!\w)\(" r"(\s*[a-zA-Z_]\w*)" r"(\s*(?:\{[^}]*\})?)" r"\s*\)"
|
r"(?<!\w)\(" r"(\s*[a-zA-Z_]\w*)" r"(\s*(?:\{[^}]*\})?)" r"\s*\)"
|
||||||
)
|
)
|
||||||
@@ -98,6 +96,11 @@ def inject_provider_label(cypher: str, provider_id: str) -> str:
|
|||||||
node pattern.
|
node pattern.
|
||||||
"""
|
"""
|
||||||
label = get_provider_label(provider_id)
|
label = get_provider_label(provider_id)
|
||||||
|
return inject_label(cypher, label)
|
||||||
|
|
||||||
|
|
||||||
|
def inject_label(cypher: str, label: str) -> str:
|
||||||
|
"""Rewrite a Cypher query to append a label to every node pattern."""
|
||||||
|
|
||||||
# Step 1: Protect strings and comments (single pass, leftmost-first)
|
# Step 1: Protect strings and comments (single pass, leftmost-first)
|
||||||
protected: list[str] = []
|
protected: list[str] = []
|
||||||
@@ -136,9 +139,7 @@ def inject_provider_label(cypher: str, provider_id: str) -> str:
|
|||||||
return work
|
return work
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Validation
|
# Validation
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Patterns that indicate SSRF or dangerous procedure calls
|
# Patterns that indicate SSRF or dangerous procedure calls
|
||||||
# Defense-in-depth layer - the primary control is `neo4j.READ_ACCESS`
|
# Defense-in-depth layer - the primary control is `neo4j.READ_ACCESS`
|
||||||
|
|||||||
@@ -1,233 +1,33 @@
|
|||||||
import atexit
|
"""Backwards-compatible facade over the ingest and sink modules.
|
||||||
import logging
|
|
||||||
import threading
|
Historically this module owned a single Neo4j driver used for both the
|
||||||
from contextlib import contextmanager
|
cartography temp database and the per-tenant sink database. The port to AWS
|
||||||
from typing import Any, Iterator
|
Neptune split those roles: the cartography ingest (temp) database is always
|
||||||
|
Neo4j and lives in `api.attack_paths.ingest`; the sink is configurable
|
||||||
|
(Neo4j or Neptune) and lives in `api.attack_paths.sink`. This shim preserves
|
||||||
|
the public API that `tasks/` and `api/v1/views.py` already depend on, and
|
||||||
|
dispatches to the right module by database-name prefix.
|
||||||
|
|
||||||
|
A database name starting with `db-tmp-scan-` is a cartography temp DB and
|
||||||
|
routes to ingest. Everything else routes to the configured sink.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from contextlib import AbstractContextManager
|
||||||
|
from typing import Any
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
import neo4j
|
import neo4j # noqa: F401 - kept for tests that patch api.attack_paths.database.neo4j
|
||||||
import neo4j.exceptions
|
from api.attack_paths import ingest
|
||||||
|
from api.attack_paths import sink as sink_module
|
||||||
from config.env import env
|
from config.env import env
|
||||||
from django.conf import settings
|
from django.conf import (
|
||||||
from tasks.jobs.attack_paths.config import (
|
settings, # noqa: F401 - kept for tests that patch ...database.settings
|
||||||
BATCH_SIZE,
|
|
||||||
PROVIDER_RESOURCE_LABEL,
|
|
||||||
get_provider_label,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from api.attack_paths.retryable_session import RetryableSession
|
|
||||||
|
|
||||||
# Without this Celery goes crazy with Neo4j logging
|
|
||||||
logging.getLogger("neo4j").setLevel(logging.ERROR)
|
|
||||||
logging.getLogger("neo4j").propagate = False
|
|
||||||
|
|
||||||
SERVICE_UNAVAILABLE_MAX_RETRIES = env.int(
|
|
||||||
"ATTACK_PATHS_SERVICE_UNAVAILABLE_MAX_RETRIES", default=3
|
|
||||||
)
|
|
||||||
READ_QUERY_TIMEOUT_SECONDS = env.int(
|
|
||||||
"ATTACK_PATHS_READ_QUERY_TIMEOUT_SECONDS", default=30
|
|
||||||
)
|
|
||||||
MAX_CUSTOM_QUERY_NODES = env.int("ATTACK_PATHS_MAX_CUSTOM_QUERY_NODES", default=250)
|
MAX_CUSTOM_QUERY_NODES = env.int("ATTACK_PATHS_MAX_CUSTOM_QUERY_NODES", default=250)
|
||||||
CONN_ACQUISITION_TIMEOUT = env.int("NEO4J_CONN_ACQUISITION_TIMEOUT", default=15)
|
|
||||||
READ_EXCEPTION_CODES = [
|
|
||||||
"Neo.ClientError.Statement.AccessMode",
|
|
||||||
"Neo.ClientError.Procedure.ProcedureNotFound",
|
|
||||||
]
|
|
||||||
CLIENT_STATEMENT_EXCEPTION_PREFIX = "Neo.ClientError.Statement."
|
|
||||||
|
|
||||||
# Module-level process-wide driver singleton
|
TEMP_DB_PREFIX = "db-tmp-scan-"
|
||||||
_driver: neo4j.Driver | None = None
|
DATABASE_NOT_FOUND_CODE = "Neo.ClientError.Database.DatabaseNotFound"
|
||||||
_lock = threading.Lock()
|
|
||||||
|
|
||||||
# Base Neo4j functions
|
|
||||||
|
|
||||||
|
|
||||||
def get_uri() -> str:
|
|
||||||
host = settings.DATABASES["neo4j"]["HOST"]
|
|
||||||
port = settings.DATABASES["neo4j"]["PORT"]
|
|
||||||
return f"bolt://{host}:{port}"
|
|
||||||
|
|
||||||
|
|
||||||
def init_driver() -> neo4j.Driver:
|
|
||||||
global _driver
|
|
||||||
if _driver is not None:
|
|
||||||
return _driver
|
|
||||||
|
|
||||||
with _lock:
|
|
||||||
if _driver is None:
|
|
||||||
uri = get_uri()
|
|
||||||
config = settings.DATABASES["neo4j"]
|
|
||||||
|
|
||||||
_driver = neo4j.GraphDatabase.driver(
|
|
||||||
uri,
|
|
||||||
auth=(config["USER"], config["PASSWORD"]),
|
|
||||||
keep_alive=True,
|
|
||||||
max_connection_lifetime=7200,
|
|
||||||
connection_acquisition_timeout=CONN_ACQUISITION_TIMEOUT,
|
|
||||||
max_connection_pool_size=50,
|
|
||||||
)
|
|
||||||
_driver.verify_connectivity()
|
|
||||||
|
|
||||||
# Register cleanup handler (only runs once since we're inside the _driver is None block)
|
|
||||||
atexit.register(close_driver)
|
|
||||||
|
|
||||||
return _driver
|
|
||||||
|
|
||||||
|
|
||||||
def get_driver() -> neo4j.Driver:
|
|
||||||
return init_driver()
|
|
||||||
|
|
||||||
|
|
||||||
def close_driver() -> None: # TODO: Use it
|
|
||||||
global _driver
|
|
||||||
with _lock:
|
|
||||||
if _driver is not None:
|
|
||||||
try:
|
|
||||||
_driver.close()
|
|
||||||
|
|
||||||
finally:
|
|
||||||
_driver = None
|
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def get_session(
|
|
||||||
database: str | None = None, default_access_mode: str | None = None
|
|
||||||
) -> Iterator[RetryableSession]:
|
|
||||||
session_wrapper: RetryableSession | None = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
session_wrapper = RetryableSession(
|
|
||||||
session_factory=lambda: get_driver().session(
|
|
||||||
database=database, default_access_mode=default_access_mode
|
|
||||||
),
|
|
||||||
max_retries=SERVICE_UNAVAILABLE_MAX_RETRIES,
|
|
||||||
)
|
|
||||||
yield session_wrapper
|
|
||||||
|
|
||||||
except neo4j.exceptions.Neo4jError as exc:
|
|
||||||
if (
|
|
||||||
default_access_mode == neo4j.READ_ACCESS
|
|
||||||
and exc.code
|
|
||||||
and exc.code in READ_EXCEPTION_CODES
|
|
||||||
):
|
|
||||||
message = "Read query not allowed"
|
|
||||||
code = READ_EXCEPTION_CODES[0]
|
|
||||||
raise WriteQueryNotAllowedException(message=message, code=code)
|
|
||||||
|
|
||||||
message = exc.message if exc.message is not None else str(exc)
|
|
||||||
|
|
||||||
if exc.code and exc.code.startswith(CLIENT_STATEMENT_EXCEPTION_PREFIX):
|
|
||||||
raise ClientStatementException(message=message, code=exc.code)
|
|
||||||
|
|
||||||
raise GraphDatabaseQueryException(message=message, code=exc.code)
|
|
||||||
|
|
||||||
finally:
|
|
||||||
if session_wrapper is not None:
|
|
||||||
session_wrapper.close()
|
|
||||||
|
|
||||||
|
|
||||||
def execute_read_query(
|
|
||||||
database: str,
|
|
||||||
cypher: str,
|
|
||||||
parameters: dict[str, Any] | None = None,
|
|
||||||
) -> neo4j.graph.Graph:
|
|
||||||
with get_session(database, default_access_mode=neo4j.READ_ACCESS) as session:
|
|
||||||
|
|
||||||
def _run(tx: neo4j.ManagedTransaction) -> neo4j.graph.Graph:
|
|
||||||
result = tx.run(
|
|
||||||
cypher, parameters or {}, timeout=READ_QUERY_TIMEOUT_SECONDS
|
|
||||||
)
|
|
||||||
return result.graph()
|
|
||||||
|
|
||||||
return session.execute_read(_run)
|
|
||||||
|
|
||||||
|
|
||||||
def create_database(database: str) -> None:
|
|
||||||
query = "CREATE DATABASE $database IF NOT EXISTS"
|
|
||||||
parameters = {"database": database}
|
|
||||||
|
|
||||||
with get_session() as session:
|
|
||||||
session.run(query, parameters)
|
|
||||||
|
|
||||||
|
|
||||||
def drop_database(database: str) -> None:
|
|
||||||
query = f"DROP DATABASE `{database}` IF EXISTS DESTROY DATA"
|
|
||||||
|
|
||||||
with get_session() as session:
|
|
||||||
session.run(query)
|
|
||||||
|
|
||||||
|
|
||||||
def drop_subgraph(database: str, provider_id: str) -> int:
|
|
||||||
"""
|
|
||||||
Delete all nodes for a provider from the tenant database.
|
|
||||||
|
|
||||||
Uses batched deletion to avoid memory issues with large graphs.
|
|
||||||
Silently returns 0 if the database doesn't exist.
|
|
||||||
"""
|
|
||||||
provider_label = get_provider_label(provider_id)
|
|
||||||
deleted_nodes = 0
|
|
||||||
|
|
||||||
try:
|
|
||||||
with get_session(database) as session:
|
|
||||||
deleted_count = 1
|
|
||||||
while deleted_count > 0:
|
|
||||||
result = session.run(
|
|
||||||
f"""
|
|
||||||
MATCH (n:{PROVIDER_RESOURCE_LABEL}:`{provider_label}`)
|
|
||||||
WITH n LIMIT $batch_size
|
|
||||||
DETACH DELETE n
|
|
||||||
RETURN COUNT(n) AS deleted_nodes_count
|
|
||||||
""",
|
|
||||||
{"batch_size": BATCH_SIZE},
|
|
||||||
)
|
|
||||||
deleted_count = result.single().get("deleted_nodes_count", 0)
|
|
||||||
deleted_nodes += deleted_count
|
|
||||||
|
|
||||||
except GraphDatabaseQueryException as exc:
|
|
||||||
if exc.code == "Neo.ClientError.Database.DatabaseNotFound":
|
|
||||||
return 0
|
|
||||||
raise
|
|
||||||
|
|
||||||
return deleted_nodes
|
|
||||||
|
|
||||||
|
|
||||||
def has_provider_data(database: str, provider_id: str) -> bool:
|
|
||||||
"""
|
|
||||||
Check if any ProviderResource node exists for this provider.
|
|
||||||
|
|
||||||
Returns `False` if the database doesn't exist.
|
|
||||||
"""
|
|
||||||
provider_label = get_provider_label(provider_id)
|
|
||||||
query = f"MATCH (n:{PROVIDER_RESOURCE_LABEL}:`{provider_label}`) RETURN 1 LIMIT 1"
|
|
||||||
|
|
||||||
try:
|
|
||||||
with get_session(database, default_access_mode=neo4j.READ_ACCESS) as session:
|
|
||||||
result = session.run(query)
|
|
||||||
return result.single() is not None
|
|
||||||
|
|
||||||
except GraphDatabaseQueryException as exc:
|
|
||||||
if exc.code == "Neo.ClientError.Database.DatabaseNotFound":
|
|
||||||
return False
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
def clear_cache(database: str) -> None:
|
|
||||||
query = "CALL db.clearQueryCaches()"
|
|
||||||
|
|
||||||
try:
|
|
||||||
with get_session(database) as session:
|
|
||||||
session.run(query)
|
|
||||||
|
|
||||||
except GraphDatabaseQueryException as exc:
|
|
||||||
logging.warning(f"Failed to clear query cache for database `{database}`: {exc}")
|
|
||||||
|
|
||||||
|
|
||||||
# Neo4j functions related to Prowler + Cartography
|
|
||||||
|
|
||||||
|
|
||||||
def get_database_name(entity_id: str | UUID, temporary: bool = False) -> str:
|
|
||||||
prefix = "tmp-scan" if temporary else "tenant"
|
|
||||||
return f"db-{prefix}-{str(entity_id).lower()}"
|
|
||||||
|
|
||||||
|
|
||||||
# Exceptions
|
# Exceptions
|
||||||
@@ -242,13 +42,190 @@ class GraphDatabaseQueryException(Exception):
|
|||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
if self.code:
|
if self.code:
|
||||||
return f"{self.code}: {self.message}"
|
return f"{self.code}: {self.message}"
|
||||||
|
|
||||||
return self.message
|
return self.message
|
||||||
|
|
||||||
|
|
||||||
|
class NeptuneWriteRetryExhaustedException(GraphDatabaseQueryException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class WriteQueryNotAllowedException(GraphDatabaseQueryException):
|
class WriteQueryNotAllowedException(GraphDatabaseQueryException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ClientStatementException(GraphDatabaseQueryException):
|
class ClientStatementException(GraphDatabaseQueryException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Routing
|
||||||
|
|
||||||
|
|
||||||
|
def _is_ingest_database(database: str | None) -> bool:
|
||||||
|
return bool(database) and database.startswith(TEMP_DB_PREFIX)
|
||||||
|
|
||||||
|
|
||||||
|
# Driver lifecycle
|
||||||
|
|
||||||
|
|
||||||
|
def init_driver() -> Any:
|
||||||
|
"""Initialize the configured sink backend.
|
||||||
|
|
||||||
|
The ingest driver (Neo4j for cartography temp DBs) stays lazy: it is
|
||||||
|
only initialized when a temp-DB operation actually runs, which never
|
||||||
|
happens on API pods.
|
||||||
|
"""
|
||||||
|
return sink_module.init()
|
||||||
|
|
||||||
|
|
||||||
|
def close_driver() -> None:
|
||||||
|
"""Close every driver held by this process."""
|
||||||
|
sink_module.close()
|
||||||
|
ingest.close_driver()
|
||||||
|
|
||||||
|
|
||||||
|
def get_driver() -> neo4j.Driver:
|
||||||
|
"""Return the sink backend's underlying driver.
|
||||||
|
|
||||||
|
Only meaningful for the Neo4j sink (where the backend has a single Neo4j
|
||||||
|
driver). On Neptune this returns the writer driver. Kept for tests and
|
||||||
|
legacy call-sites; prefer `get_session` for new code.
|
||||||
|
"""
|
||||||
|
backend = sink_module.get_backend()
|
||||||
|
|
||||||
|
# Neo4jSink exposes get_driver(); NeptuneSink exposes get_writer()
|
||||||
|
if hasattr(backend, "get_driver"):
|
||||||
|
return backend.get_driver()
|
||||||
|
|
||||||
|
if hasattr(backend, "get_writer"):
|
||||||
|
return backend.get_writer()
|
||||||
|
|
||||||
|
raise RuntimeError("Active sink backend does not expose a driver handle")
|
||||||
|
|
||||||
|
|
||||||
|
def verify_connectivity() -> None:
|
||||||
|
"""Raise if the configured graph database is unreachable on the API read path.
|
||||||
|
|
||||||
|
Backend-agnostic entry point for the readiness probe: Neo4j verifies its
|
||||||
|
driver, Neptune verifies the reader endpoint.
|
||||||
|
"""
|
||||||
|
sink_module.get_backend().verify_connectivity()
|
||||||
|
|
||||||
|
|
||||||
|
def verify_scan_databases_available() -> None:
|
||||||
|
"""Raise if either graph database needed by an Attack Paths scan is unavailable."""
|
||||||
|
errors: list[str] = []
|
||||||
|
first_error: Exception | None = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
ingest.get_driver().verify_connectivity()
|
||||||
|
except Exception as exc:
|
||||||
|
errors.append(f"ingest Neo4j: {exc}")
|
||||||
|
first_error = exc
|
||||||
|
|
||||||
|
try:
|
||||||
|
get_driver().verify_connectivity()
|
||||||
|
except Exception as exc:
|
||||||
|
errors.append(f"sink {settings.ATTACK_PATHS_SINK_DATABASE}: {exc}")
|
||||||
|
if first_error is None:
|
||||||
|
first_error = exc
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Attack Paths graph database unavailable before scan start: "
|
||||||
|
+ "; ".join(errors)
|
||||||
|
) from first_error
|
||||||
|
|
||||||
|
|
||||||
|
def get_uri() -> str:
|
||||||
|
"""Return the sink URI. Retained for backwards compatibility."""
|
||||||
|
if settings.ATTACK_PATHS_SINK_DATABASE == "neptune":
|
||||||
|
cfg = settings.DATABASES["neptune"]
|
||||||
|
return f"bolt+s://{cfg['WRITER_ENDPOINT']}:{cfg['PORT']}"
|
||||||
|
|
||||||
|
cfg = settings.DATABASES["neo4j"]
|
||||||
|
return f"bolt://{cfg['HOST']}:{cfg['PORT']}"
|
||||||
|
|
||||||
|
|
||||||
|
def get_ingest_uri() -> str:
|
||||||
|
"""Neo4j URI for the cartography temp (ingest) database, which is always
|
||||||
|
Neo4j regardless of the configured sink."""
|
||||||
|
return ingest.get_uri()
|
||||||
|
|
||||||
|
|
||||||
|
# Session API
|
||||||
|
|
||||||
|
|
||||||
|
def get_session(
|
||||||
|
database: str | None = None,
|
||||||
|
default_access_mode: str | None = None,
|
||||||
|
) -> AbstractContextManager:
|
||||||
|
"""Return a session against the right backend.
|
||||||
|
|
||||||
|
- `database` names starting with `db-tmp-scan-` always go to ingest.
|
||||||
|
- No database name → ingest (used for CREATE / DROP DATABASE admin ops).
|
||||||
|
- Any other name → sink.
|
||||||
|
"""
|
||||||
|
if _is_ingest_database(database) or database is None:
|
||||||
|
return ingest.get_session(
|
||||||
|
database=database, default_access_mode=default_access_mode
|
||||||
|
)
|
||||||
|
|
||||||
|
return sink_module.get_backend().get_session(
|
||||||
|
database=database, default_access_mode=default_access_mode
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def execute_read_query(
|
||||||
|
database: str,
|
||||||
|
cypher: str,
|
||||||
|
parameters: dict[str, Any] | None = None,
|
||||||
|
) -> neo4j.graph.Graph:
|
||||||
|
"""Read-only query against the sink."""
|
||||||
|
return sink_module.get_backend().execute_read_query(database, cypher, parameters)
|
||||||
|
|
||||||
|
|
||||||
|
def create_database(database: str) -> None:
|
||||||
|
"""Create a database. Temp DBs always land on ingest (Neo4j).
|
||||||
|
|
||||||
|
On the Neo4j sink, tenant DBs also route to ingest because both drivers
|
||||||
|
connect to the same Neo4j cluster. On the Neptune sink, tenant DB creates
|
||||||
|
are no-ops.
|
||||||
|
"""
|
||||||
|
if _is_ingest_database(database):
|
||||||
|
ingest.create_database(database)
|
||||||
|
return
|
||||||
|
|
||||||
|
sink_module.get_backend().create_database(database)
|
||||||
|
|
||||||
|
|
||||||
|
def drop_database(database: str) -> None:
|
||||||
|
"""Drop a database. Mirrors `create_database` routing."""
|
||||||
|
if _is_ingest_database(database):
|
||||||
|
ingest.drop_database(database)
|
||||||
|
return
|
||||||
|
|
||||||
|
sink_module.get_backend().drop_database(database)
|
||||||
|
|
||||||
|
|
||||||
|
def drop_subgraph(database: str, provider_id: str) -> int:
|
||||||
|
return sink_module.get_backend().drop_subgraph(database, provider_id)
|
||||||
|
|
||||||
|
|
||||||
|
def has_provider_data(database: str, provider_id: str) -> bool:
|
||||||
|
return sink_module.get_backend().has_provider_data(database, provider_id)
|
||||||
|
|
||||||
|
|
||||||
|
def clear_cache(database: str) -> None:
|
||||||
|
if _is_ingest_database(database):
|
||||||
|
ingest.clear_cache(database)
|
||||||
|
return
|
||||||
|
|
||||||
|
sink_module.get_backend().clear_cache(database)
|
||||||
|
|
||||||
|
|
||||||
|
# Name helper
|
||||||
|
|
||||||
|
|
||||||
|
def get_database_name(entity_id: str | UUID, temporary: bool = False) -> str:
|
||||||
|
prefix = "tmp-scan" if temporary else "tenant"
|
||||||
|
return f"db-{prefix}-{str(entity_id).lower()}"
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
"""Cartography ingest layer.
|
||||||
|
|
||||||
|
Public surface for the per-scan Neo4j temp database driver. Implementation
|
||||||
|
lives in `api.attack_paths.ingest.driver`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from api.attack_paths.ingest.driver import (
|
||||||
|
clear_cache,
|
||||||
|
close_driver,
|
||||||
|
create_database,
|
||||||
|
drop_database,
|
||||||
|
get_driver,
|
||||||
|
get_session,
|
||||||
|
get_uri,
|
||||||
|
init_driver,
|
||||||
|
run_cypher,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"clear_cache",
|
||||||
|
"close_driver",
|
||||||
|
"create_database",
|
||||||
|
"drop_database",
|
||||||
|
"get_driver",
|
||||||
|
"get_session",
|
||||||
|
"get_uri",
|
||||||
|
"init_driver",
|
||||||
|
"run_cypher",
|
||||||
|
]
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
"""Cartography ingest driver: per-scan throw-away Neo4j database.
|
||||||
|
|
||||||
|
Cartography writes each scan's graph into a throw-away Neo4j database named
|
||||||
|
`db-tmp-scan-{scan_uuid}`. This is always Neo4j, regardless of the configured
|
||||||
|
sink: Neptune is single-database and cannot host per-scan throw-away
|
||||||
|
databases. This module owns the Neo4j driver used for those temp DBs and the
|
||||||
|
admin ops they need (CREATE / DROP DATABASE).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import atexit
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
from collections.abc import Iterator
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import neo4j
|
||||||
|
import neo4j.exceptions
|
||||||
|
from api.attack_paths.retryable_session import RetryableSession
|
||||||
|
from config.env import env
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
logging.getLogger("neo4j").setLevel(logging.ERROR)
|
||||||
|
logging.getLogger("neo4j").propagate = False
|
||||||
|
|
||||||
|
SERVICE_UNAVAILABLE_MAX_RETRIES = env.int(
|
||||||
|
"ATTACK_PATHS_SERVICE_UNAVAILABLE_MAX_RETRIES", default=3
|
||||||
|
)
|
||||||
|
CONN_ACQUISITION_TIMEOUT = env.int("NEO4J_CONN_ACQUISITION_TIMEOUT", default=15)
|
||||||
|
# TCP connect timeout, ordered below the acquisition timeout so an unreachable
|
||||||
|
# host can't pin a worker on a temp-DB op longer than this.
|
||||||
|
CONNECTION_TIMEOUT = env.int("NEO4J_CONNECTION_TIMEOUT", default=5)
|
||||||
|
MAX_CONNECTION_LIFETIME = env.int("NEO4J_MAX_CONNECTION_LIFETIME", default=7200)
|
||||||
|
MAX_CONNECTION_POOL_SIZE = env.int("NEO4J_MAX_CONNECTION_POOL_SIZE", default=50)
|
||||||
|
|
||||||
|
_driver: neo4j.Driver | None = None
|
||||||
|
_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
def _neo4j_config() -> dict:
|
||||||
|
return settings.DATABASES["neo4j"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_uri() -> str:
|
||||||
|
"""Bolt URI for the Neo4j temp (ingest) database. Always Neo4j."""
|
||||||
|
config = _neo4j_config()
|
||||||
|
host = config["HOST"]
|
||||||
|
port = config["PORT"]
|
||||||
|
if not host or not port:
|
||||||
|
raise RuntimeError(
|
||||||
|
"NEO4J_HOST / NEO4J_PORT must be set to use the attack-paths "
|
||||||
|
"temp database. Workers require Neo4j env even when the sink is Neptune."
|
||||||
|
)
|
||||||
|
|
||||||
|
return f"bolt://{host}:{port}"
|
||||||
|
|
||||||
|
|
||||||
|
def init_driver() -> neo4j.Driver:
|
||||||
|
"""Initialize the temp-database Neo4j driver. Idempotent."""
|
||||||
|
global _driver
|
||||||
|
if _driver is not None:
|
||||||
|
return _driver
|
||||||
|
|
||||||
|
with _lock:
|
||||||
|
if _driver is None:
|
||||||
|
config = _neo4j_config()
|
||||||
|
_driver = neo4j.GraphDatabase.driver(
|
||||||
|
get_uri(),
|
||||||
|
auth=(config["USER"], config["PASSWORD"]),
|
||||||
|
keep_alive=True,
|
||||||
|
max_connection_lifetime=MAX_CONNECTION_LIFETIME,
|
||||||
|
connection_timeout=CONNECTION_TIMEOUT,
|
||||||
|
connection_acquisition_timeout=CONN_ACQUISITION_TIMEOUT,
|
||||||
|
max_connection_pool_size=MAX_CONNECTION_POOL_SIZE,
|
||||||
|
)
|
||||||
|
# Best-effort connectivity check: a Neo4j that is down at boot must
|
||||||
|
# not crash the worker. The driver reconnects lazily on first use.
|
||||||
|
try:
|
||||||
|
_driver.verify_connectivity()
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
logging.warning(
|
||||||
|
"Neo4j temp-database unreachable at init; continuing with a "
|
||||||
|
"lazily-reconnecting driver",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
atexit.register(close_driver)
|
||||||
|
|
||||||
|
return _driver
|
||||||
|
|
||||||
|
|
||||||
|
def get_driver() -> neo4j.Driver:
|
||||||
|
return init_driver()
|
||||||
|
|
||||||
|
|
||||||
|
def close_driver() -> None:
|
||||||
|
global _driver
|
||||||
|
with _lock:
|
||||||
|
if _driver is not None:
|
||||||
|
try:
|
||||||
|
_driver.close()
|
||||||
|
finally:
|
||||||
|
_driver = None
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def get_session(
|
||||||
|
database: str | None = None,
|
||||||
|
default_access_mode: str | None = None,
|
||||||
|
) -> Iterator[RetryableSession]:
|
||||||
|
"""Session against the Neo4j temp-database cluster. Used for temp DB sessions
|
||||||
|
and for admin operations (CREATE / DROP DATABASE) when `database` is None."""
|
||||||
|
from api.attack_paths.database import (
|
||||||
|
ClientStatementException,
|
||||||
|
GraphDatabaseQueryException,
|
||||||
|
WriteQueryNotAllowedException,
|
||||||
|
)
|
||||||
|
|
||||||
|
READ_EXCEPTION_CODES = [
|
||||||
|
"Neo.ClientError.Statement.AccessMode",
|
||||||
|
"Neo.ClientError.Procedure.ProcedureNotFound",
|
||||||
|
]
|
||||||
|
CLIENT_STATEMENT_EXCEPTION_PREFIX = "Neo.ClientError.Statement."
|
||||||
|
|
||||||
|
session_wrapper: RetryableSession | None = None
|
||||||
|
try:
|
||||||
|
session_wrapper = RetryableSession(
|
||||||
|
session_factory=lambda: get_driver().session(
|
||||||
|
database=database, default_access_mode=default_access_mode
|
||||||
|
),
|
||||||
|
max_retries=SERVICE_UNAVAILABLE_MAX_RETRIES,
|
||||||
|
)
|
||||||
|
yield session_wrapper
|
||||||
|
|
||||||
|
except neo4j.exceptions.Neo4jError as exc:
|
||||||
|
if (
|
||||||
|
default_access_mode == neo4j.READ_ACCESS
|
||||||
|
and exc.code
|
||||||
|
and exc.code in READ_EXCEPTION_CODES
|
||||||
|
):
|
||||||
|
raise WriteQueryNotAllowedException(
|
||||||
|
message="Read query not allowed", code=READ_EXCEPTION_CODES[0]
|
||||||
|
)
|
||||||
|
|
||||||
|
message = exc.message if exc.message is not None else str(exc)
|
||||||
|
if exc.code and exc.code.startswith(CLIENT_STATEMENT_EXCEPTION_PREFIX):
|
||||||
|
raise ClientStatementException(message=message, code=exc.code)
|
||||||
|
raise GraphDatabaseQueryException(message=message, code=exc.code)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
if session_wrapper is not None:
|
||||||
|
session_wrapper.close()
|
||||||
|
|
||||||
|
|
||||||
|
def create_database(database: str) -> None:
|
||||||
|
"""Create a database on the Neo4j cluster. Used for temp scan DBs."""
|
||||||
|
with get_session() as session:
|
||||||
|
session.run("CREATE DATABASE $database IF NOT EXISTS", {"database": database})
|
||||||
|
|
||||||
|
|
||||||
|
def drop_database(database: str) -> None:
|
||||||
|
"""Drop a database on the Neo4j cluster. Used for temp scan DBs."""
|
||||||
|
with get_session() as session:
|
||||||
|
session.run(f"DROP DATABASE `{database}` IF EXISTS DESTROY DATA")
|
||||||
|
|
||||||
|
|
||||||
|
def clear_cache(database: str) -> None:
|
||||||
|
"""Best-effort cache clear for a Neo4j database."""
|
||||||
|
from api.attack_paths.database import GraphDatabaseQueryException
|
||||||
|
|
||||||
|
try:
|
||||||
|
with get_session(database) as session:
|
||||||
|
session.run("CALL db.clearQueryCaches()")
|
||||||
|
|
||||||
|
except GraphDatabaseQueryException as exc:
|
||||||
|
logging.warning(f"Failed to clear query cache for database `{database}`: {exc}")
|
||||||
|
|
||||||
|
|
||||||
|
def run_cypher(
|
||||||
|
database: str | None,
|
||||||
|
cypher: str,
|
||||||
|
parameters: dict[str, Any] | None = None,
|
||||||
|
) -> Any:
|
||||||
|
"""Execute Cypher directly without the context manager. Thin helper."""
|
||||||
|
with get_session(database) as session:
|
||||||
|
return session.run(cypher, parameters or {})
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
from api.attack_paths.queries.types import (
|
|
||||||
AttackPathsQueryDefinition,
|
|
||||||
AttackPathsQueryParameterDefinition,
|
|
||||||
)
|
|
||||||
from api.attack_paths.queries.registry import (
|
from api.attack_paths.queries.registry import (
|
||||||
get_queries_for_provider,
|
get_queries_for_provider,
|
||||||
get_query_by_id,
|
get_query_by_id,
|
||||||
)
|
)
|
||||||
|
from api.attack_paths.queries.types import (
|
||||||
|
AttackPathsQueryDefinition,
|
||||||
|
AttackPathsQueryParameterDefinition,
|
||||||
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"AttackPathsQueryDefinition",
|
"AttackPathsQueryDefinition",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,14 @@
|
|||||||
from api.attack_paths.queries.types import AttackPathsQueryDefinition
|
|
||||||
from api.attack_paths.queries.aws import AWS_QUERIES
|
from api.attack_paths.queries.aws import AWS_QUERIES
|
||||||
|
|
||||||
|
# TODO: drop after Neptune cutover
|
||||||
|
from api.attack_paths.queries.aws_deprecated import AWS_DEPRECATED_QUERIES
|
||||||
|
from api.attack_paths.queries.types import AttackPathsQueryDefinition
|
||||||
|
|
||||||
# Query definitions organized by provider
|
# Query definitions for scans synced with the current schema.
|
||||||
_QUERY_DEFINITIONS: dict[str, list[AttackPathsQueryDefinition]] = {
|
_QUERY_DEFINITIONS: dict[str, list[AttackPathsQueryDefinition]] = {
|
||||||
"aws": AWS_QUERIES,
|
"aws": AWS_QUERIES,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Flat lookup by query ID for O(1) access
|
|
||||||
_QUERIES_BY_ID: dict[str, AttackPathsQueryDefinition] = {
|
_QUERIES_BY_ID: dict[str, AttackPathsQueryDefinition] = {
|
||||||
definition.id: definition
|
definition.id: definition
|
||||||
for definitions in _QUERY_DEFINITIONS.values()
|
for definitions in _QUERY_DEFINITIONS.values()
|
||||||
@@ -15,11 +16,45 @@ _QUERIES_BY_ID: dict[str, AttackPathsQueryDefinition] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_queries_for_provider(provider: str) -> list[AttackPathsQueryDefinition]:
|
# TODO: drop after Neptune cutover
|
||||||
"""Get all attack path queries for a specific provider."""
|
#
|
||||||
return _QUERY_DEFINITIONS.get(provider, [])
|
# Query definitions for pre-cutover scans (`AttackPathsScan.is_migrated=False`)
|
||||||
|
# whose graph data was written under the previous schema. Both maps expose the
|
||||||
|
# same query IDs so the API contract is identical regardless of which set is
|
||||||
|
# routed to.
|
||||||
|
_DEPRECATED_QUERY_DEFINITIONS: dict[str, list[AttackPathsQueryDefinition]] = {
|
||||||
|
"aws": AWS_DEPRECATED_QUERIES,
|
||||||
|
}
|
||||||
|
|
||||||
|
_DEPRECATED_QUERIES_BY_ID: dict[str, AttackPathsQueryDefinition] = {
|
||||||
|
definition.id: definition
|
||||||
|
for definitions in _DEPRECATED_QUERY_DEFINITIONS.values()
|
||||||
|
for definition in definitions
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_query_by_id(query_id: str) -> AttackPathsQueryDefinition | None:
|
def get_queries_for_provider(
|
||||||
"""Get a specific attack path query by its ID."""
|
provider: str,
|
||||||
return _QUERIES_BY_ID.get(query_id)
|
is_migrated: bool = True,
|
||||||
|
) -> list[AttackPathsQueryDefinition]:
|
||||||
|
"""Get all attack path queries for a provider.
|
||||||
|
|
||||||
|
`is_migrated` selects the catalog: True for scans synced with the current
|
||||||
|
schema, False for pre-cutover scans still using the legacy graph shape.
|
||||||
|
# TODO: drop the `is_migrated` parameter after Neptune cutover
|
||||||
|
"""
|
||||||
|
catalog = _QUERY_DEFINITIONS if is_migrated else _DEPRECATED_QUERY_DEFINITIONS
|
||||||
|
return catalog.get(provider, [])
|
||||||
|
|
||||||
|
|
||||||
|
def get_query_by_id(
|
||||||
|
query_id: str,
|
||||||
|
is_migrated: bool = True,
|
||||||
|
) -> AttackPathsQueryDefinition | None:
|
||||||
|
"""Get a specific attack path query by ID.
|
||||||
|
|
||||||
|
`is_migrated` selects the catalog (see `get_queries_for_provider`).
|
||||||
|
# TODO: drop the `is_migrated` parameter after Neptune cutover
|
||||||
|
"""
|
||||||
|
by_id = _QUERIES_BY_ID if is_migrated else _DEPRECATED_QUERIES_BY_ID
|
||||||
|
return by_id.get(query_id)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user