From ca27854ff0732ff9a65b2bed2ebb5af0f03ea401 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Jul 2024 08:55:33 +0200 Subject: [PATCH 1/4] chore(deps-dev): bump coverage from 7.5.4 to 7.6.0 (#4438) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 108 ++++++++++++++++++++++++------------------------- pyproject.toml | 2 +- 2 files changed, 55 insertions(+), 55 deletions(-) diff --git a/poetry.lock b/poetry.lock index 3ae7d9a450..3f5f653436 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1000,63 +1000,63 @@ files = [ [[package]] name = "coverage" -version = "7.5.4" +version = "7.6.0" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6cfb5a4f556bb51aba274588200a46e4dd6b505fb1a5f8c5ae408222eb416f99"}, - {file = "coverage-7.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2174e7c23e0a454ffe12267a10732c273243b4f2d50d07544a91198f05c48f47"}, - {file = "coverage-7.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2214ee920787d85db1b6a0bd9da5f8503ccc8fcd5814d90796c2f2493a2f4d2e"}, - {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1137f46adb28e3813dec8c01fefadcb8c614f33576f672962e323b5128d9a68d"}, - {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b385d49609f8e9efc885790a5a0e89f2e3ae042cdf12958b6034cc442de428d3"}, - {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4a474f799456e0eb46d78ab07303286a84a3140e9700b9e154cfebc8f527016"}, - {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5cd64adedf3be66f8ccee418473c2916492d53cbafbfcff851cbec5a8454b136"}, - {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e564c2cf45d2f44a9da56f4e3a26b2236504a496eb4cb0ca7221cd4cc7a9aca9"}, - {file = "coverage-7.5.4-cp310-cp310-win32.whl", hash = "sha256:7076b4b3a5f6d2b5d7f1185fde25b1e54eb66e647a1dfef0e2c2bfaf9b4c88c8"}, - {file = "coverage-7.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:018a12985185038a5b2bcafab04ab833a9a0f2c59995b3cec07e10074c78635f"}, - {file = "coverage-7.5.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db14f552ac38f10758ad14dd7b983dbab424e731588d300c7db25b6f89e335b5"}, - {file = "coverage-7.5.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3257fdd8e574805f27bb5342b77bc65578e98cbc004a92232106344053f319ba"}, - {file = "coverage-7.5.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a6612c99081d8d6134005b1354191e103ec9705d7ba2754e848211ac8cacc6b"}, - {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d45d3cbd94159c468b9b8c5a556e3f6b81a8d1af2a92b77320e887c3e7a5d080"}, - {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed550e7442f278af76d9d65af48069f1fb84c9f745ae249c1a183c1e9d1b025c"}, - {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a892be37ca35eb5019ec85402c3371b0f7cda5ab5056023a7f13da0961e60da"}, - {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8192794d120167e2a64721d88dbd688584675e86e15d0569599257566dec9bf0"}, - {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:820bc841faa502e727a48311948e0461132a9c8baa42f6b2b84a29ced24cc078"}, - {file = "coverage-7.5.4-cp311-cp311-win32.whl", hash = "sha256:6aae5cce399a0f065da65c7bb1e8abd5c7a3043da9dceb429ebe1b289bc07806"}, - {file = "coverage-7.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:d2e344d6adc8ef81c5a233d3a57b3c7d5181f40e79e05e1c143da143ccb6377d"}, - {file = "coverage-7.5.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:54317c2b806354cbb2dc7ac27e2b93f97096912cc16b18289c5d4e44fc663233"}, - {file = "coverage-7.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:042183de01f8b6d531e10c197f7f0315a61e8d805ab29c5f7b51a01d62782747"}, - {file = "coverage-7.5.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6bb74ed465d5fb204b2ec41d79bcd28afccf817de721e8a807d5141c3426638"}, - {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3d45ff86efb129c599a3b287ae2e44c1e281ae0f9a9bad0edc202179bcc3a2e"}, - {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5013ed890dc917cef2c9f765c4c6a8ae9df983cd60dbb635df8ed9f4ebc9f555"}, - {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1014fbf665fef86cdfd6cb5b7371496ce35e4d2a00cda501cf9f5b9e6fced69f"}, - {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3684bc2ff328f935981847082ba4fdc950d58906a40eafa93510d1b54c08a66c"}, - {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:581ea96f92bf71a5ec0974001f900db495488434a6928a2ca7f01eee20c23805"}, - {file = "coverage-7.5.4-cp312-cp312-win32.whl", hash = "sha256:73ca8fbc5bc622e54627314c1a6f1dfdd8db69788f3443e752c215f29fa87a0b"}, - {file = "coverage-7.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:cef4649ec906ea7ea5e9e796e68b987f83fa9a718514fe147f538cfeda76d7a7"}, - {file = "coverage-7.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdd31315fc20868c194130de9ee6bfd99755cc9565edff98ecc12585b90be882"}, - {file = "coverage-7.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:02ff6e898197cc1e9fa375581382b72498eb2e6d5fc0b53f03e496cfee3fac6d"}, - {file = "coverage-7.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d05c16cf4b4c2fc880cb12ba4c9b526e9e5d5bb1d81313d4d732a5b9fe2b9d53"}, - {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5986ee7ea0795a4095ac4d113cbb3448601efca7f158ec7f7087a6c705304e4"}, - {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5df54843b88901fdc2f598ac06737f03d71168fd1175728054c8f5a2739ac3e4"}, - {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ab73b35e8d109bffbda9a3e91c64e29fe26e03e49addf5b43d85fc426dde11f9"}, - {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:aea072a941b033813f5e4814541fc265a5c12ed9720daef11ca516aeacd3bd7f"}, - {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:16852febd96acd953b0d55fc842ce2dac1710f26729b31c80b940b9afcd9896f"}, - {file = "coverage-7.5.4-cp38-cp38-win32.whl", hash = "sha256:8f894208794b164e6bd4bba61fc98bf6b06be4d390cf2daacfa6eca0a6d2bb4f"}, - {file = "coverage-7.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:e2afe743289273209c992075a5a4913e8d007d569a406ffed0bd080ea02b0633"}, - {file = "coverage-7.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b95c3a8cb0463ba9f77383d0fa8c9194cf91f64445a63fc26fb2327e1e1eb088"}, - {file = "coverage-7.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d7564cc09dd91b5a6001754a5b3c6ecc4aba6323baf33a12bd751036c998be4"}, - {file = "coverage-7.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44da56a2589b684813f86d07597fdf8a9c6ce77f58976727329272f5a01f99f7"}, - {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e16f3d6b491c48c5ae726308e6ab1e18ee830b4cdd6913f2d7f77354b33f91c8"}, - {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbc5958cb471e5a5af41b0ddaea96a37e74ed289535e8deca404811f6cb0bc3d"}, - {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a04e990a2a41740b02d6182b498ee9796cf60eefe40cf859b016650147908029"}, - {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ddbd2f9713a79e8e7242d7c51f1929611e991d855f414ca9996c20e44a895f7c"}, - {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b1ccf5e728ccf83acd313c89f07c22d70d6c375a9c6f339233dcf792094bcbf7"}, - {file = "coverage-7.5.4-cp39-cp39-win32.whl", hash = "sha256:56b4eafa21c6c175b3ede004ca12c653a88b6f922494b023aeb1e836df953ace"}, - {file = "coverage-7.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:65e528e2e921ba8fd67d9055e6b9f9e34b21ebd6768ae1c1723f4ea6ace1234d"}, - {file = "coverage-7.5.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:79b356f3dd5b26f3ad23b35c75dbdaf1f9e2450b6bcefc6d0825ea0aa3f86ca5"}, - {file = "coverage-7.5.4.tar.gz", hash = "sha256:a44963520b069e12789d0faea4e9fdb1e410cdc4aab89d94f7f55cbb7fef0353"}, + {file = "coverage-7.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dff044f661f59dace805eedb4a7404c573b6ff0cdba4a524141bc63d7be5c7fd"}, + {file = "coverage-7.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8659fd33ee9e6ca03950cfdcdf271d645cf681609153f218826dd9805ab585c"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7792f0ab20df8071d669d929c75c97fecfa6bcab82c10ee4adb91c7a54055463"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b3cd1ca7cd73d229487fa5caca9e4bc1f0bca96526b922d61053ea751fe791"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7e128f85c0b419907d1f38e616c4f1e9f1d1b37a7949f44df9a73d5da5cd53c"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a94925102c89247530ae1dab7dc02c690942566f22e189cbd53579b0693c0783"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dcd070b5b585b50e6617e8972f3fbbee786afca71b1936ac06257f7e178f00f6"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d50a252b23b9b4dfeefc1f663c568a221092cbaded20a05a11665d0dbec9b8fb"}, + {file = "coverage-7.6.0-cp310-cp310-win32.whl", hash = "sha256:0e7b27d04131c46e6894f23a4ae186a6a2207209a05df5b6ad4caee6d54a222c"}, + {file = "coverage-7.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dece71673b3187c86226c3ca793c5f891f9fc3d8aa183f2e3653da18566169"}, + {file = "coverage-7.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7b525ab52ce18c57ae232ba6f7010297a87ced82a2383b1afd238849c1ff933"}, + {file = "coverage-7.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bea27c4269234e06f621f3fac3925f56ff34bc14521484b8f66a580aacc2e7d"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8d1d1821ba5fc88d4a4f45387b65de52382fa3ef1f0115a4f7a20cdfab0e94"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c322ef2bbe15057bc4bf132b525b7e3f7206f071799eb8aa6ad1940bcf5fb1"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03cafe82c1b32b770a29fd6de923625ccac3185a54a5e66606da26d105f37dac"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d1b923fc4a40c5832be4f35a5dab0e5ff89cddf83bb4174499e02ea089daf57"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4b03741e70fb811d1a9a1d75355cf391f274ed85847f4b78e35459899f57af4d"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a73d18625f6a8a1cbb11eadc1d03929f9510f4131879288e3f7922097a429f63"}, + {file = "coverage-7.6.0-cp311-cp311-win32.whl", hash = "sha256:65fa405b837060db569a61ec368b74688f429b32fa47a8929a7a2f9b47183713"}, + {file = "coverage-7.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:6379688fb4cfa921ae349c76eb1a9ab26b65f32b03d46bb0eed841fd4cb6afb1"}, + {file = "coverage-7.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f7db0b6ae1f96ae41afe626095149ecd1b212b424626175a6633c2999eaad45b"}, + {file = "coverage-7.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bbdf9a72403110a3bdae77948b8011f644571311c2fb35ee15f0f10a8fc082e8"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc44bf0315268e253bf563f3560e6c004efe38f76db03a1558274a6e04bf5d5"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da8549d17489cd52f85a9829d0e1d91059359b3c54a26f28bec2c5d369524807"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0086cd4fc71b7d485ac93ca4239c8f75732c2ae3ba83f6be1c9be59d9e2c6382"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fad32ee9b27350687035cb5fdf9145bc9cf0a094a9577d43e909948ebcfa27b"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:044a0985a4f25b335882b0966625270a8d9db3d3409ddc49a4eb00b0ef5e8cee"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:76d5f82213aa78098b9b964ea89de4617e70e0d43e97900c2778a50856dac605"}, + {file = "coverage-7.6.0-cp312-cp312-win32.whl", hash = "sha256:3c59105f8d58ce500f348c5b56163a4113a440dad6daa2294b5052a10db866da"}, + {file = "coverage-7.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca5d79cfdae420a1d52bf177de4bc2289c321d6c961ae321503b2ca59c17ae67"}, + {file = "coverage-7.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d39bd10f0ae453554798b125d2f39884290c480f56e8a02ba7a6ed552005243b"}, + {file = "coverage-7.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beb08e8508e53a568811016e59f3234d29c2583f6b6e28572f0954a6b4f7e03d"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2e16f4cd2bc4d88ba30ca2d3bbf2f21f00f382cf4e1ce3b1ddc96c634bc48ca"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6616d1c9bf1e3faea78711ee42a8b972367d82ceae233ec0ac61cc7fec09fa6b"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad4567d6c334c46046d1c4c20024de2a1c3abc626817ae21ae3da600f5779b44"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d17c6a415d68cfe1091d3296ba5749d3d8696e42c37fca5d4860c5bf7b729f03"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9146579352d7b5f6412735d0f203bbd8d00113a680b66565e205bc605ef81bc6"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cdab02a0a941af190df8782aafc591ef3ad08824f97850b015c8c6a8b3877b0b"}, + {file = "coverage-7.6.0-cp38-cp38-win32.whl", hash = "sha256:df423f351b162a702c053d5dddc0fc0ef9a9e27ea3f449781ace5f906b664428"}, + {file = "coverage-7.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:f2501d60d7497fd55e391f423f965bbe9e650e9ffc3c627d5f0ac516026000b8"}, + {file = "coverage-7.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7221f9ac9dad9492cecab6f676b3eaf9185141539d5c9689d13fd6b0d7de840c"}, + {file = "coverage-7.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ddaaa91bfc4477d2871442bbf30a125e8fe6b05da8a0015507bfbf4718228ab2"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cbe651f3904e28f3a55d6f371203049034b4ddbce65a54527a3f189ca3b390"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831b476d79408ab6ccfadaaf199906c833f02fdb32c9ab907b1d4aa0713cfa3b"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c3d091059ad0b9c59d1034de74a7f36dcfa7f6d3bde782c49deb42438f2450"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4d5fae0a22dc86259dee66f2cc6c1d3e490c4a1214d7daa2a93d07491c5c04b6"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:07ed352205574aad067482e53dd606926afebcb5590653121063fbf4e2175166"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:49c76cdfa13015c4560702574bad67f0e15ca5a2872c6a125f6327ead2b731dd"}, + {file = "coverage-7.6.0-cp39-cp39-win32.whl", hash = "sha256:482855914928c8175735a2a59c8dc5806cf7d8f032e4820d52e845d1f731dca2"}, + {file = "coverage-7.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:543ef9179bc55edfd895154a51792b01c017c87af0ebaae092720152e19e42ca"}, + {file = "coverage-7.6.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:6fe885135c8a479d3e37a7aae61cbd3a0fb2deccb4dda3c25f92a49189f766d6"}, + {file = "coverage-7.6.0.tar.gz", hash = "sha256:289cc803fa1dc901f84701ac10c9ee873619320f2f9aff38794db4a4a0268d51"}, ] [package.dependencies] @@ -4901,4 +4901,4 @@ test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-it [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.13" -content-hash = "9ce442da28ce02660cb549788d2f6cfff9cee9cbf33d56fca40f15a119f8ac1c" +content-hash = "ccc4ea47941b1b8dacfb86d1dd27c1e8b154a6e0fccf5ba902c4a549e45dc1b0" diff --git a/pyproject.toml b/pyproject.toml index b1f8a72e1f..7121f592b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,7 @@ typer = "0.12.3" [tool.poetry.group.dev.dependencies] bandit = "1.7.9" black = "24.4.2" -coverage = "7.5.4" +coverage = "7.6.0" docker = "7.1.0" flake8 = "7.1.0" freezegun = "1.5.1" From bd26d74b2853aa7857b228db391128ae1f9108fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Jul 2024 09:36:43 +0200 Subject: [PATCH 2/4] chore(deps): bump boto3 from 1.34.142 to 1.34.143 (#4437) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 10 +++++----- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 3f5f653436..f5fbfe8319 100644 --- a/poetry.lock +++ b/poetry.lock @@ -709,17 +709,17 @@ files = [ [[package]] name = "boto3" -version = "1.34.142" +version = "1.34.143" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" files = [ - {file = "boto3-1.34.142-py3-none-any.whl", hash = "sha256:cae11cb54f79795e44248a9e53ec5c7328519019df1ba54bc01413f51c548626"}, - {file = "boto3-1.34.142.tar.gz", hash = "sha256:72daee953cfa0631c584c9e3aef594079e1fe6a2f64c81ff791dab9a7b25c013"}, + {file = "boto3-1.34.143-py3-none-any.whl", hash = "sha256:0d16832f23e6bd3ae94e35ea8e625529850bfad9baccd426de96ad8f445d8e03"}, + {file = "boto3-1.34.143.tar.gz", hash = "sha256:b590ce80c65149194def43ebf0ea1cf0533945502507837389a8d22e3ecbcf05"}, ] [package.dependencies] -botocore = ">=1.34.142,<1.35.0" +botocore = ">=1.34.143,<1.35.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -4901,4 +4901,4 @@ test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-it [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.13" -content-hash = "ccc4ea47941b1b8dacfb86d1dd27c1e8b154a6e0fccf5ba902c4a549e45dc1b0" +content-hash = "51f6a45f9b6f2cbae07b7a85be62f08d3e582d0b430a872fabce8511322caa0a" diff --git a/pyproject.toml b/pyproject.toml index 7121f592b6..bf7b071074 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,7 @@ azure-mgmt-storage = "21.2.1" azure-mgmt-subscription = "3.1.1" azure-mgmt-web = "7.3.0" azure-storage-blob = "12.20.0" -boto3 = "1.34.142" +boto3 = "1.34.143" botocore = "1.34.143" colorama = "0.4.6" dash = "2.17.1" From 3be9de376a2fd6992e03edb6ed9559ae6e920fa9 Mon Sep 17 00:00:00 2001 From: Sergio Garcia <38561120+sergargar@users.noreply.github.com> Date: Fri, 12 Jul 2024 10:08:32 -0400 Subject: [PATCH 3/4] chore(mitre): add MITRE ATT&CK output class (#4425) --- prowler/__main__.py | 44 ++++++ prowler/lib/outputs/compliance/compliance.py | 9 +- .../compliance/mitre_attack/mitre_attack.py | 96 +------------ .../mitre_attack/mitre_attack_aws.py | 109 ++++++++++++++ .../mitre_attack/mitre_attack_azure.py | 110 ++++++++++++++ .../mitre_attack/mitre_attack_gcp.py | 109 ++++++++++++++ .../outputs/compliance/mitre_attack/models.py | 9 ++ prowler/lib/outputs/file_descriptors.py | 76 ++-------- .../compliance/{ => cis}/cis_aws_test.py | 1 - .../compliance/{ => cis}/cis_azure_test.py | 0 .../compliance/{ => cis}/cis_gcp_test.py | 0 .../{ => cis}/cis_kubernetes_test.py | 0 tests/lib/outputs/compliance/fixtures.py | 136 ++++++++++++++++++ .../mitre_attack/mitre_attack_aws_test.py | 81 +++++++++++ .../mitre_attack/mitre_attack_azure_test.py | 102 +++++++++++++ .../mitre_attack/mitre_attack_gcp_test.py | 96 +++++++++++++ 16 files changed, 814 insertions(+), 164 deletions(-) create mode 100644 prowler/lib/outputs/compliance/mitre_attack/mitre_attack_aws.py create mode 100644 prowler/lib/outputs/compliance/mitre_attack/mitre_attack_azure.py create mode 100644 prowler/lib/outputs/compliance/mitre_attack/mitre_attack_gcp.py rename tests/lib/outputs/compliance/{ => cis}/cis_aws_test.py (99%) rename tests/lib/outputs/compliance/{ => cis}/cis_azure_test.py (100%) rename tests/lib/outputs/compliance/{ => cis}/cis_gcp_test.py (100%) rename tests/lib/outputs/compliance/{ => cis}/cis_kubernetes_test.py (100%) create mode 100644 tests/lib/outputs/compliance/mitre_attack/mitre_attack_aws_test.py create mode 100644 tests/lib/outputs/compliance/mitre_attack/mitre_attack_azure_test.py create mode 100644 tests/lib/outputs/compliance/mitre_attack/mitre_attack_gcp_test.py diff --git a/prowler/__main__.py b/prowler/__main__.py index d1924717ee..aeade8688c 100644 --- a/prowler/__main__.py +++ b/prowler/__main__.py @@ -48,6 +48,11 @@ from prowler.lib.outputs.compliance.cis.cis_gcp import GCPCIS from prowler.lib.outputs.compliance.cis.cis_kubernetes import KubernetesCIS from prowler.lib.outputs.compliance.compliance import display_compliance_table +from prowler.lib.outputs.compliance.mitre_attack.mitre_attack_aws import AWSMitreAttack +from prowler.lib.outputs.compliance.mitre_attack.mitre_attack_azure import ( + AzureMitreAttack, +) +from prowler.lib.outputs.compliance.mitre_attack.mitre_attack_gcp import GCPMitreAttack from prowler.lib.outputs.csv.models import CSV from prowler.lib.outputs.finding import Finding from prowler.lib.outputs.html.html import HTML @@ -373,6 +378,19 @@ def prowler(): file_path=filename, ) cis_finding.batch_write_data_to_file() + elif compliance_name == "mitre_attack_aws": + # Generate MITRE ATT&CK Finding Object + filename = ( + f"{global_provider.output_options.output_directory}/compliance/" + f"{global_provider.output_options.output_filename}_{compliance_name}.csv" + ) + mitre_attack_finding = AWSMitreAttack( + findings=finding_outputs, + compliance=bulk_compliance_frameworks[compliance_name], + create_file_descriptor=True, + file_path=filename, + ) + mitre_attack_finding.batch_write_data_to_file() elif provider == "azure": for compliance_name in input_compliance_frameworks: @@ -389,6 +407,19 @@ def prowler(): file_path=filename, ) cis_finding.batch_write_data_to_file() + elif compliance_name == "mitre_attack_azure": + # Generate MITRE ATT&CK Finding Object + filename = ( + f"{global_provider.output_options.output_directory}/compliance/" + f"{global_provider.output_options.output_filename}_{compliance_name}.csv" + ) + mitre_attack_finding = AzureMitreAttack( + findings=finding_outputs, + compliance=bulk_compliance_frameworks[compliance_name], + create_file_descriptor=True, + file_path=filename, + ) + mitre_attack_finding.batch_write_data_to_file() elif provider == "gcp": for compliance_name in input_compliance_frameworks: @@ -405,6 +436,19 @@ def prowler(): file_path=filename, ) cis_finding.batch_write_data_to_file() + elif compliance_name == "mitre_attack_gcp": + # Generate MITRE ATT&CK Finding Object + filename = ( + f"{global_provider.output_options.output_directory}/compliance/" + f"{global_provider.output_options.output_filename}_{compliance_name}.csv" + ) + mitre_attack_finding = GCPMitreAttack( + findings=finding_outputs, + compliance=bulk_compliance_frameworks[compliance_name], + create_file_descriptor=True, + file_path=filename, + ) + mitre_attack_finding.batch_write_data_to_file() elif provider == "kubernetes": for compliance_name in input_compliance_frameworks: diff --git a/prowler/lib/outputs/compliance/compliance.py b/prowler/lib/outputs/compliance/compliance.py index b53245e707..924224decb 100644 --- a/prowler/lib/outputs/compliance/compliance.py +++ b/prowler/lib/outputs/compliance/compliance.py @@ -19,7 +19,6 @@ ) from prowler.lib.outputs.compliance.mitre_attack.mitre_attack import ( get_mitre_attack_table, - write_compliance_row_mitre_attack, ) @@ -102,8 +101,11 @@ def fill_compliance( file_descriptors, finding, compliance, output_options, provider ) + # FIXME: Remove this once we merge all the compliance frameworks elif compliance.Framework == "CIS": continue + elif compliance.Framework == "MITRE-ATTACK" and compliance.Version == "": + continue elif ( "AWS-Well-Architected-Framework" in compliance.Framework @@ -122,11 +124,6 @@ def fill_compliance( file_descriptors, finding, compliance, output_options, provider ) - elif compliance.Framework == "MITRE-ATTACK" and compliance.Version == "": - write_compliance_row_mitre_attack( - file_descriptors, finding, compliance, provider - ) - else: write_compliance_row_generic( file_descriptors, finding, compliance, output_options, provider diff --git a/prowler/lib/outputs/compliance/mitre_attack/mitre_attack.py b/prowler/lib/outputs/compliance/mitre_attack/mitre_attack.py index d072e43f0a..24c78e9469 100644 --- a/prowler/lib/outputs/compliance/mitre_attack/mitre_attack.py +++ b/prowler/lib/outputs/compliance/mitre_attack/mitre_attack.py @@ -1,101 +1,7 @@ -from csv import DictWriter -from importlib import import_module - from colorama import Fore, Style from tabulate import tabulate -from prowler.config.config import orange_color, timestamp -from prowler.lib.logger import logger -from prowler.lib.outputs.csv.csv import generate_csv_fields -from prowler.lib.outputs.utils import unroll_list -from prowler.lib.utils.utils import outputs_unix_timestamp - - -def write_compliance_row_mitre_attack(file_descriptors, finding, compliance, provider): - try: - compliance_output = compliance.Framework - if compliance.Version != "": - compliance_output += "_" + compliance.Version - if compliance.Provider != "": - compliance_output += "_" + compliance.Provider - - mitre_attack_model_name = "MitreAttack" + compliance.Provider - module = import_module("prowler.lib.outputs.compliance.mitre_attack.models") - mitre_attack_model = getattr(module, mitre_attack_model_name) - compliance_output = compliance_output.lower().replace("-", "_") - csv_header = generate_csv_fields(mitre_attack_model) - csv_writer = DictWriter( - file_descriptors[compliance_output], - fieldnames=csv_header, - delimiter=";", - ) - for requirement in compliance.Requirements: - - if compliance.Provider == "AWS": - attributes_services = ", ".join( - attribute.AWSService for attribute in requirement.Attributes - ) - elif compliance.Provider == "Azure": - attributes_services = ", ".join( - attribute.AzureService for attribute in requirement.Attributes - ) - elif compliance.Provider == "GCP": - attributes_services = ", ".join( - attribute.GCPService for attribute in requirement.Attributes - ) - requirement_description = requirement.Description - requirement_id = requirement.Id - requirement_name = requirement.Name - attributes_categories = ", ".join( - attribute.Category for attribute in requirement.Attributes - ) - attributes_values = ", ".join( - attribute.Value for attribute in requirement.Attributes - ) - attributes_comments = ", ".join( - attribute.Comment for attribute in requirement.Attributes - ) - - common_data = { - "Provider": finding.check_metadata.Provider, - "Description": compliance.Description, - "AssessmentDate": outputs_unix_timestamp( - provider.output_options.unix_timestamp, timestamp - ), - "Requirements_Id": requirement_id, - "Requirements_Name": requirement_name, - "Requirements_Description": requirement_description, - "Requirements_Tactics": unroll_list(requirement.Tactics), - "Requirements_SubTechniques": unroll_list(requirement.SubTechniques), - "Requirements_Platforms": unroll_list(requirement.Platforms), - "Requirements_TechniqueURL": requirement.TechniqueURL, - "Requirements_Attributes_Services": attributes_services, - "Requirements_Attributes_Categories": attributes_categories, - "Requirements_Attributes_Values": attributes_values, - "Requirements_Attributes_Comments": attributes_comments, - "Status": finding.status, - "StatusExtended": finding.status_extended, - "ResourceId": finding.resource_id, - "CheckId": finding.check_metadata.CheckID, - "Muted": finding.muted, - } - if compliance.Provider == "AWS": - common_data["AccountId"] = provider.identity.account - common_data["Region"] = finding.region - elif compliance.Provider == "Azure": - common_data["SubscriptionId"] = unroll_list( - provider.identity.subscriptions - ) - elif compliance.Provider == "GCP": - common_data["ProjectId"] = unroll_list(provider.projects) - - compliance_row = mitre_attack_model(**common_data) - - csv_writer.writerow(compliance_row.__dict__) - except Exception as error: - logger.error( - f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" - ) +from prowler.config.config import orange_color def get_mitre_attack_table( diff --git a/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_aws.py b/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_aws.py new file mode 100644 index 0000000000..921bdca329 --- /dev/null +++ b/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_aws.py @@ -0,0 +1,109 @@ +from csv import DictWriter +from venv import logger + +from prowler.lib.check.compliance_models import ComplianceBaseModel +from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput +from prowler.lib.outputs.compliance.mitre_attack.models import MitreAttackAWS +from prowler.lib.outputs.finding import Finding +from prowler.lib.outputs.utils import unroll_list + + +class AWSMitreAttack(ComplianceOutput): + """ + This class represents the AWS MITRE ATT&CK compliance output. + + Attributes: + - _data (list): A list to store transformed data from findings. + - _file_descriptor (TextIOWrapper): A file descriptor to write data to a file. + + Methods: + - transform: Transforms findings into AWS MITRE ATT&CK compliance format. + - batch_write_data_to_file: Writes the findings data to a CSV file in AWS MITRE ATT&CK compliance format. + """ + + def transform( + self, + findings: list[Finding], + compliance: ComplianceBaseModel, + compliance_name: str, + ) -> None: + """ + Transforms a list of findings into AWS MITRE ATT&CK compliance format. + + Parameters: + - findings (list): A list of findings. + - compliance (ComplianceBaseModel): A compliance model. + - compliance_name (str): The name of the compliance model. + + Returns: + - None + """ + for finding in findings: + # Get the compliance requirements for the finding + finding_requirements = finding.compliance.get(compliance_name, []) + for requirement in compliance.Requirements: + if requirement.Id in finding_requirements: + compliance_row = MitreAttackAWS( + Provider=finding.provider, + Description=compliance.Description, + AccountId=finding.account_uid, + Region=finding.region, + AssessmentDate=str(finding.timestamp), + Requirements_Id=requirement.Id, + Requirements_Name=requirement.Name, + Requirements_Description=requirement.Description, + Requirements_Tactics=unroll_list(requirement.Tactics), + Requirements_SubTechniques=unroll_list( + requirement.SubTechniques + ), + Requirements_Platforms=unroll_list(requirement.Platforms), + Requirements_TechniqueURL=requirement.TechniqueURL, + Requirements_Attributes_Services=", ".join( + attribute.AWSService for attribute in requirement.Attributes + ), + Requirements_Attributes_Categories=", ".join( + attribute.Category for attribute in requirement.Attributes + ), + Requirements_Attributes_Values=", ".join( + attribute.Value for attribute in requirement.Attributes + ), + Requirements_Attributes_Comments=", ".join( + attribute.Comment for attribute in requirement.Attributes + ), + Status=finding.status, + StatusExtended=finding.status_extended, + ResourceId=finding.resource_uid, + ResourceName=finding.resource_name, + CheckId=finding.check_id, + Muted=finding.muted, + ) + self._data.append(compliance_row) + + def batch_write_data_to_file(self) -> None: + """ + Writes the findings data to a CSV file in AWS MITRE ATT&CK compliance format. + + Returns: + - None + """ + try: + if ( + getattr(self, "_file_descriptor", None) + and not self._file_descriptor.closed + and self._data + ): + csv_writer = DictWriter( + self._file_descriptor, + fieldnames=[field.upper() for field in self._data[0].dict().keys()], + delimiter=";", + ) + csv_writer.writeheader() + for finding in self._data: + csv_writer.writerow( + {k.upper(): v for k, v in finding.dict().items()} + ) + self._file_descriptor.close() + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) diff --git a/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_azure.py b/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_azure.py new file mode 100644 index 0000000000..e6cd50b391 --- /dev/null +++ b/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_azure.py @@ -0,0 +1,110 @@ +from csv import DictWriter +from venv import logger + +from prowler.lib.check.compliance_models import ComplianceBaseModel +from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput +from prowler.lib.outputs.compliance.mitre_attack.models import MitreAttackAzure +from prowler.lib.outputs.finding import Finding +from prowler.lib.outputs.utils import unroll_list + + +class AzureMitreAttack(ComplianceOutput): + """ + This class represents the Azure MITRE ATT&CK compliance output. + + Attributes: + - _data (list): A list to store transformed data from findings. + - _file_descriptor (TextIOWrapper): A file descriptor to write data to a file. + + Methods: + - transform: Transforms findings into Azure MITRE ATT&CK compliance format. + - batch_write_data_to_file: Writes the findings data to a CSV file in Azure MITRE ATT&CK compliance format. + """ + + def transform( + self, + findings: list[Finding], + compliance: ComplianceBaseModel, + compliance_name: str, + ) -> None: + """ + Transforms a list of findings into Azure MITRE ATT&CK compliance format. + + Parameters: + - findings (list): A list of findings. + - compliance (ComplianceBaseModel): A compliance model. + - compliance_name (str): The name of the compliance model. + + Returns: + - None + """ + for finding in findings: + # Get the compliance requirements for the finding + finding_requirements = finding.compliance.get(compliance_name, []) + for requirement in compliance.Requirements: + if requirement.Id in finding_requirements: + compliance_row = MitreAttackAzure( + Provider=finding.provider, + Description=compliance.Description, + SubscriptionId=finding.account_uid, + Location=finding.region, + AssessmentDate=str(finding.timestamp), + Requirements_Id=requirement.Id, + Requirements_Name=requirement.Name, + Requirements_Description=requirement.Description, + Requirements_Tactics=unroll_list(requirement.Tactics), + Requirements_SubTechniques=unroll_list( + requirement.SubTechniques + ), + Requirements_Platforms=unroll_list(requirement.Platforms), + Requirements_TechniqueURL=requirement.TechniqueURL, + Requirements_Attributes_Services=", ".join( + attribute.AzureService + for attribute in requirement.Attributes + ), + Requirements_Attributes_Categories=", ".join( + attribute.Category for attribute in requirement.Attributes + ), + Requirements_Attributes_Values=", ".join( + attribute.Value for attribute in requirement.Attributes + ), + Requirements_Attributes_Comments=", ".join( + attribute.Comment for attribute in requirement.Attributes + ), + Status=finding.status, + StatusExtended=finding.status_extended, + ResourceId=finding.resource_uid, + ResourceName=finding.resource_name, + CheckId=finding.check_id, + Muted=finding.muted, + ) + self._data.append(compliance_row) + + def batch_write_data_to_file(self) -> None: + """ + Writes the findings data to a CSV file in Azure MITRE ATT&CK compliance format. + + Returns: + - None + """ + try: + if ( + getattr(self, "_file_descriptor", None) + and not self._file_descriptor.closed + and self._data + ): + csv_writer = DictWriter( + self._file_descriptor, + fieldnames=[field.upper() for field in self._data[0].dict().keys()], + delimiter=";", + ) + csv_writer.writeheader() + for finding in self._data: + csv_writer.writerow( + {k.upper(): v for k, v in finding.dict().items()} + ) + self._file_descriptor.close() + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) diff --git a/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_gcp.py b/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_gcp.py new file mode 100644 index 0000000000..ccc55ebf57 --- /dev/null +++ b/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_gcp.py @@ -0,0 +1,109 @@ +from csv import DictWriter +from venv import logger + +from prowler.lib.check.compliance_models import ComplianceBaseModel +from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput +from prowler.lib.outputs.compliance.mitre_attack.models import MitreAttackGCP +from prowler.lib.outputs.finding import Finding +from prowler.lib.outputs.utils import unroll_list + + +class GCPMitreAttack(ComplianceOutput): + """ + This class represents the GCP MITRE ATT&CK compliance output. + + Attributes: + - _data (list): A list to store transformed data from findings. + - _file_descriptor (TextIOWrapper): A file descriptor to write data to a file. + + Methods: + - transform: Transforms findings into GCP MITRE ATT&CK compliance format. + - batch_write_data_to_file: Writes the findings data to a CSV file in GCP MITRE ATT&CK compliance format. + """ + + def transform( + self, + findings: list[Finding], + compliance: ComplianceBaseModel, + compliance_name: str, + ) -> None: + """ + Transforms a list of findings into GCP MITRE ATT&CK compliance format. + + Parameters: + - findings (list): A list of findings. + - compliance (ComplianceBaseModel): A compliance model. + - compliance_name (str): The name of the compliance model. + + Returns: + - None + """ + for finding in findings: + # Get the compliance requirements for the finding + finding_requirements = finding.compliance.get(compliance_name, []) + for requirement in compliance.Requirements: + if requirement.Id in finding_requirements: + compliance_row = MitreAttackGCP( + Provider=finding.provider, + Description=compliance.Description, + ProjectId=finding.account_uid, + Location=finding.region, + AssessmentDate=str(finding.timestamp), + Requirements_Id=requirement.Id, + Requirements_Name=requirement.Name, + Requirements_Description=requirement.Description, + Requirements_Tactics=unroll_list(requirement.Tactics), + Requirements_SubTechniques=unroll_list( + requirement.SubTechniques + ), + Requirements_Platforms=unroll_list(requirement.Platforms), + Requirements_TechniqueURL=requirement.TechniqueURL, + Requirements_Attributes_Services=", ".join( + attribute.GCPService for attribute in requirement.Attributes + ), + Requirements_Attributes_Categories=", ".join( + attribute.Category for attribute in requirement.Attributes + ), + Requirements_Attributes_Values=", ".join( + attribute.Value for attribute in requirement.Attributes + ), + Requirements_Attributes_Comments=", ".join( + attribute.Comment for attribute in requirement.Attributes + ), + Status=finding.status, + StatusExtended=finding.status_extended, + ResourceId=finding.resource_uid, + ResourceName=finding.resource_name, + CheckId=finding.check_id, + Muted=finding.muted, + ) + self._data.append(compliance_row) + + def batch_write_data_to_file(self) -> None: + """ + Writes the findings data to a CSV file in GCP MITRE ATT&CK compliance format. + + Returns: + - None + """ + try: + if ( + getattr(self, "_file_descriptor", None) + and not self._file_descriptor.closed + and self._data + ): + csv_writer = DictWriter( + self._file_descriptor, + fieldnames=[field.upper() for field in self._data[0].dict().keys()], + delimiter=";", + ) + csv_writer.writeheader() + for finding in self._data: + csv_writer.writerow( + {k.upper(): v for k, v in finding.dict().items()} + ) + self._file_descriptor.close() + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) diff --git a/prowler/lib/outputs/compliance/mitre_attack/models.py b/prowler/lib/outputs/compliance/mitre_attack/models.py index a2e38d3929..c3ee7192d2 100644 --- a/prowler/lib/outputs/compliance/mitre_attack/models.py +++ b/prowler/lib/outputs/compliance/mitre_attack/models.py @@ -27,6 +27,7 @@ class MitreAttackAWS(BaseModel): ResourceId: str CheckId: str Muted: bool + ResourceName: str class MitreAttackAzure(BaseModel): @@ -54,6 +55,8 @@ class MitreAttackAzure(BaseModel): ResourceId: str CheckId: str Muted: bool + ResourceName: str + Location: str class MitreAttackGCP(BaseModel): @@ -81,3 +84,9 @@ class MitreAttackGCP(BaseModel): ResourceId: str CheckId: str Muted: bool + ResourceName: str + Location: str + + +# TODO: Create a parent class for the common fields of MITRE ATT&CK and have the specific classes from each provider to inherit from it. +# It is not done yet because it is needed to respect the current order of the fields in the output file. diff --git a/prowler/lib/outputs/file_descriptors.py b/prowler/lib/outputs/file_descriptors.py index 1869a7de56..4f6406285b 100644 --- a/prowler/lib/outputs/file_descriptors.py +++ b/prowler/lib/outputs/file_descriptors.py @@ -4,11 +4,6 @@ from prowler.config.config import csv_file_suffix from prowler.lib.logger import logger -from prowler.lib.outputs.compliance.mitre_attack.models import ( - MitreAttackAWS, - MitreAttackAzure, - MitreAttackGCP, -) from prowler.lib.outputs.compliance.models import ( Check_Output_CSV_AWS_ISO27001_2013, Check_Output_CSV_AWS_Well_Architected, @@ -22,12 +17,9 @@ def initialize_file_descriptor( filename: str, - output_mode: str, - provider: Any = None, format: Any = Finding, - write_header: bool = True, ) -> TextIOWrapper: - """Open/Create the output file. If needed include headers or the required format, by default will use the Finding""" + """Open/Create the output file. If needed include headers or the required format, by default will use the FindingOutput""" try: if file_exists(filename): file_descriptor = open_file( @@ -44,8 +36,7 @@ def initialize_file_descriptor( csv_writer = DictWriter( file_descriptor, fieldnames=csv_header, delimiter=";" ) - if write_header: - csv_writer.writeheader() + csv_writer.writeheader() return file_descriptor except Exception as error: logger.error( @@ -70,54 +61,32 @@ def fill_file_descriptors(output_modes, output_directory, output_filename, provi # FIXME: Remove this once we merge all the compliance frameworks if "cis_" in output_mode: continue + elif "mitre_attack_" in output_mode: + continue elif provider.type == "gcp": filename = f"{output_directory}/compliance/{output_filename}_{output_mode}{csv_file_suffix}" - if output_mode == "mitre_attack_gcp": - file_descriptor = initialize_file_descriptor( - filename, - output_mode, - provider.type, - MitreAttackGCP, - ) - file_descriptors.update({output_mode: file_descriptor}) - else: - file_descriptor = initialize_file_descriptor( - filename, - output_mode, - provider.type, - Check_Output_CSV_Generic_Compliance, - ) - file_descriptors.update({output_mode: file_descriptor}) + file_descriptor = initialize_file_descriptor( + filename, + Check_Output_CSV_Generic_Compliance, + ) + file_descriptors.update({output_mode: file_descriptor}) elif provider.type == "kubernetes": filename = f"{output_directory}/compliance/{output_filename}_{output_mode}{csv_file_suffix}" file_descriptor = initialize_file_descriptor( filename, - output_mode, - provider.type, Check_Output_CSV_Generic_Compliance, ) file_descriptors.update({output_mode: file_descriptor}) elif provider.type == "azure": filename = f"{output_directory}/compliance/{output_filename}_{output_mode}{csv_file_suffix}" - if output_mode == "mitre_attack_azure": - file_descriptor = initialize_file_descriptor( - filename, - output_mode, - provider.type, - MitreAttackAzure, - ) - file_descriptors.update({output_mode: file_descriptor}) - else: - file_descriptor = initialize_file_descriptor( - filename, - output_mode, - provider.type, - Check_Output_CSV_Generic_Compliance, - ) - file_descriptors.update({output_mode: file_descriptor}) + file_descriptor = initialize_file_descriptor( + filename, + Check_Output_CSV_Generic_Compliance, + ) + file_descriptors.update({output_mode: file_descriptor}) elif provider.type == "aws": # Compliance frameworks @@ -125,8 +94,6 @@ def fill_file_descriptors(output_modes, output_directory, output_filename, provi if output_mode == "ens_rd2022_aws": file_descriptor = initialize_file_descriptor( filename, - output_mode, - provider.type, Check_Output_CSV_ENS_RD2022, ) file_descriptors.update({output_mode: file_descriptor}) @@ -134,8 +101,6 @@ def fill_file_descriptors(output_modes, output_directory, output_filename, provi elif "aws_well_architected_framework" in output_mode: file_descriptor = initialize_file_descriptor( filename, - output_mode, - provider.type, Check_Output_CSV_AWS_Well_Architected, ) file_descriptors.update({output_mode: file_descriptor}) @@ -143,27 +108,14 @@ def fill_file_descriptors(output_modes, output_directory, output_filename, provi elif output_mode == "iso27001_2013_aws": file_descriptor = initialize_file_descriptor( filename, - output_mode, - provider.type, Check_Output_CSV_AWS_ISO27001_2013, ) file_descriptors.update({output_mode: file_descriptor}) - elif output_mode == "mitre_attack_aws": - file_descriptor = initialize_file_descriptor( - filename, - output_mode, - provider.type, - MitreAttackAWS, - ) - file_descriptors.update({output_mode: file_descriptor}) - else: # Generic Compliance framework file_descriptor = initialize_file_descriptor( filename, - output_mode, - provider.type, Check_Output_CSV_Generic_Compliance, ) file_descriptors.update({output_mode: file_descriptor}) diff --git a/tests/lib/outputs/compliance/cis_aws_test.py b/tests/lib/outputs/compliance/cis/cis_aws_test.py similarity index 99% rename from tests/lib/outputs/compliance/cis_aws_test.py rename to tests/lib/outputs/compliance/cis/cis_aws_test.py index 1f2d4653b4..f5e8da2842 100644 --- a/tests/lib/outputs/compliance/cis_aws_test.py +++ b/tests/lib/outputs/compliance/cis/cis_aws_test.py @@ -78,7 +78,6 @@ def test_output_transform(self): def test_batch_write_data_to_file(self): mock_file = StringIO() findings = [generate_finding_output(compliance={"CIS-1.4": "2.1.3"})] - # Clear the data from CSV class output = AWSCIS(findings, CIS_1_4_AWS) output._file_descriptor = mock_file diff --git a/tests/lib/outputs/compliance/cis_azure_test.py b/tests/lib/outputs/compliance/cis/cis_azure_test.py similarity index 100% rename from tests/lib/outputs/compliance/cis_azure_test.py rename to tests/lib/outputs/compliance/cis/cis_azure_test.py diff --git a/tests/lib/outputs/compliance/cis_gcp_test.py b/tests/lib/outputs/compliance/cis/cis_gcp_test.py similarity index 100% rename from tests/lib/outputs/compliance/cis_gcp_test.py rename to tests/lib/outputs/compliance/cis/cis_gcp_test.py diff --git a/tests/lib/outputs/compliance/cis_kubernetes_test.py b/tests/lib/outputs/compliance/cis/cis_kubernetes_test.py similarity index 100% rename from tests/lib/outputs/compliance/cis_kubernetes_test.py rename to tests/lib/outputs/compliance/cis/cis_kubernetes_test.py diff --git a/tests/lib/outputs/compliance/fixtures.py b/tests/lib/outputs/compliance/fixtures.py index f918eee7c8..eb2ccbe3f8 100644 --- a/tests/lib/outputs/compliance/fixtures.py +++ b/tests/lib/outputs/compliance/fixtures.py @@ -2,6 +2,10 @@ CIS_Requirement_Attribute, Compliance_Requirement, ComplianceBaseModel, + Mitre_Requirement, + Mitre_Requirement_Attribute_AWS, + Mitre_Requirement_Attribute_Azure, + Mitre_Requirement_Attribute_GCP, ) CIS_1_4_AWS_NAME = "cis_1.4_aws" @@ -147,6 +151,138 @@ ], ) +MITRE_ATTACK_AWS_NAME = "mitre_attack_aws" +MITRE_ATTACK_AWS = ComplianceBaseModel( + Framework="MITRE-ATTACK", + Provider="AWS", + Version="", + Description="MITRE ATT&CK® is a globally-accessible knowledge base of adversary tactics and techniques based on real-world observations. The ATT&CK knowledge base is used as a foundation for the development of specific threat models and methodologies in the private sector, in government, and in the cybersecurity product and service community.", + Requirements=[ + Mitre_Requirement( + Name="Exploit Public-Facing Application", + Id="T1190", + Tactics=["Initial Access"], + SubTechniques=[], + Description="Adversaries may attempt to exploit a weakness in an Internet-facing host or system to initially access a network. The weakness in the system can be a software bug, a temporary glitch, or a misconfiguration.", + Platforms=["Containers", "IaaS", "Linux", "Network", "Windows", "macOS"], + TechniqueURL="https://attack.mitre.org/techniques/T1190/", + Attributes=[ + Mitre_Requirement_Attribute_AWS( + AWSService="AWS CloudEndure Disaster Recovery", + Category="Respond", + Value="Significant", + Comment="AWS CloudEndure Disaster Recovery enables the replication and recovery of servers into AWS Cloud. In the event that a public-facing application or server is compromised, AWS CloudEndure can be used to provision an instance of the server from a previous point in time within minutes. As a result, this mapping is given a score of Significant.", + ) + ], + Checks=[ + "drs_job_exist", + "config_recorder_all_regions_enabled", + "rds_instance_minor_version_upgrade_enabled", + "rds_instance_backup_enabled", + "securityhub_enabled", + "elbv2_waf_acl_attached", + "guardduty_is_enabled", + "inspector2_is_enabled", + "inspector2_active_findings_exist", + "awslambda_function_not_publicly_accessible", + "ec2_instance_public_ip", + ], + ) + ], +) +MITRE_ATTACK_AZURE_NAME = "mitre_attack_azure" +MITRE_ATTACK_AZURE = ComplianceBaseModel( + Framework="MITRE-ATTACK", + Provider="Azure", + Version="", + Description="MITRE ATT&CK® is a globally-accessible knowledge base of adversary tactics and techniques based on real-world observations. The ATT&CK knowledge base is used as a foundation for the development of specific threat models and methodologies in the private sector, in government, and in the cybersecurity product and service community.", + Requirements=[ + Mitre_Requirement( + Name="Exploit Public-Facing Application", + Id="T1190", + Tactics=["Initial Access"], + SubTechniques=[], + Description="Adversaries may attempt to exploit a weakness in an Internet-facing host or system to initially access a network. The weakness in the system can be a software bug, a temporary glitch, or a misconfiguration.", + Platforms=["Containers", "IaaS", "Linux", "Network", "Windows", "macOS"], + TechniqueURL="https://attack.mitre.org/techniques/T1190/", + Attributes=[ + Mitre_Requirement_Attribute_Azure( + AzureService="Azure SQL Database", + Category="Detect", + Value="Minimal", + Comment="This control may alert on usage of faulty SQL statements. This generates an alert for a possible SQL injection by an application. Alerts may not be generated on usage of valid SQL statements by attackers for malicious purposes.", + ) + ], + Checks=[ + "aks_clusters_created_with_private_nodes", + "aks_clusters_public_access_disabled", + "app_ensure_java_version_is_latest", + "app_ensure_php_version_is_latest", + "app_ensure_python_version_is_latest", + "defender_assessments_vm_endpoint_protection_installed", + "defender_assessments_vm_endpoint_protection_installed", + "defender_auto_provisioning_log_analytics_agent_vms_on", + "defender_auto_provisioning_vulnerabilty_assessments_machines_on", + "defender_container_images_resolved_vulnerabilities", + "defender_container_images_scan_enabled", + "defender_ensure_defender_for_app_services_is_on", + "defender_ensure_defender_for_arm_is_on", + "defender_ensure_defender_for_azure_sql_databases_is_on", + "defender_ensure_defender_for_containers_is_on", + "defender_ensure_defender_for_cosmosdb_is_on", + "defender_ensure_defender_for_databases_is_on", + "defender_ensure_defender_for_dns_is_on", + "defender_ensure_defender_for_keyvault_is_on", + "defender_ensure_defender_for_os_relational_databases_is_on", + "defender_ensure_defender_for_server_is_on", + "defender_ensure_defender_for_sql_servers_is_on", + "defender_ensure_defender_for_storage_is_on", + "defender_ensure_iot_hub_defender_is_on", + "defender_ensure_mcas_is_enabled", + "defender_ensure_notify_alerts_severity_is_high", + "defender_ensure_notify_emails_to_owners", + "defender_ensure_system_updates_are_applied", + "defender_ensure_wdatp_is_enabled", + ], + ) + ], +) +MITRE_ATTACK_GCP_NAME = "mitre_attack_gcp" +MITRE_ATTACK_GCP = ComplianceBaseModel( + Framework="MITRE-ATTACK", + Provider="GCP", + Version="", + Description="MITRE ATT&CK® is a globally-accessible knowledge base of adversary tactics and techniques based on real-world observations. The ATT&CK knowledge base is used as a foundation for the development of specific threat models and methodologies in the private sector, in government, and in the cybersecurity product and service community.", + Requirements=[ + Mitre_Requirement( + Name="Exploit Public-Facing Application", + Id="T1190", + Tactics=["Initial Access"], + SubTechniques=[], + Description="Adversaries may attempt to exploit a weakness in an Internet-facing host or system to initially access a network. The weakness in the system can be a software bug, a temporary glitch, or a misconfiguration.", + Platforms=["Containers", "IaaS", "Linux", "Network", "Windows", "macOS"], + TechniqueURL="https://attack.mitre.org/techniques/T1190/", + Attributes=[ + Mitre_Requirement_Attribute_GCP( + GCPService="Artifact Registry", + Category="Protect", + Value="Partial", + Comment="Once this control is deployed, it can detect known vulnerabilities in various Linux OS packages. This information can be used to patch, isolate, or remove vulnerable software and machines. This control does not directly protect against exploitation and is not effective against zero day attacks, vulnerabilities with no available patch, and other end-of-life packages.", + ) + ], + Checks=[ + "cloudsql_instance_public_access", + "cloudsql_instance_public_ip", + "cloudstorage_bucket_public_access", + "compute_firewall_rdp_access_from_the_internet_allowed", + "compute_firewall_ssh_access_from_the_internet_allowed", + "compute_instance_public_ip", + "compute_public_address_shodan", + "kms_key_not_publicly_accessible", + ], + ) + ], +) NOT_PRESENT_COMPLIANCE_NAME = "not_present_compliance_name" NOT_PRESENT_COMPLIANCE = ComplianceBaseModel( Framework="NOT_EXISTENT", diff --git a/tests/lib/outputs/compliance/mitre_attack/mitre_attack_aws_test.py b/tests/lib/outputs/compliance/mitre_attack/mitre_attack_aws_test.py new file mode 100644 index 0000000000..1cfa5de1ba --- /dev/null +++ b/tests/lib/outputs/compliance/mitre_attack/mitre_attack_aws_test.py @@ -0,0 +1,81 @@ +from datetime import datetime +from io import StringIO + +from freezegun import freeze_time +from mock import patch + +from prowler.lib.outputs.compliance.mitre_attack.mitre_attack_aws import AWSMitreAttack +from prowler.lib.outputs.compliance.mitre_attack.models import MitreAttackAWS +from prowler.lib.outputs.utils import unroll_list +from tests.lib.outputs.compliance.fixtures import MITRE_ATTACK_AWS +from tests.lib.outputs.fixtures.fixtures import generate_finding_output +from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_EU_WEST_1 + + +class TestAWSCIS: + def test_output_transform(self): + findings = [generate_finding_output(compliance={"MITRE-ATTACK": "T1190"})] + + output = AWSMitreAttack(findings, MITRE_ATTACK_AWS) + output_data = output.data[0] + assert isinstance(output_data, MitreAttackAWS) + assert output_data.Provider == "aws" + assert output_data.Description == MITRE_ATTACK_AWS.Description + assert output_data.AccountId == AWS_ACCOUNT_NUMBER + assert output_data.Region == AWS_REGION_EU_WEST_1 + assert output_data.Requirements_Id == MITRE_ATTACK_AWS.Requirements[0].Id + assert output_data.Requirements_Name == MITRE_ATTACK_AWS.Requirements[0].Name + assert ( + output_data.Requirements_Description + == MITRE_ATTACK_AWS.Requirements[0].Description + ) + assert output_data.Requirements_Tactics == unroll_list( + MITRE_ATTACK_AWS.Requirements[0].Tactics + ) + assert output_data.Requirements_SubTechniques == unroll_list( + MITRE_ATTACK_AWS.Requirements[0].SubTechniques + ) + assert output_data.Requirements_Platforms == unroll_list( + MITRE_ATTACK_AWS.Requirements[0].Platforms + ) + assert ( + output_data.Requirements_TechniqueURL + == MITRE_ATTACK_AWS.Requirements[0].TechniqueURL + ) + assert output_data.Requirements_Attributes_Services == ", ".join( + attribute.AWSService + for attribute in MITRE_ATTACK_AWS.Requirements[0].Attributes + ) + assert output_data.Requirements_Attributes_Categories == ", ".join( + attribute.Category + for attribute in MITRE_ATTACK_AWS.Requirements[0].Attributes + ) + assert output_data.Requirements_Attributes_Values == ", ".join( + attribute.Value for attribute in MITRE_ATTACK_AWS.Requirements[0].Attributes + ) + assert output_data.Requirements_Attributes_Comments == ", ".join( + attribute.Comment + for attribute in MITRE_ATTACK_AWS.Requirements[0].Attributes + ) + assert output_data.Status == "PASS" + assert output_data.StatusExtended == "" + assert output_data.ResourceId == "" + assert output_data.ResourceName == "" + assert output_data.CheckId == "test-check-id" + assert not output_data.Muted + + @freeze_time(datetime.now()) + def test_batch_write_data_to_file(self): + mock_file = StringIO() + findings = [generate_finding_output(compliance={"MITRE-ATTACK": "T1190"})] + output = AWSMitreAttack(findings, MITRE_ATTACK_AWS) + output._file_descriptor = mock_file + + with patch.object(mock_file, "close", return_value=None): + output.batch_write_data_to_file() + + mock_file.seek(0) + content = mock_file.read() + print(repr(content) + "\n") + expected_csv = f"PROVIDER;DESCRIPTION;ACCOUNTID;REGION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_NAME;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_TACTICS;REQUIREMENTS_SUBTECHNIQUES;REQUIREMENTS_PLATFORMS;REQUIREMENTS_TECHNIQUEURL;REQUIREMENTS_ATTRIBUTES_SERVICES;REQUIREMENTS_ATTRIBUTES_CATEGORIES;REQUIREMENTS_ATTRIBUTES_VALUES;REQUIREMENTS_ATTRIBUTES_COMMENTS;STATUS;STATUSEXTENDED;RESOURCEID;CHECKID;MUTED;RESOURCENAME\r\naws;MITRE ATT&CK® is a globally-accessible knowledge base of adversary tactics and techniques based on real-world observations. The ATT&CK knowledge base is used as a foundation for the development of specific threat models and methodologies in the private sector, in government, and in the cybersecurity product and service community.;123456789012;eu-west-1;{datetime.now()};T1190;Exploit Public-Facing Application;Adversaries may attempt to exploit a weakness in an Internet-facing host or system to initially access a network. The weakness in the system can be a software bug, a temporary glitch, or a misconfiguration.;Initial Access;;Containers | IaaS | Linux | Network | Windows | macOS;https://attack.mitre.org/techniques/T1190/;AWS CloudEndure Disaster Recovery;Respond;Significant;AWS CloudEndure Disaster Recovery enables the replication and recovery of servers into AWS Cloud. In the event that a public-facing application or server is compromised, AWS CloudEndure can be used to provision an instance of the server from a previous point in time within minutes. As a result, this mapping is given a score of Significant.;PASS;;;test-check-id;False;\r\n" + assert content == expected_csv diff --git a/tests/lib/outputs/compliance/mitre_attack/mitre_attack_azure_test.py b/tests/lib/outputs/compliance/mitre_attack/mitre_attack_azure_test.py new file mode 100644 index 0000000000..97088cb842 --- /dev/null +++ b/tests/lib/outputs/compliance/mitre_attack/mitre_attack_azure_test.py @@ -0,0 +1,102 @@ +from datetime import datetime +from io import StringIO + +from freezegun import freeze_time +from mock import patch + +from prowler.lib.outputs.compliance.mitre_attack.mitre_attack_azure import ( + AzureMitreAttack, +) +from prowler.lib.outputs.compliance.mitre_attack.models import MitreAttackAzure +from prowler.lib.outputs.utils import unroll_list +from tests.lib.outputs.compliance.fixtures import MITRE_ATTACK_AZURE +from tests.lib.outputs.fixtures.fixtures import generate_finding_output +from tests.providers.azure.azure_fixtures import ( + AZURE_SUBSCRIPTION_ID, + AZURE_SUBSCRIPTION_NAME, +) + + +class TestAzureCIS: + def test_output_transform(self): + findings = [ + generate_finding_output( + provider="azure", + compliance={"MITRE-ATTACK": "T1190"}, + account_name=AZURE_SUBSCRIPTION_NAME, + account_uid=AZURE_SUBSCRIPTION_ID, + region="", + ) + ] + + output = AzureMitreAttack(findings, MITRE_ATTACK_AZURE) + output_data = output.data[0] + assert isinstance(output_data, MitreAttackAzure) + assert output_data.Provider == "azure" + assert output_data.Description == MITRE_ATTACK_AZURE.Description + assert output_data.SubscriptionId == AZURE_SUBSCRIPTION_ID + assert output_data.Location == "" + assert output_data.Requirements_Id == MITRE_ATTACK_AZURE.Requirements[0].Id + assert output_data.Requirements_Name == MITRE_ATTACK_AZURE.Requirements[0].Name + assert ( + output_data.Requirements_Description + == MITRE_ATTACK_AZURE.Requirements[0].Description + ) + assert output_data.Requirements_Tactics == unroll_list( + MITRE_ATTACK_AZURE.Requirements[0].Tactics + ) + assert output_data.Requirements_SubTechniques == unroll_list( + MITRE_ATTACK_AZURE.Requirements[0].SubTechniques + ) + assert output_data.Requirements_Platforms == unroll_list( + MITRE_ATTACK_AZURE.Requirements[0].Platforms + ) + assert ( + output_data.Requirements_TechniqueURL + == MITRE_ATTACK_AZURE.Requirements[0].TechniqueURL + ) + assert output_data.Requirements_Attributes_Services == ", ".join( + attribute.AzureService + for attribute in MITRE_ATTACK_AZURE.Requirements[0].Attributes + ) + assert output_data.Requirements_Attributes_Categories == ", ".join( + attribute.Category + for attribute in MITRE_ATTACK_AZURE.Requirements[0].Attributes + ) + assert output_data.Requirements_Attributes_Values == ", ".join( + attribute.Value + for attribute in MITRE_ATTACK_AZURE.Requirements[0].Attributes + ) + assert output_data.Requirements_Attributes_Comments == ", ".join( + attribute.Comment + for attribute in MITRE_ATTACK_AZURE.Requirements[0].Attributes + ) + assert output_data.Status == "PASS" + assert output_data.StatusExtended == "" + assert output_data.ResourceId == "" + assert output_data.ResourceName == "" + assert output_data.CheckId == "test-check-id" + assert not output_data.Muted + + @freeze_time(datetime.now()) + def test_batch_write_data_to_file(self): + mock_file = StringIO() + findings = [ + generate_finding_output( + provider="azure", + compliance={"MITRE-ATTACK": "T1190"}, + account_name=AZURE_SUBSCRIPTION_NAME, + account_uid=AZURE_SUBSCRIPTION_ID, + region="", + ) + ] + output = AzureMitreAttack(findings, MITRE_ATTACK_AZURE) + output._file_descriptor = mock_file + + with patch.object(mock_file, "close", return_value=None): + output.batch_write_data_to_file() + + mock_file.seek(0) + content = mock_file.read() + expected_csv = f"PROVIDER;DESCRIPTION;SUBSCRIPTIONID;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_NAME;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_TACTICS;REQUIREMENTS_SUBTECHNIQUES;REQUIREMENTS_PLATFORMS;REQUIREMENTS_TECHNIQUEURL;REQUIREMENTS_ATTRIBUTES_SERVICES;REQUIREMENTS_ATTRIBUTES_CATEGORIES;REQUIREMENTS_ATTRIBUTES_VALUES;REQUIREMENTS_ATTRIBUTES_COMMENTS;STATUS;STATUSEXTENDED;RESOURCEID;CHECKID;MUTED;RESOURCENAME;LOCATION\r\nazure;MITRE ATT&CK® is a globally-accessible knowledge base of adversary tactics and techniques based on real-world observations. The ATT&CK knowledge base is used as a foundation for the development of specific threat models and methodologies in the private sector, in government, and in the cybersecurity product and service community.;{AZURE_SUBSCRIPTION_ID};{datetime.now()};T1190;Exploit Public-Facing Application;Adversaries may attempt to exploit a weakness in an Internet-facing host or system to initially access a network. The weakness in the system can be a software bug, a temporary glitch, or a misconfiguration.;Initial Access;;Containers | IaaS | Linux | Network | Windows | macOS;https://attack.mitre.org/techniques/T1190/;Azure SQL Database;Detect;Minimal;This control may alert on usage of faulty SQL statements. This generates an alert for a possible SQL injection by an application. Alerts may not be generated on usage of valid SQL statements by attackers for malicious purposes.;PASS;;;test-check-id;False;;\r\n" + assert content == expected_csv diff --git a/tests/lib/outputs/compliance/mitre_attack/mitre_attack_gcp_test.py b/tests/lib/outputs/compliance/mitre_attack/mitre_attack_gcp_test.py new file mode 100644 index 0000000000..1eb5543384 --- /dev/null +++ b/tests/lib/outputs/compliance/mitre_attack/mitre_attack_gcp_test.py @@ -0,0 +1,96 @@ +from datetime import datetime +from io import StringIO + +from freezegun import freeze_time +from mock import patch + +from prowler.lib.outputs.compliance.mitre_attack.mitre_attack_gcp import GCPMitreAttack +from prowler.lib.outputs.compliance.mitre_attack.models import MitreAttackGCP +from prowler.lib.outputs.utils import unroll_list +from tests.lib.outputs.compliance.fixtures import MITRE_ATTACK_GCP +from tests.lib.outputs.fixtures.fixtures import generate_finding_output +from tests.providers.gcp.gcp_fixtures import GCP_PROJECT_ID + + +class TestGCPCIS: + def test_output_transform(self): + findings = [ + generate_finding_output( + provider="gcp", + compliance={"MITRE-ATTACK": "T1190"}, + account_name=GCP_PROJECT_ID, + account_uid=GCP_PROJECT_ID, + region="", + ) + ] + + output = GCPMitreAttack(findings, MITRE_ATTACK_GCP) + output_data = output.data[0] + assert isinstance(output_data, MitreAttackGCP) + assert output_data.Provider == "gcp" + assert output_data.Description == MITRE_ATTACK_GCP.Description + assert output_data.ProjectId == GCP_PROJECT_ID + assert output_data.Location == "" + assert output_data.Requirements_Id == MITRE_ATTACK_GCP.Requirements[0].Id + assert output_data.Requirements_Name == MITRE_ATTACK_GCP.Requirements[0].Name + assert ( + output_data.Requirements_Description + == MITRE_ATTACK_GCP.Requirements[0].Description + ) + assert output_data.Requirements_Tactics == unroll_list( + MITRE_ATTACK_GCP.Requirements[0].Tactics + ) + assert output_data.Requirements_SubTechniques == unroll_list( + MITRE_ATTACK_GCP.Requirements[0].SubTechniques + ) + assert output_data.Requirements_Platforms == unroll_list( + MITRE_ATTACK_GCP.Requirements[0].Platforms + ) + assert ( + output_data.Requirements_TechniqueURL + == MITRE_ATTACK_GCP.Requirements[0].TechniqueURL + ) + assert output_data.Requirements_Attributes_Services == ", ".join( + attribute.GCPService + for attribute in MITRE_ATTACK_GCP.Requirements[0].Attributes + ) + assert output_data.Requirements_Attributes_Categories == ", ".join( + attribute.Category + for attribute in MITRE_ATTACK_GCP.Requirements[0].Attributes + ) + assert output_data.Requirements_Attributes_Values == ", ".join( + attribute.Value for attribute in MITRE_ATTACK_GCP.Requirements[0].Attributes + ) + assert output_data.Requirements_Attributes_Comments == ", ".join( + attribute.Comment + for attribute in MITRE_ATTACK_GCP.Requirements[0].Attributes + ) + assert output_data.Status == "PASS" + assert output_data.StatusExtended == "" + assert output_data.ResourceId == "" + assert output_data.ResourceName == "" + assert output_data.CheckId == "test-check-id" + assert not output_data.Muted + + @freeze_time(datetime.now()) + def test_batch_write_data_to_file(self): + mock_file = StringIO() + findings = [ + generate_finding_output( + provider="gcp", + compliance={"MITRE-ATTACK": "T1190"}, + account_name=GCP_PROJECT_ID, + account_uid=GCP_PROJECT_ID, + region="", + ) + ] + output = GCPMitreAttack(findings, MITRE_ATTACK_GCP) + output._file_descriptor = mock_file + + with patch.object(mock_file, "close", return_value=None): + output.batch_write_data_to_file() + + mock_file.seek(0) + content = mock_file.read() + expected_csv = f"PROVIDER;DESCRIPTION;PROJECTID;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_NAME;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_TACTICS;REQUIREMENTS_SUBTECHNIQUES;REQUIREMENTS_PLATFORMS;REQUIREMENTS_TECHNIQUEURL;REQUIREMENTS_ATTRIBUTES_SERVICES;REQUIREMENTS_ATTRIBUTES_CATEGORIES;REQUIREMENTS_ATTRIBUTES_VALUES;REQUIREMENTS_ATTRIBUTES_COMMENTS;STATUS;STATUSEXTENDED;RESOURCEID;CHECKID;MUTED;RESOURCENAME;LOCATION\r\ngcp;MITRE ATT&CK® is a globally-accessible knowledge base of adversary tactics and techniques based on real-world observations. The ATT&CK knowledge base is used as a foundation for the development of specific threat models and methodologies in the private sector, in government, and in the cybersecurity product and service community.;{GCP_PROJECT_ID};{datetime.now()};T1190;Exploit Public-Facing Application;Adversaries may attempt to exploit a weakness in an Internet-facing host or system to initially access a network. The weakness in the system can be a software bug, a temporary glitch, or a misconfiguration.;Initial Access;;Containers | IaaS | Linux | Network | Windows | macOS;https://attack.mitre.org/techniques/T1190/;Artifact Registry;Protect;Partial;Once this control is deployed, it can detect known vulnerabilities in various Linux OS packages. This information can be used to patch, isolate, or remove vulnerable software and machines. This control does not directly protect against exploitation and is not effective against zero day attacks, vulnerabilities with no available patch, and other end-of-life packages.;PASS;;;test-check-id;False;;\r\n" + assert content == expected_csv From a5057762275d344002ee36d6971dada080d27013 Mon Sep 17 00:00:00 2001 From: Sergio Garcia <38561120+sergargar@users.noreply.github.com> Date: Fri, 12 Jul 2024 10:50:41 -0400 Subject: [PATCH 4/4] chore(ens): add ENS output class (#4435) --- prowler/__main__.py | 14 +++ prowler/lib/cli/parser.py | 2 +- prowler/lib/outputs/compliance/compliance.py | 20 ++-- .../{ens_rd2022_aws.py => ens/ens.py} | 58 +--------- prowler/lib/outputs/compliance/ens/ens_aws.py | 103 ++++++++++++++++++ prowler/lib/outputs/compliance/ens/models.py | 30 +++++ prowler/lib/outputs/compliance/models.py | 27 ----- prowler/lib/outputs/file_descriptors.py | 12 +- .../outputs/compliance/ens/ens_aws_test.py | 86 +++++++++++++++ tests/lib/outputs/compliance/fixtures.py | 31 ++++++ 10 files changed, 279 insertions(+), 104 deletions(-) rename prowler/lib/outputs/compliance/{ens_rd2022_aws.py => ens/ens.py} (67%) create mode 100644 prowler/lib/outputs/compliance/ens/ens_aws.py create mode 100644 prowler/lib/outputs/compliance/ens/models.py create mode 100644 tests/lib/outputs/compliance/ens/ens_aws_test.py diff --git a/prowler/__main__.py b/prowler/__main__.py index aeade8688c..4a8ccef055 100644 --- a/prowler/__main__.py +++ b/prowler/__main__.py @@ -48,6 +48,7 @@ from prowler.lib.outputs.compliance.cis.cis_gcp import GCPCIS from prowler.lib.outputs.compliance.cis.cis_kubernetes import KubernetesCIS from prowler.lib.outputs.compliance.compliance import display_compliance_table +from prowler.lib.outputs.compliance.ens.ens_aws import AWSENS from prowler.lib.outputs.compliance.mitre_attack.mitre_attack_aws import AWSMitreAttack from prowler.lib.outputs.compliance.mitre_attack.mitre_attack_azure import ( AzureMitreAttack, @@ -391,6 +392,19 @@ def prowler(): file_path=filename, ) mitre_attack_finding.batch_write_data_to_file() + elif compliance_name.startswith("ens_"): + # Generate ENS Finding Object + filename = ( + f"{global_provider.output_options.output_directory}/compliance/" + f"{global_provider.output_options.output_filename}_{compliance_name}.csv" + ) + ens_finding = AWSENS( + findings=finding_outputs, + compliance=bulk_compliance_frameworks[compliance_name], + create_file_descriptor=True, + file_path=filename, + ) + ens_finding.batch_write_data_to_file() elif provider == "azure": for compliance_name in input_compliance_frameworks: diff --git a/prowler/lib/cli/parser.py b/prowler/lib/cli/parser.py index bb3af1de2f..2309d8742c 100644 --- a/prowler/lib/cli/parser.py +++ b/prowler/lib/cli/parser.py @@ -263,7 +263,7 @@ def __init_checks_parser__(self): group.add_argument( "--compliance", nargs="+", - help="Compliance Framework to check against for. The format should be the following: framework_version_provider (e.g.: ens_rd2022_aws)", + help="Compliance Framework to check against for. The format should be the following: framework_version_provider (e.g.: cis_3.0_aws)", choices=available_compliance_frameworks, ) group.add_argument( diff --git a/prowler/lib/outputs/compliance/compliance.py b/prowler/lib/outputs/compliance/compliance.py index 924224decb..077de85473 100644 --- a/prowler/lib/outputs/compliance/compliance.py +++ b/prowler/lib/outputs/compliance/compliance.py @@ -6,10 +6,7 @@ write_compliance_row_aws_well_architected_framework, ) from prowler.lib.outputs.compliance.cis.cis import get_cis_table -from prowler.lib.outputs.compliance.ens_rd2022_aws import ( - get_ens_rd2022_aws_table, - write_compliance_row_ens_rd2022_aws, -) +from prowler.lib.outputs.compliance.ens.ens import get_ens_table from prowler.lib.outputs.compliance.generic import ( get_generic_compliance_table, write_compliance_row_generic, @@ -96,13 +93,12 @@ def fill_compliance( ) for compliance in check_compliances: - if compliance.Framework == "ENS" and compliance.Version == "RD2022": - write_compliance_row_ens_rd2022_aws( - file_descriptors, finding, compliance, output_options, provider - ) - # FIXME: Remove this once we merge all the compliance frameworks - elif compliance.Framework == "CIS": + if compliance.Framework == "CIS": + continue + elif compliance.Framework == "MITRE-ATTACK" and compliance.Version == "": + continue + elif compliance.Framework == "ENS": continue elif compliance.Framework == "MITRE-ATTACK" and compliance.Version == "": continue @@ -144,8 +140,8 @@ def display_compliance_table( compliance_overview: bool, ): try: - if "ens_rd2022_aws" == compliance_framework: - get_ens_rd2022_aws_table( + if "ens_" in compliance_framework: + get_ens_table( findings, bulk_checks_metadata, compliance_framework, diff --git a/prowler/lib/outputs/compliance/ens_rd2022_aws.py b/prowler/lib/outputs/compliance/ens/ens.py similarity index 67% rename from prowler/lib/outputs/compliance/ens_rd2022_aws.py rename to prowler/lib/outputs/compliance/ens/ens.py index 1dfc1164db..923638350f 100644 --- a/prowler/lib/outputs/compliance/ens_rd2022_aws.py +++ b/prowler/lib/outputs/compliance/ens/ens.py @@ -1,58 +1,10 @@ -from csv import DictWriter - from colorama import Fore, Style from tabulate import tabulate -from prowler.config.config import orange_color, timestamp -from prowler.lib.outputs.compliance.models import Check_Output_CSV_ENS_RD2022 -from prowler.lib.outputs.csv.csv import generate_csv_fields -from prowler.lib.utils.utils import outputs_unix_timestamp - - -def write_compliance_row_ens_rd2022_aws( - file_descriptors, finding, compliance, output_options, provider -): - compliance_output = "ens_rd2022_aws" - csv_header = generate_csv_fields(Check_Output_CSV_ENS_RD2022) - csv_writer = DictWriter( - file_descriptors[compliance_output], - fieldnames=csv_header, - delimiter=";", - ) - for requirement in compliance.Requirements: - requirement_description = requirement.Description - requirement_id = requirement.Id - for attribute in requirement.Attributes: - compliance_row = Check_Output_CSV_ENS_RD2022( - Provider=finding.check_metadata.Provider, - Description=compliance.Description, - AccountId=provider.identity.account, - Region=finding.region, - AssessmentDate=outputs_unix_timestamp( - output_options.unix_timestamp, timestamp - ), - Requirements_Id=requirement_id, - Requirements_Description=requirement_description, - Requirements_Attributes_IdGrupoControl=attribute.IdGrupoControl, - Requirements_Attributes_Marco=attribute.Marco, - Requirements_Attributes_Categoria=attribute.Categoria, - Requirements_Attributes_DescripcionControl=attribute.DescripcionControl, - Requirements_Attributes_Nivel=attribute.Nivel, - Requirements_Attributes_Tipo=attribute.Tipo, - Requirements_Attributes_Dimensiones=",".join(attribute.Dimensiones), - Requirements_Attributes_ModoEjecucion=attribute.ModoEjecucion, - Requirements_Attributes_Dependencias=",".join(attribute.Dependencias), - Status=finding.status, - StatusExtended=finding.status_extended, - ResourceId=finding.resource_id, - CheckId=finding.check_metadata.CheckID, - Muted=finding.muted, - ) - - csv_writer.writerow(compliance_row.__dict__) +from prowler.config.config import orange_color -def get_ens_rd2022_aws_table( +def get_ens_table( findings: list, bulk_checks_metadata: dict, compliance_framework: str, @@ -78,11 +30,7 @@ def get_ens_rd2022_aws_table( check = bulk_checks_metadata[finding.check_metadata.CheckID] check_compliances = check.Compliance for compliance in check_compliances: - if ( - compliance.Framework == "ENS" - and compliance.Provider == "AWS" - and compliance.Version == "RD2022" - ): + if compliance.Framework == "ENS" and compliance.Provider == "AWS": for requirement in compliance.Requirements: for attribute in requirement.Attributes: marco_categoria = f"{attribute.Marco}/{attribute.Categoria}" diff --git a/prowler/lib/outputs/compliance/ens/ens_aws.py b/prowler/lib/outputs/compliance/ens/ens_aws.py new file mode 100644 index 0000000000..ed96576455 --- /dev/null +++ b/prowler/lib/outputs/compliance/ens/ens_aws.py @@ -0,0 +1,103 @@ +from csv import DictWriter + +from prowler.lib import logger +from prowler.lib.check.compliance_models import ComplianceBaseModel +from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput +from prowler.lib.outputs.compliance.ens.models import ENSAWS +from prowler.lib.outputs.finding import Finding + + +class AWSENS(ComplianceOutput): + """ + This class represents the AWS ENS compliance output. + + Attributes: + - _data (list): A list to store transformed data from findings. + - _file_descriptor (TextIOWrapper): A file descriptor to write data to a file. + + Methods: + - transform: Transforms findings into AWS ENS compliance format. + - batch_write_data_to_file: Writes the findings data to a CSV file in AWS ENS compliance format. + """ + + def transform( + self, + findings: list[Finding], + compliance: ComplianceBaseModel, + compliance_name: str, + ) -> None: + """ + Transforms a list of findings into AWS ENS compliance format. + + Parameters: + - findings (list): A list of findings. + - compliance (ComplianceBaseModel): A compliance model. + - compliance_name (str): The name of the compliance model. + + Returns: + - None + """ + for finding in findings: + # Get the compliance requirements for the finding + finding_requirements = finding.compliance.get(compliance_name, []) + for requirement in compliance.Requirements: + if requirement.Id in finding_requirements: + for attribute in requirement.Attributes: + compliance_row = ENSAWS( + Provider=finding.provider, + Description=compliance.Description, + AccountId=finding.account_uid, + Region=finding.region, + AssessmentDate=str(finding.timestamp), + Requirements_Id=requirement.Id, + Requirements_Description=requirement.Description, + Requirements_Attributes_IdGrupoControl=attribute.IdGrupoControl, + Requirements_Attributes_Marco=attribute.Marco, + Requirements_Attributes_Categoria=attribute.Categoria, + Requirements_Attributes_DescripcionControl=attribute.DescripcionControl, + Requirements_Attributes_Nivel=attribute.Nivel, + Requirements_Attributes_Tipo=attribute.Tipo, + Requirements_Attributes_Dimensiones=",".join( + attribute.Dimensiones + ), + Requirements_Attributes_ModoEjecucion=attribute.ModoEjecucion, + Requirements_Attributes_Dependencias=",".join( + attribute.Dependencias + ), + Status=finding.status, + StatusExtended=finding.status_extended, + ResourceId=finding.resource_uid, + CheckId=finding.check_id, + Muted=finding.muted, + ResourceName=finding.resource_name, + ) + self._data.append(compliance_row) + + def batch_write_data_to_file(self) -> None: + """ + Writes the findings data to a CSV file in AWS ENS compliance format. + + Returns: + - None + """ + try: + if ( + getattr(self, "_file_descriptor", None) + and not self._file_descriptor.closed + and self._data + ): + csv_writer = DictWriter( + self._file_descriptor, + fieldnames=[field.upper() for field in self._data[0].dict().keys()], + delimiter=";", + ) + csv_writer.writeheader() + for finding in self._data: + csv_writer.writerow( + {k.upper(): v for k, v in finding.dict().items()} + ) + self._file_descriptor.close() + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) diff --git a/prowler/lib/outputs/compliance/ens/models.py b/prowler/lib/outputs/compliance/ens/models.py new file mode 100644 index 0000000000..12b20ed84f --- /dev/null +++ b/prowler/lib/outputs/compliance/ens/models.py @@ -0,0 +1,30 @@ +from pydantic import BaseModel + + +class ENSAWS(BaseModel): + """ + ENSAWS generates a finding's output in CSV ENS format for AWS. + """ + + Provider: str + Description: str + AccountId: str + Region: str + AssessmentDate: str + Requirements_Id: str + Requirements_Description: str + Requirements_Attributes_IdGrupoControl: str + Requirements_Attributes_Marco: str + Requirements_Attributes_Categoria: str + Requirements_Attributes_DescripcionControl: str + Requirements_Attributes_Nivel: str + Requirements_Attributes_Tipo: str + Requirements_Attributes_Dimensiones: str + Requirements_Attributes_ModoEjecucion: str + Requirements_Attributes_Dependencias: str + Status: str + StatusExtended: str + ResourceId: str + CheckId: str + Muted: bool + ResourceName: str diff --git a/prowler/lib/outputs/compliance/models.py b/prowler/lib/outputs/compliance/models.py index c651f092b0..68a26ea982 100644 --- a/prowler/lib/outputs/compliance/models.py +++ b/prowler/lib/outputs/compliance/models.py @@ -2,34 +2,7 @@ from pydantic import BaseModel - # TODO: move this to outputs//models.py -class Check_Output_CSV_ENS_RD2022(BaseModel): - """ - Check_Output_CSV_ENS_RD2022 generates a finding's output in CSV ENS RD2022 format. - """ - - Provider: str - Description: str - AccountId: str - Region: str - AssessmentDate: str - Requirements_Id: str - Requirements_Description: str - Requirements_Attributes_IdGrupoControl: str - Requirements_Attributes_Marco: str - Requirements_Attributes_Categoria: str - Requirements_Attributes_DescripcionControl: str - Requirements_Attributes_Nivel: str - Requirements_Attributes_Tipo: str - Requirements_Attributes_Dimensiones: str - Requirements_Attributes_ModoEjecucion: str - Requirements_Attributes_Dependencias: str - Status: str - StatusExtended: str - ResourceId: str - CheckId: str - Muted: bool class Check_Output_CSV_Generic_Compliance(BaseModel): diff --git a/prowler/lib/outputs/file_descriptors.py b/prowler/lib/outputs/file_descriptors.py index 4f6406285b..e33a268590 100644 --- a/prowler/lib/outputs/file_descriptors.py +++ b/prowler/lib/outputs/file_descriptors.py @@ -7,7 +7,6 @@ from prowler.lib.outputs.compliance.models import ( Check_Output_CSV_AWS_ISO27001_2013, Check_Output_CSV_AWS_Well_Architected, - Check_Output_CSV_ENS_RD2022, Check_Output_CSV_Generic_Compliance, ) from prowler.lib.outputs.csv.csv import generate_csv_fields @@ -63,6 +62,8 @@ def fill_file_descriptors(output_modes, output_directory, output_filename, provi continue elif "mitre_attack_" in output_mode: continue + elif "ens_" in output_mode: + continue elif provider.type == "gcp": filename = f"{output_directory}/compliance/{output_filename}_{output_mode}{csv_file_suffix}" @@ -91,14 +92,7 @@ def fill_file_descriptors(output_modes, output_directory, output_filename, provi elif provider.type == "aws": # Compliance frameworks filename = f"{output_directory}/compliance/{output_filename}_{output_mode}{csv_file_suffix}" - if output_mode == "ens_rd2022_aws": - file_descriptor = initialize_file_descriptor( - filename, - Check_Output_CSV_ENS_RD2022, - ) - file_descriptors.update({output_mode: file_descriptor}) - - elif "aws_well_architected_framework" in output_mode: + if "aws_well_architected_framework" in output_mode: file_descriptor = initialize_file_descriptor( filename, Check_Output_CSV_AWS_Well_Architected, diff --git a/tests/lib/outputs/compliance/ens/ens_aws_test.py b/tests/lib/outputs/compliance/ens/ens_aws_test.py new file mode 100644 index 0000000000..e6dcbb2a35 --- /dev/null +++ b/tests/lib/outputs/compliance/ens/ens_aws_test.py @@ -0,0 +1,86 @@ +from datetime import datetime +from io import StringIO + +from freezegun import freeze_time +from mock import patch + +from prowler.lib.outputs.compliance.ens.ens_aws import AWSENS +from prowler.lib.outputs.compliance.ens.models import ENSAWS +from tests.lib.outputs.compliance.fixtures import ENS_RD2022_AWS +from tests.lib.outputs.fixtures.fixtures import generate_finding_output +from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_EU_WEST_1 + + +class TestAWSENS: + def test_output_transform(self): + findings = [ + generate_finding_output(compliance={"ENS-RD2022": "op.exp.8.aws.ct.3"}) + ] + + output = AWSENS(findings, ENS_RD2022_AWS) + output_data = output.data[0] + assert isinstance(output_data, ENSAWS) + assert output_data.Provider == "aws" + assert output_data.AccountId == AWS_ACCOUNT_NUMBER + assert output_data.Region == AWS_REGION_EU_WEST_1 + assert output_data.Description == ENS_RD2022_AWS.Description + assert output_data.Requirements_Id == ENS_RD2022_AWS.Requirements[0].Id + assert ( + output_data.Requirements_Description + == ENS_RD2022_AWS.Requirements[0].Description + ) + assert ( + output_data.Requirements_Attributes_IdGrupoControl + == ENS_RD2022_AWS.Requirements[0].Attributes[0].IdGrupoControl + ) + assert ( + output_data.Requirements_Attributes_Marco + == ENS_RD2022_AWS.Requirements[0].Attributes[0].Marco + ) + assert ( + output_data.Requirements_Attributes_Categoria + == ENS_RD2022_AWS.Requirements[0].Attributes[0].Categoria + ) + assert ( + output_data.Requirements_Attributes_DescripcionControl + == ENS_RD2022_AWS.Requirements[0].Attributes[0].DescripcionControl + ) + assert ( + output_data.Requirements_Attributes_Nivel + == ENS_RD2022_AWS.Requirements[0].Attributes[0].Nivel + ) + assert ( + output_data.Requirements_Attributes_Tipo + == ENS_RD2022_AWS.Requirements[0].Attributes[0].Tipo + ) + assert [ + output_data.Requirements_Attributes_Dimensiones + ] == ENS_RD2022_AWS.Requirements[0].Attributes[0].Dimensiones + assert ( + output_data.Requirements_Attributes_ModoEjecucion + == ENS_RD2022_AWS.Requirements[0].Attributes[0].ModoEjecucion + ) + assert output_data.Requirements_Attributes_Dependencias == "" + assert output_data.Status == "PASS" + assert output_data.StatusExtended == "" + assert output_data.ResourceId == "" + assert output_data.ResourceName == "" + assert output_data.CheckId == "test-check-id" + assert output_data.Muted is False + + @freeze_time(datetime.now()) + def test_batch_write_data_to_file(self): + mock_file = StringIO() + findings = [ + generate_finding_output(compliance={"ENS-RD2022": "op.exp.8.aws.ct.3"}) + ] + output = AWSENS(findings, ENS_RD2022_AWS) + output._file_descriptor = mock_file + + with patch.object(mock_file, "close", return_value=None): + output.batch_write_data_to_file() + + mock_file.seek(0) + content = mock_file.read() + expected_csv = f"""PROVIDER;DESCRIPTION;ACCOUNTID;REGION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_IDGRUPOCONTROL;REQUIREMENTS_ATTRIBUTES_MARCO;REQUIREMENTS_ATTRIBUTES_CATEGORIA;REQUIREMENTS_ATTRIBUTES_DESCRIPCIONCONTROL;REQUIREMENTS_ATTRIBUTES_NIVEL;REQUIREMENTS_ATTRIBUTES_TIPO;REQUIREMENTS_ATTRIBUTES_DIMENSIONES;REQUIREMENTS_ATTRIBUTES_MODOEJECUCION;REQUIREMENTS_ATTRIBUTES_DEPENDENCIAS;STATUS;STATUSEXTENDED;RESOURCEID;CHECKID;MUTED;RESOURCENAME\r\naws;The accreditation scheme of the ENS (National Security Scheme) has been developed by the Ministry of Finance and Public Administrations and the CCN (National Cryptological Center). This includes the basic principles and minimum requirements necessary for the adequate protection of information.;123456789012;eu-west-1;{datetime.now()};op.exp.8.aws.ct.3;Registro de actividad;op.exp.8;operacional;explotación;Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.;alto;requisito;trazabilidad;automático;;PASS;;;test-check-id;False;\r\n""" + assert content == expected_csv diff --git a/tests/lib/outputs/compliance/fixtures.py b/tests/lib/outputs/compliance/fixtures.py index eb2ccbe3f8..3aa1fa6500 100644 --- a/tests/lib/outputs/compliance/fixtures.py +++ b/tests/lib/outputs/compliance/fixtures.py @@ -2,6 +2,9 @@ CIS_Requirement_Attribute, Compliance_Requirement, ComplianceBaseModel, + ENS_Requirement_Attribute, + ENS_Requirement_Attribute_Nivel, + ENS_Requirement_Attribute_Tipos, Mitre_Requirement, Mitre_Requirement_Attribute_AWS, Mitre_Requirement_Attribute_Azure, @@ -283,6 +286,34 @@ ) ], ) +ENS_RD2022_AWS_NAME = "ens_rd2022_aws" +ENS_RD2022_AWS = ComplianceBaseModel( + Framework="ENS", + Provider="AWS", + Version="RD2022", + Description="The accreditation scheme of the ENS (National Security Scheme) has been developed by the Ministry of Finance and Public Administrations and the CCN (National Cryptological Center). This includes the basic principles and minimum requirements necessary for the adequate protection of information.", + Requirements=[ + Compliance_Requirement( + Id="op.exp.8.aws.ct.3", + Description="Registro de actividad", + Name=None, + Attributes=[ + ENS_Requirement_Attribute( + IdGrupoControl="op.exp.8", + Marco="operacional", + Categoria="explotación", + DescripcionControl="Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.", + Tipo=ENS_Requirement_Attribute_Tipos.requisito, + Nivel=ENS_Requirement_Attribute_Nivel.alto, + Dimensiones=["trazabilidad"], + ModoEjecucion="automático", + Dependencias=[], + ) + ], + Checks=["cloudtrail_log_file_validation_enabled"], + ) + ], +) NOT_PRESENT_COMPLIANCE_NAME = "not_present_compliance_name" NOT_PRESENT_COMPLIANCE = ComplianceBaseModel( Framework="NOT_EXISTENT",