From 8791b7e3f1221788ec374d80ffb5a662dc4a20bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20Poyatos?= Date: Tue, 8 Oct 2024 14:43:18 +0200 Subject: [PATCH] feat(Scan, Finding): PRWLR-4987 Adjust findings delta based on previous findings (#52) * feat(Finding): PRWLR-4987 add uid field to Findings * feat(Scan): PRWLR-4987 implement logic to calculate Finding delta value * test(Scan): PRWLR-4987 add unit tests for _create_finding_delta * chore(Spec): PRWLR-4987 update API spec --- poetry.lock | 112 ++++++++++--------- pyproject.toml | 2 +- src/backend/api/filters.py | 2 + src/backend/api/fixtures/5_dev_findings.json | 4 + src/backend/api/migrations/0001_initial.py | 8 ++ src/backend/api/models.py | 2 + src/backend/api/specs/v1.yaml | 19 ++++ src/backend/api/v1/serializers.py | 1 + src/backend/api/v1/views.py | 4 +- src/backend/conftest.py | 2 + src/backend/tasks/jobs/scan.py | 32 +++++- src/backend/tasks/tests/test_scan.py | 17 ++- 12 files changed, 144 insertions(+), 61 deletions(-) diff --git a/poetry.lock b/poetry.lock index 94d3af0d4b..342722a07a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1620,13 +1620,13 @@ sidecar = ["drf-spectacular-sidecar"] [[package]] name = "drf-spectacular-jsonapi" -version = "0.4.1" +version = "0.5.1" description = "open api 3 schema generator for drf-json-api package based on drf-spectacular package." optional = false python-versions = ">=3.7" files = [ - {file = "drf-spectacular-jsonapi-0.4.1.tar.gz", hash = "sha256:e6af0583a698e04c3bc7260f0768d9cbeb8dd6aac7ad5c58221dd58db948d6e7"}, - {file = "drf_spectacular_jsonapi-0.4.1-py3-none-any.whl", hash = "sha256:793d8e8cafbb72c727563c5dcb92a120ce83fd8a60f9848d6b5da39422ca143e"}, + {file = "drf-spectacular-jsonapi-0.5.1.tar.gz", hash = "sha256:e45f87f3cce2692f4f546e0785d8fcc32c6b49770fff858065c267ae8f9cfde5"}, + {file = "drf_spectacular_jsonapi-0.5.1-py3-none-any.whl", hash = "sha256:abac728abd83e2544408cc900d682d532ca2088f2f9321d1c9101bcdfdabca78"}, ] [package.dependencies] @@ -1649,13 +1649,13 @@ files = [ [[package]] name = "email-validator" -version = "2.1.1" +version = "2.2.0" description = "A robust email address syntax and deliverability validation library." optional = false python-versions = ">=3.8" files = [ - {file = "email_validator-2.1.1-py3-none-any.whl", hash = "sha256:97d882d174e2a65732fb43bfce81a3a834cbc1bde8bf419e30ef5ea976370a05"}, - {file = "email_validator-2.1.1.tar.gz", hash = "sha256:200a70680ba08904be6d1eef729205cc0d687634399a5924d842533efb824b84"}, + {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"}, + {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"}, ] [package.dependencies] @@ -3267,8 +3267,8 @@ microsoft-kiota-abstractions = "1.3.3" msgraph-sdk = "1.8.0" numpy = "2.0.2" pandas = "2.2.3" -py-ocsf-models = "0.1.1" -pydantic = "1.10.15" +py-ocsf-models = "0.2.0" +pydantic = "1.10.18" python-dateutil = "^2.9.0.post0" pytz = "2024.2" schema = "0.7.7" @@ -3281,7 +3281,7 @@ tzlocal = "5.2" type = "git" url = "https://github.com/prowler-cloud/prowler.git" reference = "master" -resolved_reference = "e389e0136f11038d4e0ad0008a922398c7641ac0" +resolved_reference = "30bc971f4b289cf4c53ddd10fa7cfba425c32a98" [[package]] name = "psutil" @@ -3395,18 +3395,19 @@ files = [ [[package]] name = "py-ocsf-models" -version = "0.1.1" +version = "0.2.0" description = "This is a Python implementation of the OCSF models. The models are used to represent the data of the OCSF Schema defined in https://schema.ocsf.io/." optional = false python-versions = "<3.13,>=3.9" files = [ - {file = "py_ocsf_models-0.1.1-py3-none-any.whl", hash = "sha256:c6ea465fda85470b938a48da65b1f19664f6d83820ebe849ef5551094e6768de"}, - {file = "py_ocsf_models-0.1.1.tar.gz", hash = "sha256:b0f2d4495a2596793f75e61a1ba218edea3a2d17a2b9911d46ee0fa623cc657b"}, + {file = "py_ocsf_models-0.2.0-py3-none-any.whl", hash = "sha256:ac75fd21077694b343ebaad3479194db113c274879b114277560ff287d5cd7b5"}, + {file = "py_ocsf_models-0.2.0.tar.gz", hash = "sha256:3e12648d05329e6776a0e6b1ffea87a3eb60aa7d8cb2c4afd69e5724f443ce03"}, ] [package.dependencies] -email-validator = "2.1.1" -pydantic = "1.10.15" +cryptography = "43.0.1" +email-validator = "2.2.0" +pydantic = "1.10.18" [[package]] name = "pyasn1" @@ -3491,47 +3492,54 @@ files = [ [[package]] name = "pydantic" -version = "1.10.15" +version = "1.10.18" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:22ed12ee588b1df028a2aa5d66f07bf8f8b4c8579c2e96d5a9c1f96b77f3bb55"}, - {file = "pydantic-1.10.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:75279d3cac98186b6ebc2597b06bcbc7244744f6b0b44a23e4ef01e5683cc0d2"}, - {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50f1666a9940d3d68683c9d96e39640f709d7a72ff8702987dab1761036206bb"}, - {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82790d4753ee5d00739d6cb5cf56bceb186d9d6ce134aca3ba7befb1eedbc2c8"}, - {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d207d5b87f6cbefbdb1198154292faee8017d7495a54ae58db06762004500d00"}, - {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e49db944fad339b2ccb80128ffd3f8af076f9f287197a480bf1e4ca053a866f0"}, - {file = "pydantic-1.10.15-cp310-cp310-win_amd64.whl", hash = "sha256:d3b5c4cbd0c9cb61bbbb19ce335e1f8ab87a811f6d589ed52b0254cf585d709c"}, - {file = "pydantic-1.10.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c3d5731a120752248844676bf92f25a12f6e45425e63ce22e0849297a093b5b0"}, - {file = "pydantic-1.10.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c365ad9c394f9eeffcb30a82f4246c0006417f03a7c0f8315d6211f25f7cb654"}, - {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3287e1614393119c67bd4404f46e33ae3be3ed4cd10360b48d0a4459f420c6a3"}, - {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be51dd2c8596b25fe43c0a4a59c2bee4f18d88efb8031188f9e7ddc6b469cf44"}, - {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6a51a1dd4aa7b3f1317f65493a182d3cff708385327c1c82c81e4a9d6d65b2e4"}, - {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4e316e54b5775d1eb59187f9290aeb38acf620e10f7fd2f776d97bb788199e53"}, - {file = "pydantic-1.10.15-cp311-cp311-win_amd64.whl", hash = "sha256:0d142fa1b8f2f0ae11ddd5e3e317dcac060b951d605fda26ca9b234b92214986"}, - {file = "pydantic-1.10.15-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7ea210336b891f5ea334f8fc9f8f862b87acd5d4a0cbc9e3e208e7aa1775dabf"}, - {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3453685ccd7140715e05f2193d64030101eaad26076fad4e246c1cc97e1bb30d"}, - {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bea1f03b8d4e8e86702c918ccfd5d947ac268f0f0cc6ed71782e4b09353b26f"}, - {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:005655cabc29081de8243126e036f2065bd7ea5b9dff95fde6d2c642d39755de"}, - {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:af9850d98fc21e5bc24ea9e35dd80a29faf6462c608728a110c0a30b595e58b7"}, - {file = "pydantic-1.10.15-cp37-cp37m-win_amd64.whl", hash = "sha256:d31ee5b14a82c9afe2bd26aaa405293d4237d0591527d9129ce36e58f19f95c1"}, - {file = "pydantic-1.10.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5e09c19df304b8123938dc3c53d3d3be6ec74b9d7d0d80f4f4b5432ae16c2022"}, - {file = "pydantic-1.10.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7ac9237cd62947db00a0d16acf2f3e00d1ae9d3bd602b9c415f93e7a9fc10528"}, - {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:584f2d4c98ffec420e02305cf675857bae03c9d617fcfdc34946b1160213a948"}, - {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbc6989fad0c030bd70a0b6f626f98a862224bc2b1e36bfc531ea2facc0a340c"}, - {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d573082c6ef99336f2cb5b667b781d2f776d4af311574fb53d908517ba523c22"}, - {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6bd7030c9abc80134087d8b6e7aa957e43d35714daa116aced57269a445b8f7b"}, - {file = "pydantic-1.10.15-cp38-cp38-win_amd64.whl", hash = "sha256:3350f527bb04138f8aff932dc828f154847fbdc7a1a44c240fbfff1b57f49a12"}, - {file = "pydantic-1.10.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:51d405b42f1b86703555797270e4970a9f9bd7953f3990142e69d1037f9d9e51"}, - {file = "pydantic-1.10.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a980a77c52723b0dc56640ced396b73a024d4b74f02bcb2d21dbbac1debbe9d0"}, - {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67f1a1fb467d3f49e1708a3f632b11c69fccb4e748a325d5a491ddc7b5d22383"}, - {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:676ed48f2c5bbad835f1a8ed8a6d44c1cd5a21121116d2ac40bd1cd3619746ed"}, - {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:92229f73400b80c13afcd050687f4d7e88de9234d74b27e6728aa689abcf58cc"}, - {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2746189100c646682eff0bce95efa7d2e203420d8e1c613dc0c6b4c1d9c1fde4"}, - {file = "pydantic-1.10.15-cp39-cp39-win_amd64.whl", hash = "sha256:394f08750bd8eaad714718812e7fab615f873b3cdd0b9d84e76e51ef3b50b6b7"}, - {file = "pydantic-1.10.15-py3-none-any.whl", hash = "sha256:28e552a060ba2740d0d2aabe35162652c1459a0b9069fe0db7f4ee0e18e74d58"}, - {file = "pydantic-1.10.15.tar.gz", hash = "sha256:ca832e124eda231a60a041da4f013e3ff24949d94a01154b137fc2f2a43c3ffb"}, + {file = "pydantic-1.10.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e405ffcc1254d76bb0e760db101ee8916b620893e6edfbfee563b3c6f7a67c02"}, + {file = "pydantic-1.10.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e306e280ebebc65040034bff1a0a81fd86b2f4f05daac0131f29541cafd80b80"}, + {file = "pydantic-1.10.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11d9d9b87b50338b1b7de4ebf34fd29fdb0d219dc07ade29effc74d3d2609c62"}, + {file = "pydantic-1.10.18-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b661ce52c7b5e5f600c0c3c5839e71918346af2ef20062705ae76b5c16914cab"}, + {file = "pydantic-1.10.18-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c20f682defc9ef81cd7eaa485879ab29a86a0ba58acf669a78ed868e72bb89e0"}, + {file = "pydantic-1.10.18-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c5ae6b7c8483b1e0bf59e5f1843e4fd8fd405e11df7de217ee65b98eb5462861"}, + {file = "pydantic-1.10.18-cp310-cp310-win_amd64.whl", hash = "sha256:74fe19dda960b193b0eb82c1f4d2c8e5e26918d9cda858cbf3f41dd28549cb70"}, + {file = "pydantic-1.10.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72fa46abace0a7743cc697dbb830a41ee84c9db8456e8d77a46d79b537efd7ec"}, + {file = "pydantic-1.10.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef0fe7ad7cbdb5f372463d42e6ed4ca9c443a52ce544472d8842a0576d830da5"}, + {file = "pydantic-1.10.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a00e63104346145389b8e8f500bc6a241e729feaf0559b88b8aa513dd2065481"}, + {file = "pydantic-1.10.18-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae6fa2008e1443c46b7b3a5eb03800121868d5ab6bc7cda20b5df3e133cde8b3"}, + {file = "pydantic-1.10.18-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9f463abafdc92635da4b38807f5b9972276be7c8c5121989768549fceb8d2588"}, + {file = "pydantic-1.10.18-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3445426da503c7e40baccefb2b2989a0c5ce6b163679dd75f55493b460f05a8f"}, + {file = "pydantic-1.10.18-cp311-cp311-win_amd64.whl", hash = "sha256:467a14ee2183bc9c902579bb2f04c3d3dac00eff52e252850509a562255b2a33"}, + {file = "pydantic-1.10.18-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:efbc8a7f9cb5fe26122acba1852d8dcd1e125e723727c59dcd244da7bdaa54f2"}, + {file = "pydantic-1.10.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24a4a159d0f7a8e26bf6463b0d3d60871d6a52eac5bb6a07a7df85c806f4c048"}, + {file = "pydantic-1.10.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b74be007703547dc52e3c37344d130a7bfacca7df112a9e5ceeb840a9ce195c7"}, + {file = "pydantic-1.10.18-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcb20d4cb355195c75000a49bb4a31d75e4295200df620f454bbc6bdf60ca890"}, + {file = "pydantic-1.10.18-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:46f379b8cb8a3585e3f61bf9ae7d606c70d133943f339d38b76e041ec234953f"}, + {file = "pydantic-1.10.18-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cbfbca662ed3729204090c4d09ee4beeecc1a7ecba5a159a94b5a4eb24e3759a"}, + {file = "pydantic-1.10.18-cp312-cp312-win_amd64.whl", hash = "sha256:c6d0a9f9eccaf7f438671a64acf654ef0d045466e63f9f68a579e2383b63f357"}, + {file = "pydantic-1.10.18-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3d5492dbf953d7d849751917e3b2433fb26010d977aa7a0765c37425a4026ff1"}, + {file = "pydantic-1.10.18-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe734914977eed33033b70bfc097e1baaffb589517863955430bf2e0846ac30f"}, + {file = "pydantic-1.10.18-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15fdbe568beaca9aacfccd5ceadfb5f1a235087a127e8af5e48df9d8a45ae85c"}, + {file = "pydantic-1.10.18-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c3e742f62198c9eb9201781fbebe64533a3bbf6a76a91b8d438d62b813079dbc"}, + {file = "pydantic-1.10.18-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:19a3bd00b9dafc2cd7250d94d5b578edf7a0bd7daf102617153ff9a8fa37871c"}, + {file = "pydantic-1.10.18-cp37-cp37m-win_amd64.whl", hash = "sha256:2ce3fcf75b2bae99aa31bd4968de0474ebe8c8258a0110903478bd83dfee4e3b"}, + {file = "pydantic-1.10.18-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:335a32d72c51a313b33fa3a9b0fe283503272ef6467910338e123f90925f0f03"}, + {file = "pydantic-1.10.18-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:34a3613c7edb8c6fa578e58e9abe3c0f5e7430e0fc34a65a415a1683b9c32d9a"}, + {file = "pydantic-1.10.18-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9ee4e6ca1d9616797fa2e9c0bfb8815912c7d67aca96f77428e316741082a1b"}, + {file = "pydantic-1.10.18-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23e8ec1ce4e57b4f441fc91e3c12adba023fedd06868445a5b5f1d48f0ab3682"}, + {file = "pydantic-1.10.18-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:44ae8a3e35a54d2e8fa88ed65e1b08967a9ef8c320819a969bfa09ce5528fafe"}, + {file = "pydantic-1.10.18-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5389eb3b48a72da28c6e061a247ab224381435256eb541e175798483368fdd3"}, + {file = "pydantic-1.10.18-cp38-cp38-win_amd64.whl", hash = "sha256:069b9c9fc645474d5ea3653788b544a9e0ccd3dca3ad8c900c4c6eac844b4620"}, + {file = "pydantic-1.10.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:80b982d42515632eb51f60fa1d217dfe0729f008e81a82d1544cc392e0a50ddf"}, + {file = "pydantic-1.10.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aad8771ec8dbf9139b01b56f66386537c6fe4e76c8f7a47c10261b69ad25c2c9"}, + {file = "pydantic-1.10.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941a2eb0a1509bd7f31e355912eb33b698eb0051730b2eaf9e70e2e1589cae1d"}, + {file = "pydantic-1.10.18-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65f7361a09b07915a98efd17fdec23103307a54db2000bb92095457ca758d485"}, + {file = "pydantic-1.10.18-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6951f3f47cb5ca4da536ab161ac0163cab31417d20c54c6de5ddcab8bc813c3f"}, + {file = "pydantic-1.10.18-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7a4c5eec138a9b52c67f664c7d51d4c7234c5ad65dd8aacd919fb47445a62c86"}, + {file = "pydantic-1.10.18-cp39-cp39-win_amd64.whl", hash = "sha256:49e26c51ca854286bffc22b69787a8d4063a62bf7d83dc21d44d2ff426108518"}, + {file = "pydantic-1.10.18-py3-none-any.whl", hash = "sha256:06a189b81ffc52746ec9c8c007f16e5167c8b0a696e1a726369327e3db7b2a82"}, + {file = "pydantic-1.10.18.tar.gz", hash = "sha256:baebdff1907d1d96a139c25136a9bb7d17e118f133a76a2ef3b845e831e3403a"}, ] [package.dependencies] @@ -4882,4 +4890,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.11,<3.13" -content-hash = "6053bca227f7d7b27b4ca023fb41afc336b919331196c0bef1daa81636948c61" +content-hash = "58f90b3d204474e915f555faf7deb4d7a9da7b6f6088e7c744e7e0c3784667c0" diff --git a/pyproject.toml b/pyproject.toml index fceb8e1f02..ac65940670 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ djangorestframework-jsonapi = "7.0.2" djangorestframework-simplejwt = "^5.3.1" drf-nested-routers = "^0.94.1" drf-spectacular = "0.27.2" -drf-spectacular-jsonapi = "0.4.1" +drf-spectacular-jsonapi = "0.5.1" gunicorn = "23.0.0" prowler = {git = "https://github.com/prowler-cloud/prowler.git", branch = "master"} psycopg2-binary = "2.9.9" diff --git a/src/backend/api/filters.py b/src/backend/api/filters.py index e9ef112d2e..341ef0e196 100644 --- a/src/backend/api/filters.py +++ b/src/backend/api/filters.py @@ -246,6 +246,7 @@ class FindingFilter(FilterSet): updated_at = DateFilter(field_name="updated_at", lookup_expr="date") + uid = CharFilter(field_name="uid") delta = ChoiceFilter(choices=Finding.DeltaChoices.choices) status = ChoiceFilter(choices=StatusChoices.choices) severity = ChoiceFilter(choices=SeverityChoices) @@ -294,6 +295,7 @@ class FindingFilter(FilterSet): class Meta: model = Finding fields = { + "uid": ["exact", "in"], "scan": ["exact", "in"], "delta": ["exact", "in"], "status": ["exact", "in"], diff --git a/src/backend/api/fixtures/5_dev_findings.json b/src/backend/api/fixtures/5_dev_findings.json index f081de8f77..57a9e7694e 100644 --- a/src/backend/api/fixtures/5_dev_findings.json +++ b/src/backend/api/fixtures/5_dev_findings.json @@ -5,6 +5,7 @@ "fields": { "tenant": "12646005-9067-4d2a-a098-8bb378604362", "scan": "0191e280-9d2f-71c8-9b18-487a23ba185e", + "uid": "prowler-aws-accessanalyzer_enabled-123456789012-eu-west-1-11111111", "status": "FAIL", "status_extended": "test status extended", "impact": "low", @@ -68,6 +69,7 @@ "pk": "01920bdb-faf8-7fbd-9aa4-ae144d4b4b91", "fields": { "tenant": "12646005-9067-4d2a-a098-8bb378604362", + "uid": "prowler-aws-workspaces_vpc_2private_1public_subnets_nat-123456789012-eu-west-1-11111112", "scan": "01920573-aa9c-73c9-bcda-f2e35c9b19d2", "status": "PASS", "status_extended": "test status extended", @@ -130,6 +132,7 @@ "pk": "01920bdf-8150-7630-a385-62a80cdc75b5", "fields": { "tenant": "12646005-9067-4d2a-a098-8bb378604362", + "uid": "prowler-aws-accessanalyzer_enabled-123456789012-eu-west-1-11111113", "scan": "01920573-ea5b-77fd-a93f-1ed2ae12f728", "status": "FAIL", "status_extended": "test status extended", @@ -194,6 +197,7 @@ "pk": "01920be0-8320-75ce-a9a2-f927821769fa", "fields": { "tenant": "12646005-9067-4d2a-a098-8bb378604362", + "uid": "prowler-aws-workspaces_vpc_2private_1public_subnets_nat-123456789012-eu-west-1-11111115", "scan": "01920573-ea5b-77fd-a93f-1ed2ae12f728", "status": "PASS", "status_extended": "test status extended", diff --git a/src/backend/api/migrations/0001_initial.py b/src/backend/api/migrations/0001_initial.py index 40cb5da16a..aba4eae2a1 100644 --- a/src/backend/api/migrations/0001_initial.py +++ b/src/backend/api/migrations/0001_initial.py @@ -883,6 +883,7 @@ class Migration(migrations.Migration): ), ("inserted_at", models.DateTimeField(auto_now_add=True)), ("updated_at", models.DateTimeField(auto_now=True)), + ("uid", models.CharField(max_length=150)), ( "delta", api.db_utils.FindingDeltaEnumField( @@ -993,6 +994,13 @@ class Migration(migrations.Migration): ), ], ), + migrations.AddIndex( + model_name="finding", + index=models.Index( + fields=["uid"], + name="findings_uid_idx", + ), + ), migrations.AddIndex( model_name="finding", index=models.Index( diff --git a/src/backend/api/models.py b/src/backend/api/models.py index f6e1c0baf6..b0446684b9 100644 --- a/src/backend/api/models.py +++ b/src/backend/api/models.py @@ -477,6 +477,7 @@ class Finding(PostgresPartitionedModel, RowLevelSecurityProtectedModel): inserted_at = models.DateTimeField(auto_now_add=True, editable=False) updated_at = models.DateTimeField(auto_now=True, editable=False) + uid = models.CharField(max_length=150) delta = FindingDeltaEnumField( choices=DeltaChoices.choices, blank=True, @@ -539,6 +540,7 @@ class Finding(PostgresPartitionedModel, RowLevelSecurityProtectedModel): ] indexes = [ + models.Index(fields=["uid"], name="findings_uid_idx"), models.Index( fields=[ "scan_id", diff --git a/src/backend/api/specs/v1.yaml b/src/backend/api/specs/v1.yaml index 7d98fca484..22dd9adaaf 100644 --- a/src/backend/api/specs/v1.yaml +++ b/src/backend/api/specs/v1.yaml @@ -22,6 +22,7 @@ paths: items: type: string enum: + - uid - delta - status - status_extended @@ -359,6 +360,19 @@ paths: description: Multiple values may be separated by commas. explode: false style: form + - in: query + name: filter[uid] + schema: + type: string + - in: query + name: filter[uid__in] + schema: + type: array + items: + type: string + description: Multiple values may be separated by commas. + explode: false + style: form - in: query name: filter[updated_at] schema: @@ -444,6 +458,7 @@ paths: items: type: string enum: + - uid - delta - status - status_extended @@ -2381,6 +2396,9 @@ components: attributes: type: object properties: + uid: + type: string + maxLength: 150 delta: enum: - new @@ -2434,6 +2452,7 @@ components: format: date-time readOnly: true required: + - uid - status - severity - check_id diff --git a/src/backend/api/v1/serializers.py b/src/backend/api/v1/serializers.py index ab14688e54..c3aa272e72 100644 --- a/src/backend/api/v1/serializers.py +++ b/src/backend/api/v1/serializers.py @@ -601,6 +601,7 @@ class FindingSerializer(RLSSerializer): model = Finding fields = [ "id", + "uid", "delta", "status", "status_extended", diff --git a/src/backend/api/v1/views.py b/src/backend/api/v1/views.py index 28a87c0432..6f9ed6bb43 100644 --- a/src/backend/api/v1/views.py +++ b/src/backend/api/v1/views.py @@ -717,7 +717,9 @@ class ResourceViewSet(BaseRLSViewSet): list=extend_schema( tags=["Finding"], summary="List all findings", - description="Retrieve a list of all findings with options for filtering by various criteria. If no `scan` or `inserted_at` filter is provided, a default filter will be applied that will return all findings for the current day.", + description="Retrieve a list of all findings with options for filtering by various criteria. " + "If no `scan` or `inserted_at` filter is provided, a default filter will be applied that will " + "return all findings for the current day.", ), retrieve=extend_schema( tags=["Finding"], diff --git a/src/backend/conftest.py b/src/backend/conftest.py index ff62565e01..3341b1fd45 100644 --- a/src/backend/conftest.py +++ b/src/backend/conftest.py @@ -290,6 +290,7 @@ def findings_fixture(scans_fixture, resources_fixture): finding1 = Finding.objects.create( tenant_id=scan.tenant_id, + uid="test_finding_uid_1", scan=scan, delta=None, status=Status.FAIL, @@ -314,6 +315,7 @@ def findings_fixture(scans_fixture, resources_fixture): finding2 = Finding.objects.create( tenant_id=scan.tenant_id, + uid="test_finding_uid_2", scan=scan, delta="new", status=Status.FAIL, diff --git a/src/backend/tasks/jobs/scan.py b/src/backend/tasks/jobs/scan.py index 2d2e129a73..2994122724 100644 --- a/src/backend/tasks/jobs/scan.py +++ b/src/backend/tasks/jobs/scan.py @@ -25,17 +25,37 @@ from api.v1.serializers import ScanTaskSerializer logger = get_task_logger(__name__) -def store_finding( +def _create_finding_delta( + last_status: FindingStatus | None | str, new_status: FindingStatus | None +) -> Finding.DeltaChoices: + if last_status is None: + return Finding.DeltaChoices.NEW + return Finding.DeltaChoices.CHANGED if last_status != new_status else None + + +def _store_finding( finding: ProwlerFinding, tenant_id: str, scan_instance: Scan, resource_instance: Resource, ) -> Finding: + finding_uid = finding.finding_uid + status = FindingStatus[finding.status.value] if finding.status is not None else None + with tenant_transaction(tenant_id): + most_recent_finding = ( + Finding.objects.filter(uid=finding_uid) + .order_by("-id") + .values("status") + .first() + ) + last_status = most_recent_finding["status"] if most_recent_finding else None + delta = _create_finding_delta(last_status, status) with tenant_transaction(tenant_id): finding_instance = Finding.objects.create( tenant_id=tenant_id, - delta=Finding.DeltaChoices.NEW, - status=FindingStatus[finding.status.value], + uid=finding_uid, + delta=delta, + status=status, status_extended=finding.status_extended, severity=finding.severity.value, impact=finding.severity.value, @@ -47,7 +67,7 @@ def store_finding( return finding_instance -def store_resources( +def _store_resources( finding: ProwlerFinding, tenant_id: str, provider_instance: Provider ) -> tuple[Resource, tuple[str, str]]: with tenant_transaction(tenant_id): @@ -113,10 +133,10 @@ def perform_prowler_scan( ) for progress, findings in prowler_scan.scan(): for finding in findings: - resource_instance, resource_uid_tuple = store_resources( + resource_instance, resource_uid_tuple = _store_resources( finding, tenant_id, provider_instance ) - store_finding(finding, tenant_id, scan_instance, resource_instance) + _store_finding(finding, tenant_id, scan_instance, resource_instance) unique_resources.add(resource_uid_tuple) with tenant_transaction(tenant_id): scan_instance.progress = progress diff --git a/src/backend/tasks/tests/test_scan.py b/src/backend/tasks/tests/test_scan.py index bf0d3d4dea..c389510066 100644 --- a/src/backend/tasks/tests/test_scan.py +++ b/src/backend/tasks/tests/test_scan.py @@ -4,7 +4,7 @@ from unittest.mock import patch, MagicMock, PropertyMock import pytest from api.models import StateChoices, StatusChoices, Severity, Finding, Resource -from tasks.jobs.scan import perform_prowler_scan +from tasks.jobs.scan import perform_prowler_scan, _create_finding_delta @pytest.mark.django_db @@ -40,6 +40,7 @@ class TestPerformScan: mock_aws_provider_instance.test_connection.return_value = connection_status_mock finding = MagicMock() + finding.finding_uid = "this_is_a_test_finding_id" finding.status = StatusChoices.PASS finding.status_extended = "test status extended" finding.severity = Severity.medium @@ -65,6 +66,7 @@ class TestPerformScan: assert scan.started_at is not None assert scan.unique_resource_count == 1 assert scan.progress == 100 + assert scan_finding.uid == finding.finding_uid assert scan_finding.status == finding.status assert scan_finding.status_extended == finding.status_extended assert scan_finding.severity == finding.severity @@ -109,3 +111,16 @@ class TestPerformScan: scan.refresh_from_db() assert scan.state == StateChoices.FAILED + + @pytest.mark.parametrize( + "last_status, new_status, expected_delta", + [ + (None, None, Finding.DeltaChoices.NEW), + (None, StatusChoices.PASS, Finding.DeltaChoices.NEW), + (StatusChoices.PASS, StatusChoices.PASS, None), + (StatusChoices.PASS, StatusChoices.FAIL, Finding.DeltaChoices.CHANGED), + (StatusChoices.FAIL, StatusChoices.PASS, Finding.DeltaChoices.CHANGED), + ], + ) + def test_create_finding_delta(self, last_status, new_status, expected_delta): + assert _create_finding_delta(last_status, new_status) == expected_delta