From 8ae989cce8415503a7946765cf6a8dbdb2a05c72 Mon Sep 17 00:00:00 2001 From: Nacho Rivera <59198746+n4ch04@users.noreply.github.com> Date: Thu, 3 Nov 2022 15:39:41 +0100 Subject: [PATCH] feat(cloudtrail): cloudtrail service and checks (#1449) Co-authored-by: sergargar Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com> --- .gitignore | 3 + Pipfile | 2 +- Pipfile.lock | 239 ++++-------------- providers/aws/services/cloudtrail/__init__.py | 0 providers/aws/services/cloudtrail/check21 | 68 ----- providers/aws/services/cloudtrail/check22 | 60 ----- providers/aws/services/cloudtrail/check23 | 94 ------- providers/aws/services/cloudtrail/check24 | 66 ----- providers/aws/services/cloudtrail/check27 | 59 ----- .../services/cloudtrail/cloudtrail_client.py | 4 + .../__init__.py | 0 ...l_cloudwatch_logging_enabled.metadata.json | 35 +++ .../cloudtrail_cloudwatch_logging_enabled.py | 44 ++++ ...udtrail_cloudwatch_logging_enabled_test.py | 225 +++++++++++++++++ .../__init__.py | 0 ...trail_kms_encryption_enabled.metadata.json | 35 +++ .../cloudtrail_kms_encryption_enabled.py | 35 +++ .../cloudtrail_kms_encryption_enabled_test.py | 89 +++++++ .../__init__.py | 0 ..._log_file_validation_enabled.metadata.json | 35 +++ .../cloudtrail_log_file_validation_enabled.py | 31 +++ ...dtrail_log_file_validation_enabled_test.py | 102 ++++++++ .../__init__.py | 0 ...ucket_access_logging_enabled.metadata.json | 35 +++ ...l_logs_s3_bucket_access_logging_enabled.py | 31 +++ ...s_s3_bucket_access_logging_enabled_test.py | 113 +++++++++ .../__init__.py | 0 ...t_is_not_publicly_accessible.metadata.json | 35 +++ ...gs_s3_bucket_is_not_publicly_accessible.py | 39 +++ ..._bucket_is_not_publicly_accessible_test.py | 166 ++++++++++++ .../__init__.py | 0 ...udtrail_multi_region_enabled.metadata.json | 46 ++++ .../cloudtrail_multi_region_enabled.py | 47 ++++ .../cloudtrail_multi_region_enabled_test.py | 155 ++++++++++++ .../services/cloudtrail/cloudtrail_service.py | 131 ++++++++++ .../cloudtrail/cloudtrail_service_test.py | 145 +++++++++++ providers/aws/services/s3/s3_service.py | 46 ++++ providers/aws/services/s3/s3_service_test.py | 35 +++ 38 files changed, 1716 insertions(+), 534 deletions(-) create mode 100644 providers/aws/services/cloudtrail/__init__.py delete mode 100644 providers/aws/services/cloudtrail/check21 delete mode 100644 providers/aws/services/cloudtrail/check22 delete mode 100644 providers/aws/services/cloudtrail/check23 delete mode 100644 providers/aws/services/cloudtrail/check24 delete mode 100644 providers/aws/services/cloudtrail/check27 create mode 100644 providers/aws/services/cloudtrail/cloudtrail_client.py create mode 100644 providers/aws/services/cloudtrail/cloudtrail_cloudwatch_logging_enabled/__init__.py create mode 100644 providers/aws/services/cloudtrail/cloudtrail_cloudwatch_logging_enabled/cloudtrail_cloudwatch_logging_enabled.metadata.json create mode 100644 providers/aws/services/cloudtrail/cloudtrail_cloudwatch_logging_enabled/cloudtrail_cloudwatch_logging_enabled.py create mode 100644 providers/aws/services/cloudtrail/cloudtrail_cloudwatch_logging_enabled/cloudtrail_cloudwatch_logging_enabled_test.py create mode 100644 providers/aws/services/cloudtrail/cloudtrail_kms_encryption_enabled/__init__.py create mode 100644 providers/aws/services/cloudtrail/cloudtrail_kms_encryption_enabled/cloudtrail_kms_encryption_enabled.metadata.json create mode 100644 providers/aws/services/cloudtrail/cloudtrail_kms_encryption_enabled/cloudtrail_kms_encryption_enabled.py create mode 100644 providers/aws/services/cloudtrail/cloudtrail_kms_encryption_enabled/cloudtrail_kms_encryption_enabled_test.py create mode 100644 providers/aws/services/cloudtrail/cloudtrail_log_file_validation_enabled/__init__.py create mode 100644 providers/aws/services/cloudtrail/cloudtrail_log_file_validation_enabled/cloudtrail_log_file_validation_enabled.metadata.json create mode 100644 providers/aws/services/cloudtrail/cloudtrail_log_file_validation_enabled/cloudtrail_log_file_validation_enabled.py create mode 100644 providers/aws/services/cloudtrail/cloudtrail_log_file_validation_enabled/cloudtrail_log_file_validation_enabled_test.py create mode 100644 providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_access_logging_enabled/__init__.py create mode 100644 providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_access_logging_enabled/cloudtrail_logs_s3_bucket_access_logging_enabled.metadata.json create mode 100644 providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_access_logging_enabled/cloudtrail_logs_s3_bucket_access_logging_enabled.py create mode 100644 providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_access_logging_enabled/cloudtrail_logs_s3_bucket_access_logging_enabled_test.py create mode 100644 providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/__init__.py create mode 100644 providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible.metadata.json create mode 100644 providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible.py create mode 100644 providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible_test.py create mode 100644 providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled/__init__.py create mode 100644 providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled/cloudtrail_multi_region_enabled.metadata.json create mode 100644 providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled/cloudtrail_multi_region_enabled.py create mode 100644 providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled/cloudtrail_multi_region_enabled_test.py create mode 100644 providers/aws/services/cloudtrail/cloudtrail_service.py create mode 100644 providers/aws/services/cloudtrail/cloudtrail_service_test.py diff --git a/.gitignore b/.gitignore index 0638fd54..99e639d9 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,6 @@ junit-reports/ # Text *.txt + +# .env +.env diff --git a/Pipfile b/Pipfile index 52c6f25e..b3fe11f5 100644 --- a/Pipfile +++ b/Pipfile @@ -9,7 +9,7 @@ boto3 = "1.24.8" arnparse = "0.0.2" botocore = "1.27.8" pydantic = "1.9.1" -moto = "4.0.8" +moto = "4.0.9" sure = "2.0.0" bandit = "1.7.4" safety = "1.10.3" diff --git a/Pipfile.lock b/Pipfile.lock index 15757531..eca2ef87 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "02042e0bff2fe10e70314636dd3fe288654c6b0deb47a1270b0d1dbcf3660cd5" + "sha256": "17d457477683f58bd28bc11042edcc0374d620d037243490cc3c7803cb5a4536" }, "pipfile-spec": 6, "requires": { @@ -42,25 +42,26 @@ }, "boto3": { "hashes": [ - "sha256:2284a107d43f73b6007c7c8b946a8fd6f9baa6c97b5c956edc67d9be864def58" + "sha256:0a19d07a39d69b8e84e24d75474bbf4e737b1749d0c665503dfc2446f321e1f0", + "sha256:8f0e4eb5c26f927c09bc03302d38156af578b816f1e4f8ca4f0f734d134b9d4f" ], "index": "pypi", - "version": "==1.25.3" + "version": "==1.26.0" }, "botocore": { "hashes": [ - "sha256:2c2604262e5ab35ea83e9d5cf8be267e7fcdab6c815a432cfe15f23d92ce723d", - "sha256:4ea45626d8c5875c12e5767aa637388ce81871162f494eaf7b3888e875de84b7" + "sha256:c706640f8cf9297d2af87fc711394631afc14aaa225c7554e220964b5047b47d", + "sha256:f25dc0827005f81abf4b890a20c71f64ee40ea9e9795df38a242fdeed79e0a89" ], "index": "pypi", - "version": "==1.28.3" + "version": "==1.29.0" }, "certifi": { "hashes": [ "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14", "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382" ], - "markers": "python_version >= '3.6'", + "markers": "python_full_version >= '3.6.0'", "version": "==2022.9.24" }, "cffi": { @@ -221,51 +222,35 @@ }, "cryptography": { "hashes": [ - "sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a", - "sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f", - "sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0", - "sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407", - "sha256:1db3d807a14931fa317f96435695d9ec386be7b84b618cc61cfa5d08b0ae33d7", - "sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6", - "sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153", - "sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750", - "sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad", - "sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6", - "sha256:52e7bee800ec869b4031093875279f1ff2ed12c1e2f74923e8f49c916afd1d3b", - "sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5", - "sha256:765fa194a0f3372d83005ab83ab35d7c5526c4e22951e46059b8ac678b44fa5a", - "sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d", - "sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d", - "sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294", - "sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0", - "sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a", - "sha256:b0163a849b6f315bf52815e238bc2b2346604413fa7c1601eea84bcddb5fb9ac", - "sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61", - "sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013", - "sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e", - "sha256:d1a5bd52d684e49a36582193e0b89ff267704cd4025abefb9e26803adeb3e5fb", - "sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9", - "sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd", - "sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818" + "sha256:068147f32fa662c81aebab95c74679b401b12b57494872886eb5c1139250ec5d", + "sha256:06fc3cc7b6f6cca87bd56ec80a580c88f1da5306f505876a71c8cfa7050257dd", + "sha256:25c1d1f19729fb09d42e06b4bf9895212292cb27bb50229f5aa64d039ab29146", + "sha256:402852a0aea73833d982cabb6d0c3bb582c15483d29fb7085ef2c42bfa7e38d7", + "sha256:4e269dcd9b102c5a3d72be3c45d8ce20377b8076a43cbed6f660a1afe365e436", + "sha256:5419a127426084933076132d317911e3c6eb77568a1ce23c3ac1e12d111e61e0", + "sha256:554bec92ee7d1e9d10ded2f7e92a5d70c1f74ba9524947c0ba0c850c7b011828", + "sha256:5e89468fbd2fcd733b5899333bc54d0d06c80e04cd23d8c6f3e0542358c6060b", + "sha256:65535bc550b70bd6271984d9863a37741352b4aad6fb1b3344a54e6950249b55", + "sha256:6ab9516b85bebe7aa83f309bacc5f44a61eeb90d0b4ec125d2d003ce41932d36", + "sha256:6addc3b6d593cd980989261dc1cce38263c76954d758c3c94de51f1e010c9a50", + "sha256:728f2694fa743a996d7784a6194da430f197d5c58e2f4e278612b359f455e4a2", + "sha256:785e4056b5a8b28f05a533fab69febf5004458e20dad7e2e13a3120d8ecec75a", + "sha256:78cf5eefac2b52c10398a42765bfa981ce2372cbc0457e6bf9658f41ec3c41d8", + "sha256:7f836217000342d448e1c9a342e9163149e45d5b5eca76a30e84503a5a96cab0", + "sha256:8d41a46251bf0634e21fac50ffd643216ccecfaf3701a063257fe0b2be1b6548", + "sha256:984fe150f350a3c91e84de405fe49e688aa6092b3525f407a18b9646f6612320", + "sha256:9b24bcff7853ed18a63cfb0c2b008936a9554af24af2fb146e16d8e1aed75748", + "sha256:b1b35d9d3a65542ed2e9d90115dfd16bbc027b3f07ee3304fc83580f26e43249", + "sha256:b1b52c9e5f8aa2b802d48bd693190341fae201ea51c7a167d69fc48b60e8a959", + "sha256:bbf203f1a814007ce24bd4d51362991d5cb90ba0c177a9c08825f2cc304d871f", + "sha256:be243c7e2bfcf6cc4cb350c0d5cdf15ca6383bbcb2a8ef51d3c9411a9d4386f0", + "sha256:bfbe6ee19615b07a98b1d2287d6a6073f734735b49ee45b11324d85efc4d5cbd", + "sha256:c46837ea467ed1efea562bbeb543994c2d1f6e800785bd5a2c98bc096f5cb220", + "sha256:dfb4f4dd568de1b6af9f4cda334adf7d72cf5bc052516e1b2608b683375dd95c", + "sha256:ed7b00096790213e09eb11c97cc6e2b757f15f3d2f85833cd2d3ec3fe37c1722" ], - "markers": "python_version >= '3.6'", - "version": "==38.0.1" - }, - "detect-secrets": { - "hashes": [ - "sha256:d08ecabeee8b68c0acb0e8a354fb98d822a653f6ed05e520cead4c6fc1fc02cd", - "sha256:d56787e339758cef48c9ccd6692f7a094b9963c979c9813580b0169e41132833" - ], - "index": "pypi", - "version": "==1.4.0" - }, - "docker": { - "hashes": [ - "sha256:19e330470af40167d293b0352578c1fa22d74b34d3edf5d4ff90ebc203bbb2f1", - "sha256:6e06ee8eca46cd88733df09b6b80c24a1a556bc5cb1e1ae54b2c239886d245cf" - ], - "index": "pypi", - "version": "==6.0.0" + "markers": "python_full_version >= '3.6.0'", + "version": "==38.0.3" }, "dparse": { "hashes": [ @@ -296,7 +281,7 @@ "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd", "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa" ], - "markers": "python_version >= '3.6'", + "markers": "python_full_version >= '3.6.0'", "version": "==4.0.9" }, "gitpython": { @@ -315,14 +300,6 @@ "markers": "python_version >= '3.5'", "version": "==3.4" }, - "importlib-resources": { - "hashes": [ - "sha256:c01b1b94210d9849f286b86bb51bcea7cd56dde0600d8db721d7b81330711668", - "sha256:ee17ec648f85480d523596ce49eae8ead87d5631ae1551f913c0100b5edd3437" - ], - "markers": "python_version >= '3.7'", - "version": "==5.10.0" - }, "iniconfig": { "hashes": [ "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", @@ -346,47 +323,6 @@ "markers": "python_version >= '3.7'", "version": "==1.0.1" }, - "jsonschema": { - "hashes": [ - "sha256:165059f076eff6971bae5b742fc029a7b4ef3f9bcf04c14e4776a7605de14b23", - "sha256:9e74b8f9738d6a946d70705dc692b74b5429cd0960d58e79ffecfc43b2221eb9" - ], - "markers": "python_version >= '3.7'", - "version": "==4.16.0" - }, - "jsonschema-spec": { - "hashes": [ - "sha256:1e525177574c23ae0f55cd62382632a083a0339928f0ca846a975a4da9851cec", - "sha256:780a22d517cdc857d9714a80d8349c546945063f20853ea32ba7f85bc643ec7d" - ], - "markers": "python_full_version >= '3.7.0' and python_full_version < '4.0.0'", - "version": "==0.1.2" - }, - "lazy-object-proxy": { - "hashes": [ - "sha256:0c1c7c0433154bb7c54185714c6929acc0ba04ee1b167314a779b9025517eada", - "sha256:14010b49a2f56ec4943b6cf925f597b534ee2fe1f0738c84b3bce0c1a11ff10d", - "sha256:4e2d9f764f1befd8bdc97673261b8bb888764dfdbd7a4d8f55e4fbcabb8c3fb7", - "sha256:4fd031589121ad46e293629b39604031d354043bb5cdf83da4e93c2d7f3389fe", - "sha256:5b51d6f3bfeb289dfd4e95de2ecd464cd51982fe6f00e2be1d0bf94864d58acd", - "sha256:6850e4aeca6d0df35bb06e05c8b934ff7c533734eb51d0ceb2d63696f1e6030c", - "sha256:6f593f26c470a379cf7f5bc6db6b5f1722353e7bf937b8d0d0b3fba911998858", - "sha256:71d9ae8a82203511a6f60ca5a1b9f8ad201cac0fc75038b2dc5fa519589c9288", - "sha256:7e1561626c49cb394268edd00501b289053a652ed762c58e1081224c8d881cec", - "sha256:8f6ce2118a90efa7f62dd38c7dbfffd42f468b180287b748626293bf12ed468f", - "sha256:ae032743794fba4d171b5b67310d69176287b5bf82a21f588282406a79498891", - "sha256:afcaa24e48bb23b3be31e329deb3f1858f1f1df86aea3d70cb5c8578bfe5261c", - "sha256:b70d6e7a332eb0217e7872a73926ad4fdc14f846e85ad6749ad111084e76df25", - "sha256:c219a00245af0f6fa4e95901ed28044544f50152840c5b6a3e7b2568db34d156", - "sha256:ce58b2b3734c73e68f0e30e4e725264d4d6be95818ec0a0be4bb6bf9a7e79aa8", - "sha256:d176f392dbbdaacccf15919c77f526edf11a34aece58b55ab58539807b85436f", - "sha256:e20bfa6db17a39c706d24f82df8352488d2943a3b7ce7d4c22579cb89ca8896e", - "sha256:eac3a9a5ef13b332c059772fd40b4b1c3d45a3a2b05e33a361dee48e54a4dad0", - "sha256:eb329f8d8145379bf5dbe722182410fe8863d186e51bf034d2075eb8d85ee25b" - ], - "markers": "python_version >= '3.7'", - "version": "==1.8.0" - }, "markupsafe": { "hashes": [ "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", @@ -438,52 +374,26 @@ "sha256:122fcb64ee37cfad5b3f48d7a7d51875d7031aaf3d8be7c42e2bee25044eee62", "sha256:7d3fbbde18228f4ff2f1f119a45cdffa458b4c0dee32eb4d2bb2f82554bac7bc" ], - "markers": "python_version >= '3.6'", + "markers": "python_full_version >= '3.6.0'", "version": "==4.0.3" }, "moto": { - "extras": [ - "iam" - ], + "extras": [], "hashes": [ - "sha256:3bd8a72dc385819c84ed3f9d18c386985634041c6ae544d957bd8ab88c6c15f1", - "sha256:8761880f5f3c3af27daac3882a56aae7e21fa2121f430cf917e55e977bddaf6b" + "sha256:2fb909d2ea1b732f89604e4268e2c2207c253e590a635a410c3c2aaebb34e113", + "sha256:ba03b638cf3b1cec64cbe9ac0d184ca898b69020c8e3c5b9b4961c1670629010" ], "index": "pypi", - "version": "==4.0.8" - }, - "openapi-schema-validator": { - "hashes": [ - "sha256:34fbd14b7501abe25e64d7b4624a9db02cde1a578d285b3da6f34b290cdf0b3a", - "sha256:7cf27585dd7970b7257cefe48e1a3a10d4e34421831bdb472d96967433bc27bd" - ], - "markers": "python_full_version >= '3.7.0' and python_full_version < '4.0.0'", - "version": "==0.3.4" - }, - "openapi-spec-validator": { - "hashes": [ - "sha256:4a8aee1e45b1ac868e07ab25e18828fe9837baddd29a8e20fdb3d3c61c8eea3d", - "sha256:8248634bad1f23cac5d5a34e193ab36e23914057ca69e91a1ede5af75552c465" - ], - "index": "pypi", - "version": "==0.5.1" + "version": "==4.0.9" }, "packaging": { "hashes": [ "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" ], - "markers": "python_version >= '3.6'", + "markers": "python_full_version >= '3.6.0'", "version": "==21.3" }, - "pathable": { - "hashes": [ - "sha256:5c869d315be50776cc8a993f3af43e0c60dc01506b399643f919034ebf4cdcab", - "sha256:cdd7b1f9d7d5c8b8d3315dbf5a86b2596053ae845f056f57d97c0eefff84da14" - ], - "markers": "python_full_version >= '3.7.0' and python_full_version < '4.0.0'", - "version": "==0.4.3" - }, "pbr": { "hashes": [ "sha256:b97bc6695b2aff02144133c2e7399d5885223d42b7912ffaec2ca3898e673bfe", @@ -497,7 +407,7 @@ "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" ], - "markers": "python_version >= '3.6'", + "markers": "python_full_version >= '3.6.0'", "version": "==1.0.0" }, "pycparser": { @@ -557,33 +467,6 @@ "markers": "python_full_version >= '3.6.8'", "version": "==3.0.9" }, - "pyrsistent": { - "hashes": [ - "sha256:0e3e1fcc45199df76053026a51cc59ab2ea3fc7c094c6627e93b7b44cdae2c8c", - "sha256:1b34eedd6812bf4d33814fca1b66005805d3640ce53140ab8bbb1e2651b0d9bc", - "sha256:4ed6784ceac462a7d6fcb7e9b663e93b9a6fb373b7f43594f9ff68875788e01e", - "sha256:5d45866ececf4a5fff8742c25722da6d4c9e180daa7b405dc0a2a2790d668c26", - "sha256:636ce2dc235046ccd3d8c56a7ad54e99d5c1cd0ef07d9ae847306c91d11b5fec", - "sha256:6455fc599df93d1f60e1c5c4fe471499f08d190d57eca040c0ea182301321286", - "sha256:6bc66318fb7ee012071b2792024564973ecc80e9522842eb4e17743604b5e045", - "sha256:7bfe2388663fd18bd8ce7db2c91c7400bf3e1a9e8bd7d63bf7e77d39051b85ec", - "sha256:7ec335fc998faa4febe75cc5268a9eac0478b3f681602c1f27befaf2a1abe1d8", - "sha256:914474c9f1d93080338ace89cb2acee74f4f666fb0424896fcfb8d86058bf17c", - "sha256:b568f35ad53a7b07ed9b1b2bae09eb15cdd671a5ba5d2c66caee40dbf91c68ca", - "sha256:cdfd2c361b8a8e5d9499b9082b501c452ade8bbf42aef97ea04854f4a3f43b22", - "sha256:d1b96547410f76078eaf66d282ddca2e4baae8964364abb4f4dcdde855cd123a", - "sha256:d4d61f8b993a7255ba714df3aca52700f8125289f84f704cf80916517c46eb96", - "sha256:d7a096646eab884bf8bed965bad63ea327e0d0c38989fc83c5ea7b8a87037bfc", - "sha256:df46c854f490f81210870e509818b729db4488e1f30f2a1ce1698b2295a878d1", - "sha256:e24a828f57e0c337c8d8bb9f6b12f09dfdf0273da25fda9e314f0b684b415a07", - "sha256:e4f3149fd5eb9b285d6bfb54d2e5173f6a116fe19172686797c056672689daf6", - "sha256:e92a52c166426efbe0d1ec1332ee9119b6d32fc1f0bbfd55d5c1088070e7fc1b", - "sha256:f87cc2863ef33c709e237d4b5f4502a62a00fab450c9e020892e8e2ede5847f5", - "sha256:fd8da6d0124efa2f67d86fa70c851022f87c98e205f0594e1fae044e7119a5a6" - ], - "markers": "python_version >= '3.7'", - "version": "==0.18.1" - }, "pytest": { "hashes": [ "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71", @@ -605,15 +488,15 @@ "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.8.2" }, "pytz": { "hashes": [ - "sha256:335ab46900b1465e714b4fda4963d87363264eb662aab5e65da039c25f1f5b22", - "sha256:c4d88f472f54d615e9cd582a5004d1e5f624854a6a27a6211591c251f22a6914" + "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427", + "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2" ], - "version": "==2022.5" + "version": "==2022.6" }, "pyyaml": { "hashes": [ @@ -658,7 +541,7 @@ "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" ], - "markers": "python_version >= '3.6'", + "markers": "python_full_version >= '3.6.0'", "version": "==6.0" }, "requests": { @@ -721,7 +604,7 @@ "sha256:f01da5790e95815eb5a8a138508c01c758e5f5bc0ce4286c4f7028b8dd7ac3d0", "sha256:f34019dced51047d6f70cb9383b2ae2853b7fc4dce65129a5acd49f4f9256646" ], - "markers": "platform_python_implementation == 'CPython' and python_version < '3.11'", + "markers": "python_version < '3.11' and platform_python_implementation == 'CPython'", "version": "==0.2.7" }, "s3transfer": { @@ -760,7 +643,7 @@ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.16.0" }, "smmap": { @@ -768,7 +651,7 @@ "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94", "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936" ], - "markers": "python_version >= '3.6'", + "markers": "python_full_version >= '3.6.0'", "version": "==5.0.0" }, "stevedore": { @@ -791,7 +674,7 @@ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.10.2" }, "tomli": { @@ -833,14 +716,6 @@ "index": "pypi", "version": "==2.6" }, - "websocket-client": { - "hashes": [ - "sha256:398909eb7e261f44b8f4bd474785b6ec5f5b499d4953342fe9755e01ef624090", - "sha256:f9611eb65c8241a67fb373bef040b3cf8ad377a9f6546a12b620b6511e8ea9ef" - ], - "markers": "python_version >= '3.7'", - "version": "==1.4.1" - }, "werkzeug": { "hashes": [ "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f", @@ -864,14 +739,6 @@ ], "markers": "python_version >= '3.4'", "version": "==0.13.0" - }, - "zipp": { - "hashes": [ - "sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1", - "sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8" - ], - "markers": "python_version < '3.10'", - "version": "==3.10.0" } }, "develop": {} diff --git a/providers/aws/services/cloudtrail/__init__.py b/providers/aws/services/cloudtrail/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/cloudtrail/check21 b/providers/aws/services/cloudtrail/check21 deleted file mode 100644 index db08b75e..00000000 --- a/providers/aws/services/cloudtrail/check21 +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env bash - -# Prowler - the handy cloud security tool (copyright 2019) by Toni de la Fuente -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy -# of the License at http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed -# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -CHECK_ID_check21="2.1" -CHECK_TITLE_check21="[check21] Ensure CloudTrail is enabled in all regions" -CHECK_SCORED_check21="SCORED" -CHECK_CIS_LEVEL_check21="LEVEL1" -CHECK_SEVERITY_check21="High" -CHECK_ASFF_TYPE_check21="Software and Configuration Checks/Industry and Regulatory Standards/CIS AWS Foundations Benchmark" -CHECK_ASFF_RESOURCE_TYPE_check21="AwsCloudTrailTrail" -CHECK_ALTERNATE_check201="check21" -CHECK_ASFF_COMPLIANCE_TYPE_check21="ens-op.acc.7.aws.iam.1 ens-op.mon.1.aws.trail.1" -CHECK_SERVICENAME_check21="cloudtrail" -CHECK_RISK_check21='AWS CloudTrail is a web service that records AWS API calls for your account and delivers log files to you. The recorded information includes the identity of the API caller; the time of the API call; the source IP address of the API caller; the request parameters; and the response elements returned by the AWS service.' -CHECK_REMEDIATION_check21='Ensure Logging is set to ON on all regions (even if they are not being used at the moment.' -CHECK_DOC_check21='https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrailconcepts.html#cloudtrail-concepts-management-events' -CHECK_CAF_EPIC_check21='Logging and Monitoring' - -check21(){ - trail_count=0 - # "Ensure CloudTrail is enabled in all regions (Scored)" - for regx in $REGIONS; do - TRAILS_AND_REGIONS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $regx --query 'trailList[*].{Name:TrailARN, HomeRegion:HomeRegion}' --output text 2>&1 | tr " " ',') - if [[ $(echo "$TRAILS_AND_REGIONS" | grep AccessDenied) ]]; then - textInfo "$regx: Access Denied trying to describe trails" "$regx" "$trail" - continue - fi - if [[ $TRAILS_AND_REGIONS ]]; then - for reg_trail in $TRAILS_AND_REGIONS; do - TRAIL_REGION=$(echo $reg_trail | cut -d',' -f1) - if [ $TRAIL_REGION != $regx ]; then # Only report trails once in home region - continue - fi - trail=$(echo $reg_trail | cut -d',' -f2) - trail_count=$((trail_count + 1)) - - MULTIREGION_TRAIL_STATUS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $TRAIL_REGION --query 'trailList[*].IsMultiRegionTrail' --output text --trail-name-list $trail) - if [[ "$MULTIREGION_TRAIL_STATUS" == 'False' ]];then - textFail "$regx: Trail $trail is not enabled for all regions" "$regx" "$trail" - else - TRAIL_ON_OFF_STATUS=$($AWSCLI cloudtrail get-trail-status $PROFILE_OPT --region $TRAIL_REGION --name $trail --query IsLogging --output text) - if [[ "$TRAIL_ON_OFF_STATUS" == 'False' ]];then - textFail "$regx: Trail $trail is configured for all regions but it is OFF" "$regx" "$trail" - else - textPass "$regx: Trail $trail is enabled for all regions" "$regx" "$trail" - fi - fi - done - fi - done - if [[ $trail_count == 0 ]]; then - if [[ $FILTERREGION ]]; then - textFail "$regx: No CloudTrail trails were found in the filtered region" "$regx" "$trail" - else - textFail "$regx: No CloudTrail trails were found in the account" "$regx" "$trail" - fi - fi -} diff --git a/providers/aws/services/cloudtrail/check22 b/providers/aws/services/cloudtrail/check22 deleted file mode 100644 index 98488b2b..00000000 --- a/providers/aws/services/cloudtrail/check22 +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env bash - -# Prowler - the handy cloud security tool (copyright 2019) by Toni de la Fuente -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy -# of the License at http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed -# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -CHECK_ID_check22="2.2" -CHECK_TITLE_check22="[check22] Ensure CloudTrail log file validation is enabled" -CHECK_SCORED_check22="SCORED" -CHECK_CIS_LEVEL_check22="LEVEL2" -CHECK_SEVERITY_check22="Medium" -CHECK_ASFF_TYPE_check22="Software and Configuration Checks/Industry and Regulatory Standards/CIS AWS Foundations Benchmark" -CHECK_ASFF_RESOURCE_TYPE_check22="AwsCloudTrailTrail" -CHECK_ALTERNATE_check202="check22" -CHECK_ASFF_COMPLIANCE_TYPE_check22="ens-op.exp.10.aws.trail.1" -CHECK_SERVICENAME_check22="cloudtrail" -CHECK_RISK_check22='Enabling log file validation will provide additional integrity checking of CloudTrail logs. ' -CHECK_REMEDIATION_check22='Ensure LogFileValidationEnabled is set to true for each trail.' -CHECK_DOC_check22='http://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-log-filevalidation-enabling.html' -CHECK_CAF_EPIC_check22='Logging and Monitoring' - -check22(){ - trail_count=0 - # "Ensure CloudTrail log file validation is enabled (Scored)" - for regx in $REGIONS; do - TRAILS_AND_REGIONS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $regx --query 'trailList[*].{Name:TrailARN, HomeRegion:HomeRegion}' --output text 2>&1 | tr " " ',') - if [[ $(echo "$TRAILS_AND_REGIONS" | grep AccessDenied) ]]; then - textInfo "$regx: Access Denied trying to describe trails" "$regx" "$trail" - continue - fi - if [[ $TRAILS_AND_REGIONS ]]; then - for reg_trail in $TRAILS_AND_REGIONS; do - TRAIL_REGION=$(echo $reg_trail | cut -d',' -f1) - if [ $TRAIL_REGION != $regx ]; then # Only report trails once in home region - continue - fi - trail=$(echo $reg_trail | cut -d',' -f2) - trail_count=$((trail_count + 1)) - - LOGFILEVALIDATION_TRAIL_STATUS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $TRAIL_REGION --query 'trailList[*].LogFileValidationEnabled' --output text --trail-name-list $trail) - if [[ "$LOGFILEVALIDATION_TRAIL_STATUS" == 'False' ]];then - textFail "$regx: Trail $trail log file validation disabled" "$regx" "$trail" - else - textPass "$regx: Trail $trail log file validation enabled" "$regx" "$trail" - fi - - done - fi - done - if [[ $trail_count == 0 ]]; then - textFail "$REGION: No CloudTrail trails were found in the account" "$REGION" "$trail" - fi -} diff --git a/providers/aws/services/cloudtrail/check23 b/providers/aws/services/cloudtrail/check23 deleted file mode 100644 index f8c5fde0..00000000 --- a/providers/aws/services/cloudtrail/check23 +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env bash - -# Prowler - the handy cloud security tool (copyright 2019) by Toni de la Fuente -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy -# of the License at http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed -# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -CHECK_ID_check23="2.3" -CHECK_TITLE_check23="[check23] Ensure the S3 bucket CloudTrail logs to is not publicly accessible" -CHECK_SCORED_check23="SCORED" -CHECK_CIS_LEVEL_check23="LEVEL1" -CHECK_SEVERITY_check23="Critical" -CHECK_ASFF_TYPE_check23="Software and Configuration Checks/Industry and Regulatory Standards/CIS AWS Foundations Benchmark" -CHECK_ASFF_RESOURCE_TYPE_check23="AwsS3Bucket" -CHECK_ALTERNATE_check203="check23" -CHECK_ASFF_COMPLIANCE_TYPE_check23="ens-op.exp.10.aws.trail.3 ens-op.exp.10.aws.trail.4" -CHECK_SERVICENAME_check23="cloudtrail" -CHECK_RISK_check23='Allowing public access to CloudTrail log content may aid an adversary in identifying weaknesses in the affected accounts use or configuration.' -CHECK_REMEDIATION_check23='Analyze Bucket policy to validate appropriate permissions. Ensure the AllUsers principal is not granted privileges. Ensure the AuthenticatedUsers principal is not granted privileges.' -CHECK_DOC_check23='https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_principal.html' -CHECK_CAF_EPIC_check23='Logging and Monitoring' - -check23(){ - trail_count=0 - # "Ensure the S3 bucket CloudTrail logs to is not publicly accessible (Scored)" - for regx in $REGIONS; do - TRAILS_AND_REGIONS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $regx --query 'trailList[*].{Name:TrailARN, HomeRegion:HomeRegion}' --output text 2>&1 | tr " " ',') - if [[ $(echo "$TRAILS_AND_REGIONS" | grep AccessDenied) ]]; then - textInfo "$regx: Access Denied trying to describe trails" "$regx" "$trail" - continue - fi - if [[ $TRAILS_AND_REGIONS ]]; then - for reg_trail in $TRAILS_AND_REGIONS; do - TRAIL_REGION=$(echo $reg_trail | cut -d',' -f1) - if [ $TRAIL_REGION != $regx ]; then # Only report trails once in home region - continue - fi - trail=$(echo $reg_trail | cut -d',' -f2) - trail_count=$((trail_count + 1)) - - CLOUDTRAILBUCKET=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $TRAIL_REGION --query 'trailList[*].[S3BucketName]' --output text --trail-name-list $trail) - if [[ -z $CLOUDTRAILBUCKET ]]; then - textFail "Trail $trail in $TRAIL_REGION does not publish to S3" - continue - fi - - CLOUDTRAIL_ACCOUNT_ID=$(echo $trail | awk -F: '{ print $5 }') - if [ "$CLOUDTRAIL_ACCOUNT_ID" != "$ACCOUNT_NUM" ]; then - textInfo "Trail $trail in $TRAIL_REGION S3 logging bucket $CLOUDTRAILBUCKET is not in current account" - continue - fi - - # - # LOCATION - requests referencing buckets created after March 20, 2019 - # must be made to S3 endpoints in the same region as the bucket was - # created. - # - BUCKET_LOCATION=$($AWSCLI s3api get-bucket-location $PROFILE_OPT --region $regx --bucket $CLOUDTRAILBUCKET --output text 2>&1) - if [[ $(echo "$BUCKET_LOCATION" | grep AccessDenied) ]]; then - textInfo "Trail $trail in $TRAIL_REGION Access Denied getting bucket location for $CLOUDTRAILBUCKET" - continue - fi - if [[ $BUCKET_LOCATION == "None" ]]; then - BUCKET_LOCATION="us-east-1" - fi - if [[ $BUCKET_LOCATION == "EU" ]]; then - BUCKET_LOCATION="eu-west-1" - fi - - CLOUDTRAILBUCKET_HASALLPERMISIONS=$($AWSCLI s3api get-bucket-acl --bucket $CLOUDTRAILBUCKET $PROFILE_OPT --region $BUCKET_LOCATION --query 'Grants[?Grantee.URI==`http://acs.amazonaws.com/groups/global/AllUsers`]' --output text 2>&1) - if [[ $(echo "$CLOUDTRAILBUCKET_HASALLPERMISIONS" | grep AccessDenied) ]]; then - textInfo "Trail $trail in $TRAIL_REGION Access Denied getting bucket acl for $CLOUDTRAILBUCKET" - continue - fi - - if [[ -z $CLOUDTRAILBUCKET_HASALLPERMISIONS ]]; then - textPass "Trail $trail in $TRAIL_REGION S3 logging bucket $CLOUDTRAILBUCKET is not publicly accessible" - else - textFail "Trail $trail in $TRAIL_REGION S3 logging bucket $CLOUDTRAILBUCKET is publicly accessible" - fi - - done - fi - done - if [[ $trail_count == 0 ]]; then - textFail "$REGION: No CloudTrail trails were found in the account" "$REGION" "$trail" - fi -} diff --git a/providers/aws/services/cloudtrail/check24 b/providers/aws/services/cloudtrail/check24 deleted file mode 100644 index 9a04d0ae..00000000 --- a/providers/aws/services/cloudtrail/check24 +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env bash - -# Prowler - the handy cloud security tool (copyright 2019) by Toni de la Fuente -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy -# of the License at http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed -# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -CHECK_ID_check24="2.4" -CHECK_TITLE_check24="[check24] Ensure CloudTrail trails are integrated with CloudWatch Logs" -CHECK_SCORED_check24="SCORED" -CHECK_CIS_LEVEL_check24="LEVEL1" -CHECK_SEVERITY_check24="Low" -CHECK_ASFF_TYPE_check24="Software and Configuration Checks/Industry and Regulatory Standards/CIS AWS Foundations Benchmark" -CHECK_ASFF_RESOURCE_TYPE_check24="AwsCloudTrailTrail" -CHECK_ALTERNATE_check204="check24" -CHECK_ASFF_COMPLIANCE_TYPE_check24="ens-op.exp.8.aws.cw.1" -CHECK_SERVICENAME_check24="cloudtrail" -CHECK_RISK_check24='Sending CloudTrail logs to CloudWatch Logs will facilitate real-time and historic activity logging based on user; API; resource; and IP address; and provides opportunity to establish alarms and notifications for anomalous or sensitivity account activity.' -CHECK_REMEDIATION_check24='Validate that the trails in CloudTrail has an arn set in the CloudWatchLogsLogGroupArn property.' -CHECK_DOC_check24='https://docs.aws.amazon.com/awscloudtrail/latest/userguide/send-cloudtrail-events-to-cloudwatch-logs.html' -CHECK_CAF_EPIC_check24='Logging and Monitoring' - -check24(){ - trail_count=0 - # "Ensure CloudTrail trails are integrated with CloudWatch Logs (Scored)" - for regx in $REGIONS; do - TRAILS_AND_REGIONS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $regx --query 'trailList[*].{Name:TrailARN, HomeRegion:HomeRegion}' --output text 2>&1 | tr " " ',') - if [[ $(echo "$TRAILS_AND_REGIONS" | grep AccessDenied) ]]; then - textInfo "$regx: Access Denied trying to describe trails" "$regx" "$trail" - continue - fi - if [[ $TRAILS_AND_REGIONS ]]; then - for reg_trail in $TRAILS_AND_REGIONS; do - TRAIL_REGION=$(echo $reg_trail | cut -d',' -f1) - if [ $TRAIL_REGION != $regx ]; then # Only report trails once in home region - continue - fi - trail=$(echo $reg_trail | cut -d',' -f2) - trail_count=$((trail_count + 1)) - - LATESTDELIVERY_TIMESTAMP=$($AWSCLI cloudtrail get-trail-status --name $trail $PROFILE_OPT --region $TRAIL_REGION --query 'LatestCloudWatchLogsDeliveryTime' --output text|grep -v None) - if [[ ! $LATESTDELIVERY_TIMESTAMP ]];then - textFail "$TRAIL_REGION: $trail trail is not logging in the last 24h or not configured (it is in $TRAIL_REGION)" "$TRAIL_REGION" "$trail" - else - LATESTDELIVERY_DATE=$(timestamp_to_date $LATESTDELIVERY_TIMESTAMP) - HOWOLDER=$(how_older_from_today $LATESTDELIVERY_DATE) - if [ $HOWOLDER -gt "1" ];then - textFail "$TRAIL_REGION: $trail trail is not logging in the last 24h or not configured" "$TRAIL_REGION" "$trail" - else - textPass "$TRAIL_REGION: $trail trail has been logging during the last 24h" "$TRAIL_REGION" "$trail" - fi - fi - - done - fi - done - if [[ $trail_count == 0 ]]; then - textFail "$REGION: No CloudTrail trails were found in the account" "$REGION" "$trail" - fi -} diff --git a/providers/aws/services/cloudtrail/check27 b/providers/aws/services/cloudtrail/check27 deleted file mode 100644 index 166b1a07..00000000 --- a/providers/aws/services/cloudtrail/check27 +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env bash - -# Prowler - the handy cloud security tool (copyright 2019) by Toni de la Fuente -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy -# of the License at http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed -# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -CHECK_ID_check27="2.7" -CHECK_TITLE_check27="[check27] Ensure CloudTrail logs are encrypted at rest using KMS CMKs" -CHECK_SCORED_check27="SCORED" -CHECK_CIS_LEVEL_check27="LEVEL2" -CHECK_SEVERITY_check27="Medium" -CHECK_ASFF_TYPE_check27="Software and Configuration Checks/Industry and Regulatory Standards/CIS AWS Foundations Benchmark" -CHECK_ASFF_RESOURCE_TYPE_check27="AwsCloudTrailTrail" -CHECK_ALTERNATE_check207="check27" -CHECK_ASFF_COMPLIANCE_TYPE_check27="ens-op.exp.10.aws.trail.5" -CHECK_SERVICENAME_check27="cloudtrail" -CHECK_RISK_check27='By default; the log files delivered by CloudTrail to your bucket are encrypted by Amazon server-side encryption with Amazon S3-managed encryption keys (SSE-S3). To provide a security layer that is directly manageable; you can instead use server-side encryption with AWS KMS–managed keys (SSE-KMS) for your CloudTrail log files.' -CHECK_REMEDIATION_check27='This approach has the following advantages: You can create and manage the CMK encryption keys yourself. You can use a single CMK to encrypt and decrypt log files for multiple accounts across all regions. You have control over who can use your key for encrypting and decrypting CloudTrail log files. You can assign permissions for the key to the users. You have enhanced security.' -CHECK_DOC_check27='https://docs.aws.amazon.com/awscloudtrail/latest/userguide/encrypting-cloudtrail-log-files-with-aws-kms.html' -CHECK_CAF_EPIC_check27='Logging and Monitoring' - -check27(){ - trail_count=0 - # "Ensure CloudTrail logs are encrypted at rest using KMS CMKs (Scored)" - for regx in $REGIONS; do - TRAILS_AND_REGIONS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $regx --query 'trailList[*].{Name:TrailARN, HomeRegion:HomeRegion}' --output text 2>&1 | tr " " ',') - if [[ $(echo "$TRAILS_AND_REGIONS" | grep AccessDenied) ]]; then - textInfo "$regx: Access Denied trying to describe trails" "$regx" "$trail" - continue - fi - if [[ $TRAILS_AND_REGIONS ]]; then - for reg_trail in $TRAILS_AND_REGIONS; do - TRAIL_REGION=$(echo $reg_trail | cut -d',' -f1) - if [ $TRAIL_REGION != $regx ]; then # Only report trails once in home region - continue - fi - trail=$(echo $reg_trail | cut -d',' -f2) - trail_count=$((trail_count + 1)) - - KMSKEYID=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $TRAIL_REGION --query 'trailList[*].KmsKeyId' --output text --trail-name-list $trail) - if [[ "$KMSKEYID" ]];then - textPass "$regx: Trail $trail has encryption enabled" "$regx" "$trail" - else - textFail "$regx: Trail $trail has encryption disabled" "$regx" "$trail" - fi - done - fi - done - if [[ $trail_count == 0 ]]; then - textFail "$REGION: No CloudTrail trails were found in the account" "$REGION" "$trail" - fi -} diff --git a/providers/aws/services/cloudtrail/cloudtrail_client.py b/providers/aws/services/cloudtrail/cloudtrail_client.py new file mode 100644 index 00000000..c6695748 --- /dev/null +++ b/providers/aws/services/cloudtrail/cloudtrail_client.py @@ -0,0 +1,4 @@ +from providers.aws.lib.audit_info.audit_info import current_audit_info +from providers.aws.services.cloudtrail.cloudtrail_service import Cloudtrail + +cloudtrail_client = Cloudtrail(current_audit_info) diff --git a/providers/aws/services/cloudtrail/cloudtrail_cloudwatch_logging_enabled/__init__.py b/providers/aws/services/cloudtrail/cloudtrail_cloudwatch_logging_enabled/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/cloudtrail/cloudtrail_cloudwatch_logging_enabled/cloudtrail_cloudwatch_logging_enabled.metadata.json b/providers/aws/services/cloudtrail/cloudtrail_cloudwatch_logging_enabled/cloudtrail_cloudwatch_logging_enabled.metadata.json new file mode 100644 index 00000000..85d14f34 --- /dev/null +++ b/providers/aws/services/cloudtrail/cloudtrail_cloudwatch_logging_enabled/cloudtrail_cloudwatch_logging_enabled.metadata.json @@ -0,0 +1,35 @@ +{ + "Provider": "aws", + "CheckID": "cloudtrail_cloudwatch_logging_enabled", + "CheckTitle": "Ensure CloudTrail trails are integrated with CloudWatch Logs", + "CheckType": ["Software and Configuration Checks", "Industry and Regulatory Standards" ,"CIS AWS Foundations Benchmark"], + "ServiceName": "cloudtrail", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "low", + "ResourceType": "AwsCloudTrailTrail", + "Description": "Ensure CloudTrail trails are integrated with CloudWatch Logs", + "Risk": "Sending CloudTrail logs to CloudWatch Logs will facilitate real-time and historic activity logging based on user; API; resource; and IP address; and provides opportunity to establish alarms and notifications for anomalous or sensitivity account activity.", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "aws cloudtrail update-trail --name --cloudwatch-logs-log-group- arn --cloudwatch-logs-role-arn ", + "NativeIaC": "", + "Other": "https://docs.bridgecrew.io/docs/logging_4#aws-console", + "Terraform": "" + }, + "Recommendation": { + "Text": "Validate that the trails in CloudTrail has an arn set in the CloudWatchLogsLogGroupArn property.", + "Url": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/send-cloudtrail-events-to-cloudwatch-logs.html" + } + }, + "Categories": [], + "Tags": { + "Tag1Key": "value", + "Tag2Key": "value" + }, + "DependsOn": [], + "RelatedTo": [], + "Notes": "", + "Compliance": [] +} diff --git a/providers/aws/services/cloudtrail/cloudtrail_cloudwatch_logging_enabled/cloudtrail_cloudwatch_logging_enabled.py b/providers/aws/services/cloudtrail/cloudtrail_cloudwatch_logging_enabled/cloudtrail_cloudwatch_logging_enabled.py new file mode 100644 index 00000000..3a821889 --- /dev/null +++ b/providers/aws/services/cloudtrail/cloudtrail_cloudwatch_logging_enabled/cloudtrail_cloudwatch_logging_enabled.py @@ -0,0 +1,44 @@ +from datetime import datetime, timedelta, timezone + +from lib.check.models import Check, Check_Report +from providers.aws.services.cloudtrail.cloudtrail_client import cloudtrail_client + +maximum_time_without_logging = 1 + + +class cloudtrail_cloudwatch_logging_enabled(Check): + def execute(self): + findings = [] + for trail in cloudtrail_client.trails: + if trail.name: + report = Check_Report(self.metadata) + report.region = trail.region + report.resource_id = trail.name + report.resource_arn = trail.trail_arn + report.status = "PASS" + if trail.is_multiregion: + report.status_extended = ( + f"Multiregion trail {trail.name} has been logging the last 24h" + ) + else: + report.status_extended = f"Single region trail {trail.name} has been logging the last 24h" + if trail.latest_cloudwatch_delivery_time: + last_log_delivery = ( + datetime.now().replace(tzinfo=timezone.utc) + - trail.latest_cloudwatch_delivery_time + ) + if last_log_delivery > timedelta(days=maximum_time_without_logging): + report.status = "FAIL" + if trail.is_multiregion: + report.status_extended = f"Multiregion trail {trail.name} is not logging in the last 24h" + else: + report.status_extended = f"Single region trail {trail.name} is not logging in the last 24h" + else: + report.status = "FAIL" + if trail.is_multiregion: + report.status_extended = f"Multiregion trail {trail.name} is not configured to deliver logs" + else: + report.status_extended = f"Single region trail {trail.name} is not configured to deliver logs" + findings.append(report) + + return findings diff --git a/providers/aws/services/cloudtrail/cloudtrail_cloudwatch_logging_enabled/cloudtrail_cloudwatch_logging_enabled_test.py b/providers/aws/services/cloudtrail/cloudtrail_cloudwatch_logging_enabled/cloudtrail_cloudwatch_logging_enabled_test.py new file mode 100644 index 00000000..c9ce9080 --- /dev/null +++ b/providers/aws/services/cloudtrail/cloudtrail_cloudwatch_logging_enabled/cloudtrail_cloudwatch_logging_enabled_test.py @@ -0,0 +1,225 @@ +from datetime import datetime, timedelta, timezone +from re import search +from unittest import mock + +from boto3 import client +from moto import mock_cloudtrail, mock_s3 + + +class Test_cloudtrail_cloudwatch_logging_enabled: + @mock_cloudtrail + @mock_s3 + def test_trails_sending_logs_during_and_not_last_day(self): + cloudtrail_client_us_east_1 = client("cloudtrail", region_name="us-east-1") + s3_client_us_east_1 = client("s3", region_name="us-east-1") + cloudtrail_client_eu_west_1 = client("cloudtrail", region_name="eu-west-1") + s3_client_eu_west_1 = client("s3", region_name="eu-west-1") + trail_name_us = "trail_test_us" + bucket_name_us = "bucket_test_us" + trail_name_eu = "trail_test_eu" + bucket_name_eu = "bucket_test_eu" + s3_client_us_east_1.create_bucket(Bucket=bucket_name_us) + s3_client_eu_west_1.create_bucket( + Bucket=bucket_name_eu, + CreateBucketConfiguration={"LocationConstraint": "eu-west-1"}, + ) + trail_us = cloudtrail_client_us_east_1.create_trail( + Name=trail_name_us, S3BucketName=bucket_name_us, IsMultiRegionTrail=False + ) + trail_eu = cloudtrail_client_eu_west_1.create_trail( + Name=trail_name_eu, S3BucketName=bucket_name_eu, IsMultiRegionTrail=False + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.cloudtrail.cloudtrail_service import Cloudtrail + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.cloudtrail.cloudtrail_cloudwatch_logging_enabled.cloudtrail_cloudwatch_logging_enabled.cloudtrail_client", + new=Cloudtrail(current_audit_info), + ) as service_client: + # Test Check + from providers.aws.services.cloudtrail.cloudtrail_cloudwatch_logging_enabled.cloudtrail_cloudwatch_logging_enabled import ( + cloudtrail_cloudwatch_logging_enabled, + ) + + for trail in service_client.trails: + if trail.name == trail_name_us: + trail.latest_cloudwatch_delivery_time = datetime.now().replace( + tzinfo=timezone.utc + ) + elif trail.name == trail_name_eu: + trail.latest_cloudwatch_delivery_time = ( + datetime.now() - timedelta(days=2) + ).replace(tzinfo=timezone.utc) + + regions = [] + for region in service_client.regional_clients.keys(): + regions.append(region) + + check = cloudtrail_cloudwatch_logging_enabled() + result = check.execute() + # len of result if has to be 2 since we only have 2 single region trails + assert len(result) == 2 + for report in result: + if report.resource_id == trail_name_us: + assert report.resource_id == trail_name_us + assert report.resource_arn == trail_us["TrailARN"] + assert report.status == "PASS" + assert search( + report.status_extended, + f"Single region trail {trail_name_us} has been logging the last 24h", + ) + if report.resource_id == trail_name_eu: + assert report.resource_id == trail_name_eu + assert report.resource_arn == trail_eu["TrailARN"] + assert report.status == "FAIL" + assert search( + report.status_extended, + f"Single region trail {trail_name_eu} is not logging in the last 24h", + ) + + @mock_cloudtrail + @mock_s3 + def test_multi_region_and_single_region_logging_and_not(self): + cloudtrail_client_us_east_1 = client("cloudtrail", region_name="us-east-1") + s3_client_us_east_1 = client("s3", region_name="us-east-1") + cloudtrail_client_eu_west_1 = client("cloudtrail", region_name="eu-west-1") + s3_client_eu_west_1 = client("s3", region_name="eu-west-1") + trail_name_us = "trail_test_us" + bucket_name_us = "bucket_test_us" + trail_name_eu = "trail_test_eu" + bucket_name_eu = "bucket_test_eu" + s3_client_us_east_1.create_bucket(Bucket=bucket_name_us) + s3_client_eu_west_1.create_bucket( + Bucket=bucket_name_eu, + CreateBucketConfiguration={"LocationConstraint": "eu-west-1"}, + ) + trail_us = cloudtrail_client_us_east_1.create_trail( + Name=trail_name_us, S3BucketName=bucket_name_us, IsMultiRegionTrail=True + ) + trail_eu = cloudtrail_client_eu_west_1.create_trail( + Name=trail_name_eu, S3BucketName=bucket_name_eu, IsMultiRegionTrail=False + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.cloudtrail.cloudtrail_service import Cloudtrail + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.cloudtrail.cloudtrail_cloudwatch_logging_enabled.cloudtrail_cloudwatch_logging_enabled.cloudtrail_client", + new=Cloudtrail(current_audit_info), + ) as service_client: + # Test Check + from providers.aws.services.cloudtrail.cloudtrail_cloudwatch_logging_enabled.cloudtrail_cloudwatch_logging_enabled import ( + cloudtrail_cloudwatch_logging_enabled, + ) + + for trail in service_client.trails: + if trail.name == trail_name_us: + trail.latest_cloudwatch_delivery_time = datetime.now().replace( + tzinfo=timezone.utc + ) + elif trail.name == trail_name_eu: + trail.latest_cloudwatch_delivery_time = ( + datetime.now() - timedelta(days=2) + ).replace(tzinfo=timezone.utc) + + regions = [] + for region in service_client.regional_clients.keys(): + regions.append(region) + + check = cloudtrail_cloudwatch_logging_enabled() + result = check.execute() + # len of result should be 24 -> (1 multiregion entry per region + 1 entry because of single region trail) + assert len(result) == 24 + for report in result: + if report.resource_id == trail_name_us: + assert report.resource_id == trail_name_us + assert report.resource_arn == trail_us["TrailARN"] + assert report.status == "PASS" + assert search( + report.status_extended, + f"Multiregion trail {trail_name_us} has been logging the last 24h", + ) + if report.resource_id == trail_name_eu and report.region == "eu-west-1": + assert report.resource_id == trail_name_eu + assert report.resource_arn == trail_eu["TrailARN"] + assert report.status == "FAIL" + assert search( + report.status_extended, + f"Single region trail {trail_name_eu} is not logging in the last 24h", + ) + + @mock_cloudtrail + @mock_s3 + def test_trails_sending_and_not_sending_logs(self): + cloudtrail_client_us_east_1 = client("cloudtrail", region_name="us-east-1") + s3_client_us_east_1 = client("s3", region_name="us-east-1") + cloudtrail_client_eu_west_1 = client("cloudtrail", region_name="eu-west-1") + s3_client_eu_west_1 = client("s3", region_name="eu-west-1") + trail_name_us = "trail_test_us" + bucket_name_us = "bucket_test_us" + trail_name_eu = "trail_test_eu" + bucket_name_eu = "bucket_test_eu" + s3_client_us_east_1.create_bucket(Bucket=bucket_name_us) + s3_client_eu_west_1.create_bucket( + Bucket=bucket_name_eu, + CreateBucketConfiguration={"LocationConstraint": "eu-west-1"}, + ) + trail_us = cloudtrail_client_us_east_1.create_trail( + Name=trail_name_us, S3BucketName=bucket_name_us, IsMultiRegionTrail=False + ) + trail_eu = cloudtrail_client_eu_west_1.create_trail( + Name=trail_name_eu, S3BucketName=bucket_name_eu, IsMultiRegionTrail=False + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.cloudtrail.cloudtrail_service import Cloudtrail + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.cloudtrail.cloudtrail_cloudwatch_logging_enabled.cloudtrail_cloudwatch_logging_enabled.cloudtrail_client", + new=Cloudtrail(current_audit_info), + ) as service_client: + # Test Check + from providers.aws.services.cloudtrail.cloudtrail_cloudwatch_logging_enabled.cloudtrail_cloudwatch_logging_enabled import ( + cloudtrail_cloudwatch_logging_enabled, + ) + + for trail in service_client.trails: + if trail.name == trail_name_us: + trail.latest_cloudwatch_delivery_time = datetime.now().replace( + tzinfo=timezone.utc + ) + elif trail.name == trail_name_us: + trail.latest_cloudwatch_delivery_time = None + + regions = [] + for region in service_client.regional_clients.keys(): + regions.append(region) + + check = cloudtrail_cloudwatch_logging_enabled() + result = check.execute() + # len of result if has to be 2 since we only have 2 single region trails + assert len(result) == 2 + for report in result: + if report.resource_id == trail_name_us: + assert report.resource_id == trail_name_us + assert report.resource_arn == trail_us["TrailARN"] + assert report.status == "PASS" + assert search( + report.status_extended, + f"Single region trail {trail_name_us} has been logging the last 24h", + ) + if report.resource_id == trail_name_eu: + assert report.resource_id == trail_name_eu + assert report.resource_arn == trail_eu["TrailARN"] + assert report.status == "FAIL" + assert search( + report.status_extended, + f"Single region trail {trail_name_eu} is not configured to deliver logs", + ) diff --git a/providers/aws/services/cloudtrail/cloudtrail_kms_encryption_enabled/__init__.py b/providers/aws/services/cloudtrail/cloudtrail_kms_encryption_enabled/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/cloudtrail/cloudtrail_kms_encryption_enabled/cloudtrail_kms_encryption_enabled.metadata.json b/providers/aws/services/cloudtrail/cloudtrail_kms_encryption_enabled/cloudtrail_kms_encryption_enabled.metadata.json new file mode 100644 index 00000000..a947ff99 --- /dev/null +++ b/providers/aws/services/cloudtrail/cloudtrail_kms_encryption_enabled/cloudtrail_kms_encryption_enabled.metadata.json @@ -0,0 +1,35 @@ +{ + "Provider": "aws", + "CheckID": "cloudtrail_kms_encryption_enabled", + "CheckTitle": "Ensure CloudTrail logs are encrypted at rest using KMS CMKs", + "CheckType": ["Software and Configuration Checks", "Industry and Regulatory Standards" ,"CIS AWS Foundations Benchmark"], + "ServiceName": "cloudtrail", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AwsCloudTrailTrail", + "Description": "Ensure CloudTrail logs are encrypted at rest using KMS CMKs", + "Risk": "By default; the log files delivered by CloudTrail to your bucket are encrypted by Amazon server-side encryption with Amazon S3-managed encryption keys (SSE-S3). To provide a security layer that is directly manageable; you can instead use server-side encryption with AWS KMS–managed keys (SSE-KMS) for your CloudTrail log files.", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "aws cloudtrail update-trail --name --kms-id aws kms put-key-policy --key-id --policy ", + "NativeIaC": "https://docs.bridgecrew.io/docs/logging_7#fix---buildtime", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "This approach has the following advantages: You can create and manage the CMK encryption keys yourself. You can use a single CMK to encrypt and decrypt log files for multiple accounts across all regions. You have control over who can use your key for encrypting and decrypting CloudTrail log files. You can assign permissions for the key to the users. You have enhanced security.", + "Url": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/encrypting-cloudtrail-log-files-with-aws-kms.html" + } + }, + "Categories": [], + "Tags": { + "Tag1Key": "value", + "Tag2Key": "value" + }, + "DependsOn": [], + "RelatedTo": [], + "Notes": "", + "Compliance": [] + } diff --git a/providers/aws/services/cloudtrail/cloudtrail_kms_encryption_enabled/cloudtrail_kms_encryption_enabled.py b/providers/aws/services/cloudtrail/cloudtrail_kms_encryption_enabled/cloudtrail_kms_encryption_enabled.py new file mode 100644 index 00000000..459d5971 --- /dev/null +++ b/providers/aws/services/cloudtrail/cloudtrail_kms_encryption_enabled/cloudtrail_kms_encryption_enabled.py @@ -0,0 +1,35 @@ +from lib.check.models import Check, Check_Report +from providers.aws.services.cloudtrail.cloudtrail_client import cloudtrail_client + + +class cloudtrail_kms_encryption_enabled(Check): + def execute(self): + findings = [] + for trail in cloudtrail_client.trails: + if trail.name: + report = Check_Report(self.metadata) + report.region = trail.region + report.resource_id = trail.name + report.resource_arn = trail.trail_arn + report.status = "FAIL" + if trail.is_multiregion: + report.status_extended = ( + f"Multiregion trail {trail.name} has encryption disabled" + ) + else: + report.status_extended = ( + f"Single region trail {trail.name} has encryption disabled" + ) + if trail.kms_key: + report.status = "PASS" + if trail.is_multiregion: + report.status_extended = ( + f"Multiregion trail {trail.name} has encryption enabled" + ) + else: + report.status_extended = ( + f"Single region trail {trail.name} has encryption enabled" + ) + findings.append(report) + + return findings diff --git a/providers/aws/services/cloudtrail/cloudtrail_kms_encryption_enabled/cloudtrail_kms_encryption_enabled_test.py b/providers/aws/services/cloudtrail/cloudtrail_kms_encryption_enabled/cloudtrail_kms_encryption_enabled_test.py new file mode 100644 index 00000000..e2f45ce2 --- /dev/null +++ b/providers/aws/services/cloudtrail/cloudtrail_kms_encryption_enabled/cloudtrail_kms_encryption_enabled_test.py @@ -0,0 +1,89 @@ +from re import search +from unittest import mock + +from boto3 import client +from moto import mock_cloudtrail, mock_kms, mock_s3 + + +class Test_cloudtrail_kms_encryption_enabled: + @mock_cloudtrail + @mock_s3 + def test_trail_no_kms(self): + cloudtrail_client_us_east_1 = client("cloudtrail", region_name="us-east-1") + s3_client_us_east_1 = client("s3", region_name="us-east-1") + trail_name_us = "trail_test_us" + bucket_name_us = "bucket_test_us" + s3_client_us_east_1.create_bucket(Bucket=bucket_name_us) + trail_us = cloudtrail_client_us_east_1.create_trail( + Name=trail_name_us, S3BucketName=bucket_name_us, IsMultiRegionTrail=False + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.cloudtrail.cloudtrail_service import Cloudtrail + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.cloudtrail.cloudtrail_kms_encryption_enabled.cloudtrail_kms_encryption_enabled.cloudtrail_client", + new=Cloudtrail(current_audit_info), + ): + # Test Check + from providers.aws.services.cloudtrail.cloudtrail_kms_encryption_enabled.cloudtrail_kms_encryption_enabled import ( + cloudtrail_kms_encryption_enabled, + ) + + check = cloudtrail_kms_encryption_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert search( + "has encryption disabled", + result[0].status_extended, + ) + assert result[0].resource_id == trail_name_us + assert result[0].resource_arn == trail_us["TrailARN"] + + @mock_cloudtrail + @mock_s3 + @mock_kms + def test_trail_kms(self): + cloudtrail_client_us_east_1 = client("cloudtrail", region_name="us-east-1") + s3_client_us_east_1 = client("s3", region_name="us-east-1") + kms_client = client("kms", region_name="us-east-1") + trail_name_us = "trail_test_us" + bucket_name_us = "bucket_test_us" + s3_client_us_east_1.create_bucket(Bucket=bucket_name_us) + key_arn = kms_client.create_key()["KeyMetadata"]["Arn"] + trail_us = cloudtrail_client_us_east_1.create_trail( + Name=trail_name_us, + S3BucketName=bucket_name_us, + IsMultiRegionTrail=False, + KmsKeyId=key_arn, + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.cloudtrail.cloudtrail_service import Cloudtrail + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.cloudtrail.cloudtrail_kms_encryption_enabled.cloudtrail_kms_encryption_enabled.cloudtrail_client", + new=Cloudtrail(current_audit_info), + ): + # Test Check + from providers.aws.services.cloudtrail.cloudtrail_kms_encryption_enabled.cloudtrail_kms_encryption_enabled import ( + cloudtrail_kms_encryption_enabled, + ) + + check = cloudtrail_kms_encryption_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert search( + "has encryption enabled", + result[0].status_extended, + ) + assert result[0].resource_id == trail_name_us + assert result[0].resource_arn == trail_us["TrailARN"] diff --git a/providers/aws/services/cloudtrail/cloudtrail_log_file_validation_enabled/__init__.py b/providers/aws/services/cloudtrail/cloudtrail_log_file_validation_enabled/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/cloudtrail/cloudtrail_log_file_validation_enabled/cloudtrail_log_file_validation_enabled.metadata.json b/providers/aws/services/cloudtrail/cloudtrail_log_file_validation_enabled/cloudtrail_log_file_validation_enabled.metadata.json new file mode 100644 index 00000000..a67174b6 --- /dev/null +++ b/providers/aws/services/cloudtrail/cloudtrail_log_file_validation_enabled/cloudtrail_log_file_validation_enabled.metadata.json @@ -0,0 +1,35 @@ +{ + "Provider": "aws", + "CheckID": "cloudtrail_log_file_validation_enabled", + "CheckTitle": "Ensure CloudTrail log file validation is enabled", + "CheckType": ["Software and Configuration Checks", "Industry and Regulatory Standards" ,"CIS AWS Foundations Benchmark"], + "ServiceName": "cloudtrail", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AwsCloudTrailTrail", + "Description": "Ensure CloudTrail log file validation is enabled", + "Risk": "Enabling log file validation will provide additional integrity checking of CloudTrail logs. ", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "aws cloudtrail update-trail --name --enable-log-file-validation", + "NativeIaC": "https://docs.bridgecrew.io/docs/logging_2#cloudformation", + "Other": "", + "Terraform": "https://docs.bridgecrew.io/docs/logging_2#terraform" + }, + "Recommendation": { + "Text": "Ensure LogFileValidationEnabled is set to true for each trail.", + "Url": "http://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-log-filevalidation-enabling.html" + } + }, + "Categories": [], + "Tags": { + "Tag1Key": "value", + "Tag2Key": "value" + }, + "DependsOn": [], + "RelatedTo": [], + "Notes": "", + "Compliance": [] + } diff --git a/providers/aws/services/cloudtrail/cloudtrail_log_file_validation_enabled/cloudtrail_log_file_validation_enabled.py b/providers/aws/services/cloudtrail/cloudtrail_log_file_validation_enabled/cloudtrail_log_file_validation_enabled.py new file mode 100644 index 00000000..f9962856 --- /dev/null +++ b/providers/aws/services/cloudtrail/cloudtrail_log_file_validation_enabled/cloudtrail_log_file_validation_enabled.py @@ -0,0 +1,31 @@ +from lib.check.models import Check, Check_Report +from providers.aws.services.cloudtrail.cloudtrail_client import cloudtrail_client + + +class cloudtrail_log_file_validation_enabled(Check): + def execute(self): + findings = [] + for trail in cloudtrail_client.trails: + if trail.name: + report = Check_Report(self.metadata) + report.region = trail.region + report.resource_id = trail.name + report.resource_arn = trail.trail_arn + report.status = "FAIL" + if trail.is_multiregion: + report.status_extended = ( + f"Multiregion trail {trail.name} log file validation disabled" + ) + else: + report.status_extended = ( + f"Single region trail {trail.name} log file validation disabled" + ) + if trail.log_file_validation_enabled: + report.status = "PASS" + if trail.is_multiregion: + report.status_extended = f"Multiregion trail {trail.name} log file validation enabled" + else: + report.status_extended = f"Single region trail {trail.name} log file validation enabled" + findings.append(report) + + return findings diff --git a/providers/aws/services/cloudtrail/cloudtrail_log_file_validation_enabled/cloudtrail_log_file_validation_enabled_test.py b/providers/aws/services/cloudtrail/cloudtrail_log_file_validation_enabled/cloudtrail_log_file_validation_enabled_test.py new file mode 100644 index 00000000..2919ae80 --- /dev/null +++ b/providers/aws/services/cloudtrail/cloudtrail_log_file_validation_enabled/cloudtrail_log_file_validation_enabled_test.py @@ -0,0 +1,102 @@ +from re import search +from unittest import mock + +from boto3 import client +from moto import mock_cloudtrail, mock_s3 + + +class Test_cloudtrail_log_file_validation_enabled: + @mock_cloudtrail + @mock_s3 + def test_no_logging_validation(self): + cloudtrail_client_us_east_1 = client("cloudtrail", region_name="us-east-1") + s3_client_us_east_1 = client("s3", region_name="us-east-1") + trail_name_us = "trail_test_us" + bucket_name_us = "bucket_test_us" + s3_client_us_east_1.create_bucket(Bucket=bucket_name_us) + trail_us = cloudtrail_client_us_east_1.create_trail( + Name=trail_name_us, S3BucketName=bucket_name_us, IsMultiRegionTrail=False + ) + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.cloudtrail.cloudtrail_service import Cloudtrail + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.cloudtrail.cloudtrail_log_file_validation_enabled.cloudtrail_log_file_validation_enabled.cloudtrail_client", + new=Cloudtrail(current_audit_info), + ): + # Test Check + from providers.aws.services.cloudtrail.cloudtrail_log_file_validation_enabled.cloudtrail_log_file_validation_enabled import ( + cloudtrail_log_file_validation_enabled, + ) + + check = cloudtrail_log_file_validation_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert search("log file validation disabled", result[0].status_extended) + assert result[0].resource_id == trail_name_us + assert result[0].resource_arn == trail_us["TrailARN"] + + @mock_cloudtrail + @mock_s3 + def test_various_trails_with_and_without_logging_validation(self): + cloudtrail_client_us_east_1 = client("cloudtrail", region_name="us-east-1") + s3_client_us_east_1 = client("s3", region_name="us-east-1") + cloudtrail_client_eu_west_1 = client("cloudtrail", region_name="eu-west-1") + s3_client_eu_west_1 = client("s3", region_name="eu-west-1") + trail_name_us = "trail_test_us" + bucket_name_us = "bucket_test_us" + trail_name_eu = "trail_test_eu" + bucket_name_eu = "bucket_test_eu" + s3_client_us_east_1.create_bucket(Bucket=bucket_name_us) + s3_client_eu_west_1.create_bucket( + Bucket=bucket_name_eu, + CreateBucketConfiguration={"LocationConstraint": "eu-west-1"}, + ) + trail_us = cloudtrail_client_us_east_1.create_trail( + Name=trail_name_us, + S3BucketName=bucket_name_us, + IsMultiRegionTrail=False, + EnableLogFileValidation=True, + ) + trail_eu = cloudtrail_client_eu_west_1.create_trail( + Name=trail_name_eu, S3BucketName=bucket_name_eu, IsMultiRegionTrail=False + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.cloudtrail.cloudtrail_service import Cloudtrail + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.cloudtrail.cloudtrail_log_file_validation_enabled.cloudtrail_log_file_validation_enabled.cloudtrail_client", + new=Cloudtrail(current_audit_info), + ) as service_client: + # Test Check + from providers.aws.services.cloudtrail.cloudtrail_log_file_validation_enabled.cloudtrail_log_file_validation_enabled import ( + cloudtrail_log_file_validation_enabled, + ) + + regions = [] + for region in service_client.regional_clients.keys(): + regions.append(region) + + check = cloudtrail_log_file_validation_enabled() + result = check.execute() + assert len(result) == 2 + for report in result: + if report.resource_id == trail_name_us: + assert report.status == "PASS" + assert search("log file validation enabled", report.status_extended) + assert report.resource_id == trail_name_us + assert report.resource_arn == trail_us["TrailARN"] + elif report.resource_id == trail_name_eu: + assert report.status == "FAIL" + assert search( + "log file validation disabled", report.status_extended + ) + assert report.resource_id == trail_name_eu + assert report.resource_arn == trail_eu["TrailARN"] diff --git a/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_access_logging_enabled/__init__.py b/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_access_logging_enabled/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_access_logging_enabled/cloudtrail_logs_s3_bucket_access_logging_enabled.metadata.json b/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_access_logging_enabled/cloudtrail_logs_s3_bucket_access_logging_enabled.metadata.json new file mode 100644 index 00000000..75f1ab45 --- /dev/null +++ b/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_access_logging_enabled/cloudtrail_logs_s3_bucket_access_logging_enabled.metadata.json @@ -0,0 +1,35 @@ +{ + "Provider": "aws", + "CheckID": "cloudtrail_logs_s3_bucket_access_logging_enabled", + "CheckTitle": "Ensure S3 bucket access logging is enabled on the CloudTrail S3 bucket", + "CheckType": ["Software and Configuration Checks", "Industry and Regulatory Standards" ,"CIS AWS Foundations Benchmark"], + "ServiceName": "cloudtrail", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AwsCloudTrailTrail", + "Description": "Ensure S3 bucket access logging is enabled on the CloudTrail S3 bucket", + "Risk": "Server access logs can assist you in security and access audits; help you learn about your customer base; and understand your Amazon S3 bill.", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "https://docs.bridgecrew.io/docs/logging_6#aws-console", + "Terraform": "" + }, + "Recommendation": { + "Text": "Ensure that S3 buckets have Logging enabled. CloudTrail data events can be used in place of S3 bucket logging. If that is the case; this finding can be considered a false positive.", + "Url": "https://docs.aws.amazon.com/AmazonS3/latest/dev/security-best-practices.html" + } + }, + "Categories": [], + "Tags": { + "Tag1Key": "value", + "Tag2Key": "value" + }, + "DependsOn": [], + "RelatedTo": [], + "Notes": "", + "Compliance": [] + } diff --git a/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_access_logging_enabled/cloudtrail_logs_s3_bucket_access_logging_enabled.py b/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_access_logging_enabled/cloudtrail_logs_s3_bucket_access_logging_enabled.py new file mode 100644 index 00000000..25badf30 --- /dev/null +++ b/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_access_logging_enabled/cloudtrail_logs_s3_bucket_access_logging_enabled.py @@ -0,0 +1,31 @@ +from lib.check.models import Check, Check_Report +from providers.aws.services.cloudtrail.cloudtrail_client import cloudtrail_client +from providers.aws.services.s3.s3_client import s3_client + + +class cloudtrail_logs_s3_bucket_access_logging_enabled(Check): + def execute(self): + findings = [] + for trail in cloudtrail_client.trails: + if trail.name: + trail_bucket = trail.s3_bucket + report = Check_Report(self.metadata) + report.region = trail.region + report.resource_id = trail.name + report.resource_arn = trail.trail_arn + report.status = "FAIL" + if trail.is_multiregion: + report.status_extended = f"Multiregion Trail {trail.name} S3 bucket access logging is not enabled for bucket {trail_bucket}" + else: + report.status_extended = f"Single region Trail {trail.name} S3 bucket access logging is not enabled for bucket {trail_bucket}" + for bucket in s3_client.buckets: + if trail_bucket == bucket.name and bucket.logging: + report.status = "PASS" + if trail.is_multiregion: + report.status_extended = f"Multiregion trail {trail.name} S3 bucket access logging is enabled for bucket {trail_bucket}" + else: + report.status_extended = f"Single region trail {trail.name} S3 bucket access logging is enabled for bucket {trail_bucket}" + + findings.append(report) + + return findings diff --git a/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_access_logging_enabled/cloudtrail_logs_s3_bucket_access_logging_enabled_test.py b/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_access_logging_enabled/cloudtrail_logs_s3_bucket_access_logging_enabled_test.py new file mode 100644 index 00000000..99caae69 --- /dev/null +++ b/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_access_logging_enabled/cloudtrail_logs_s3_bucket_access_logging_enabled_test.py @@ -0,0 +1,113 @@ +from re import search +from unittest import mock + +from boto3 import client +from moto import mock_cloudtrail, mock_s3 + + +class Test_cloudtrail_logs_s3_bucket_access_logging_enabled: + @mock_cloudtrail + @mock_s3 + def test_bucket_not_logging(self): + cloudtrail_client_us_east_1 = client("cloudtrail", region_name="us-east-1") + s3_client_us_east_1 = client("s3", region_name="us-east-1") + trail_name_us = "trail_test_us" + bucket_name_us = "bucket_test_us" + s3_client_us_east_1.create_bucket(Bucket=bucket_name_us) + trail_us = cloudtrail_client_us_east_1.create_trail( + Name=trail_name_us, S3BucketName=bucket_name_us, IsMultiRegionTrail=False + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.cloudtrail.cloudtrail_service import Cloudtrail + from providers.aws.services.s3.s3_service import S3 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.cloudtrail.cloudtrail_logs_s3_bucket_access_logging_enabled.cloudtrail_logs_s3_bucket_access_logging_enabled.cloudtrail_client", + new=Cloudtrail(current_audit_info), + ): + with mock.patch( + "providers.aws.services.cloudtrail.cloudtrail_logs_s3_bucket_access_logging_enabled.cloudtrail_logs_s3_bucket_access_logging_enabled.s3_client", + new=S3(current_audit_info), + ): + # Test Check + from providers.aws.services.cloudtrail.cloudtrail_logs_s3_bucket_access_logging_enabled.cloudtrail_logs_s3_bucket_access_logging_enabled import ( + cloudtrail_logs_s3_bucket_access_logging_enabled, + ) + + check = cloudtrail_logs_s3_bucket_access_logging_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert search( + "S3 bucket access logging is not enabled for bucket", + result[0].status_extended, + ) + assert result[0].resource_id == trail_name_us + assert result[0].resource_arn == trail_us["TrailARN"] + + @mock_cloudtrail + @mock_s3 + def test_bucket_logging(self): + cloudtrail_client_us_east_1 = client("cloudtrail", region_name="us-east-1") + s3_client_us_east_1 = client("s3", region_name="us-east-1") + trail_name_us = "trail_test_us" + bucket_name_us = "bucket_test_us" + logging_bucket = "logging" + s3_client_us_east_1.create_bucket( + Bucket=bucket_name_us, + ) + s3_client_us_east_1.create_bucket( + Bucket=logging_bucket, + ) + trail_us = cloudtrail_client_us_east_1.create_trail( + Name=trail_name_us, S3BucketName=bucket_name_us, IsMultiRegionTrail=False + ) + s3_client_us_east_1.put_bucket_acl( + Bucket=logging_bucket, + GrantWrite="uri=http://acs.amazonaws.com/groups/s3/LogDelivery", + GrantReadACP="uri=http://acs.amazonaws.com/groups/s3/LogDelivery", + ) + s3_client_us_east_1.put_bucket_logging( + Bucket=bucket_name_us, + BucketLoggingStatus={ + "LoggingEnabled": { + "TargetBucket": logging_bucket, + "TargetPrefix": logging_bucket, + } + }, + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.cloudtrail.cloudtrail_service import Cloudtrail + from providers.aws.services.s3.s3_service import S3 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.cloudtrail.cloudtrail_logs_s3_bucket_access_logging_enabled.cloudtrail_logs_s3_bucket_access_logging_enabled.cloudtrail_client", + new=Cloudtrail(current_audit_info), + ): + with mock.patch( + "providers.aws.services.cloudtrail.cloudtrail_logs_s3_bucket_access_logging_enabled.cloudtrail_logs_s3_bucket_access_logging_enabled.s3_client", + new=S3(current_audit_info), + ): + # Test Check + from providers.aws.services.cloudtrail.cloudtrail_logs_s3_bucket_access_logging_enabled.cloudtrail_logs_s3_bucket_access_logging_enabled import ( + cloudtrail_logs_s3_bucket_access_logging_enabled, + ) + + check = cloudtrail_logs_s3_bucket_access_logging_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert search( + "S3 bucket access logging is enabled for bucket", + result[0].status_extended, + ) + assert result[0].resource_id == trail_name_us + assert result[0].resource_arn == trail_us["TrailARN"] diff --git a/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/__init__.py b/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible.metadata.json b/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible.metadata.json new file mode 100644 index 00000000..0391ddfd --- /dev/null +++ b/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible.metadata.json @@ -0,0 +1,35 @@ +{ + "Provider": "aws", + "CheckID": "cloudtrail_logs_s3_bucket_is_not_publicly_accessible", + "CheckTitle": "Ensure the S3 bucket CloudTrail logs is not publicly accessible", + "CheckType": ["Software and Configuration Checks", "Industry and Regulatory Standards" ,"CIS AWS Foundations Benchmark"], + "ServiceName": "cloudtrail", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "critical", + "ResourceType": "AwsCloudTrailTrail", + "Description": "Ensure the S3 bucket CloudTrail logs to is not publicly accessible", + "Risk": "Allowing public access to CloudTrail log content may aid an adversary in identifying weaknesses in the affected accounts use or configuration.", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "https://docs.bridgecrew.io/docs/logging_3#aws-console", + "Terraform": "" + }, + "Recommendation": { + "Text": "Analyze Bucket policy to validate appropriate permissions. Ensure the AllUsers principal is not granted privileges. Ensure the AuthenticatedUsers principal is not granted privileges.", + "Url": "https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_principal.html" + } + }, + "Categories": [], + "Tags": { + "Tag1Key": "value", + "Tag2Key": "value" + }, + "DependsOn": [], + "RelatedTo": [], + "Notes": "", + "Compliance": [] + } diff --git a/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible.py b/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible.py new file mode 100644 index 00000000..d89aa776 --- /dev/null +++ b/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible.py @@ -0,0 +1,39 @@ +from lib.check.models import Check, Check_Report +from providers.aws.services.cloudtrail.cloudtrail_client import cloudtrail_client +from providers.aws.services.s3.s3_client import s3_client + + +class cloudtrail_logs_s3_bucket_is_not_publicly_accessible(Check): + def execute(self): + findings = [] + for trail in cloudtrail_client.trails: + if trail.name: + trail_bucket = trail.s3_bucket + report = Check_Report(self.metadata) + report.region = trail.region + report.resource_id = trail.name + report.resource_arn = trail.trail_arn + report.status = "PASS" + if trail.is_multiregion: + report.status_extended = f"S3 Bucket {trail_bucket} from multiregion trail {trail.name} is not publicly accessible" + else: + report.status_extended = f"S3 Bucket {trail_bucket} from single region trail {trail.name} is not publicly accessible" + for bucket in s3_client.buckets: + # Here we need to ensure that acl_grantee is filled since if we don't have permissions to query the api for a concrete region + # (for example due to a SCP) we are going to try access an attribute from a None type + if trail_bucket == bucket.name and bucket.acl_grantee: + for grant in bucket.acl_grantee: + if ( + grant.URI + == "http://acs.amazonaws.com/groups/global/AllUsers" + ): + report.status = "FAIL" + if trail.is_multiregion: + report.status_extended = f"S3 Bucket {trail_bucket} from multiregion trail {trail.name} is publicly accessible" + else: + report.status_extended = f"S3 Bucket {trail_bucket} from single region trail {trail.name} is publicly accessible" + break + + findings.append(report) + + return findings diff --git a/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible_test.py b/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible_test.py new file mode 100644 index 00000000..bf3a11f6 --- /dev/null +++ b/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible_test.py @@ -0,0 +1,166 @@ +from re import search +from unittest import mock + +from boto3 import client +from moto import mock_cloudtrail, mock_s3 + + +class Test_cloudtrail_logs_s3_bucket_is_not_publicly_accessible: + @mock_cloudtrail + @mock_s3 + def test_trail_bucket_no_acl(self): + cloudtrail_client = client("cloudtrail", region_name="us-east-1") + s3_client = client("s3", region_name="us-east-1") + trail_name_us = "trail_test_us" + bucket_name_us = "bucket_test_us" + s3_client.create_bucket(Bucket=bucket_name_us) + trail_us = cloudtrail_client.create_trail( + Name=trail_name_us, S3BucketName=bucket_name_us, IsMultiRegionTrail=False + ) + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.cloudtrail.cloudtrail_service import Cloudtrail + from providers.aws.services.s3.s3_service import S3 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.cloudtrail.cloudtrail_logs_s3_bucket_is_not_publicly_accessible.cloudtrail_logs_s3_bucket_is_not_publicly_accessible.cloudtrail_client", + new=Cloudtrail(current_audit_info), + ): + with mock.patch( + "providers.aws.services.cloudtrail.cloudtrail_logs_s3_bucket_access_logging_enabled.cloudtrail_logs_s3_bucket_access_logging_enabled.s3_client", + new=S3(current_audit_info), + ): + # Test Check + from providers.aws.services.cloudtrail.cloudtrail_logs_s3_bucket_is_not_publicly_accessible.cloudtrail_logs_s3_bucket_is_not_publicly_accessible import ( + cloudtrail_logs_s3_bucket_is_not_publicly_accessible, + ) + + check = cloudtrail_logs_s3_bucket_is_not_publicly_accessible() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].resource_id == trail_name_us + assert result[0].resource_arn == trail_us["TrailARN"] + assert search( + result[0].status_extended, + f"S3 Bucket {bucket_name_us} from single region trail {trail_name_us} is not publicly accessible", + ) + + @mock_cloudtrail + @mock_s3 + def test_trail_bucket_not_valid_acl(self): + cloudtrail_client = client("cloudtrail", region_name="us-east-1") + s3_client = client("s3", region_name="us-east-1") + trail_name_us = "trail_test_us" + bucket_name_us = "bucket_test_us" + s3_client.create_bucket(Bucket=bucket_name_us) + trail_us = cloudtrail_client.create_trail( + Name=trail_name_us, S3BucketName=bucket_name_us, IsMultiRegionTrail=False + ) + + s3_client.put_bucket_acl( + AccessControlPolicy={ + "Grants": [ + { + "Grantee": { + "DisplayName": "test", + "EmailAddress": "", + "ID": "test_ID", + "Type": "Group", + "URI": "http://acs.amazonaws.com/groups/global/AllUsers", + }, + "Permission": "READ", + }, + ], + "Owner": {"DisplayName": "test", "ID": "test_id"}, + }, + Bucket=bucket_name_us, + ) + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.cloudtrail.cloudtrail_service import Cloudtrail + from providers.aws.services.s3.s3_service import S3 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.cloudtrail.cloudtrail_logs_s3_bucket_is_not_publicly_accessible.cloudtrail_logs_s3_bucket_is_not_publicly_accessible.cloudtrail_client", + new=Cloudtrail(current_audit_info), + ): + with mock.patch( + "providers.aws.services.cloudtrail.cloudtrail_logs_s3_bucket_access_logging_enabled.cloudtrail_logs_s3_bucket_access_logging_enabled.s3_client", + new=S3(current_audit_info), + ): + # Test Check + from providers.aws.services.cloudtrail.cloudtrail_logs_s3_bucket_is_not_publicly_accessible.cloudtrail_logs_s3_bucket_is_not_publicly_accessible import ( + cloudtrail_logs_s3_bucket_is_not_publicly_accessible, + ) + + check = cloudtrail_logs_s3_bucket_is_not_publicly_accessible() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].resource_id == trail_name_us + assert result[0].resource_arn == trail_us["TrailARN"] + assert search( + result[0].status_extended, + f"S3 Bucket {bucket_name_us} from single region trail {trail_name_us} is publicly accessible", + ) + + @mock_cloudtrail + @mock_s3 + def test_trail_bucket_not_valid_acl(self): + cloudtrail_client = client("cloudtrail", region_name="us-east-1") + s3_client = client("s3", region_name="us-east-1") + trail_name_us = "trail_test_us" + bucket_name_us = "bucket_test_us" + s3_client.create_bucket(Bucket=bucket_name_us) + trail_us = cloudtrail_client.create_trail( + Name=trail_name_us, S3BucketName=bucket_name_us, IsMultiRegionTrail=False + ) + + s3_client.put_bucket_acl( + AccessControlPolicy={ + "Grants": [ + { + "Grantee": { + "DisplayName": "test", + "EmailAddress": "", + "ID": "test_ID", + "Type": "CanonicalUser", + "URI": "http://acs.amazonaws.com/groups/global/AuthenticatedUsers", + }, + "Permission": "READ", + }, + ], + "Owner": {"DisplayName": "test", "ID": "test_id"}, + }, + Bucket=bucket_name_us, + ) + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.cloudtrail.cloudtrail_service import Cloudtrail + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.cloudtrail.cloudtrail_logs_s3_bucket_is_not_publicly_accessible.cloudtrail_logs_s3_bucket_is_not_publicly_accessible.cloudtrail_client", + new=Cloudtrail(current_audit_info), + ): + # Test Check + from providers.aws.services.cloudtrail.cloudtrail_logs_s3_bucket_is_not_publicly_accessible.cloudtrail_logs_s3_bucket_is_not_publicly_accessible import ( + cloudtrail_logs_s3_bucket_is_not_publicly_accessible, + ) + + check = cloudtrail_logs_s3_bucket_is_not_publicly_accessible() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].resource_id == trail_name_us + assert result[0].resource_arn == trail_us["TrailARN"] + assert search( + result[0].status_extended, + f"S3 Bucket {bucket_name_us} from single region trail {trail_name_us} is not publicly accessible", + ) diff --git a/providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled/__init__.py b/providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled/cloudtrail_multi_region_enabled.metadata.json b/providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled/cloudtrail_multi_region_enabled.metadata.json new file mode 100644 index 00000000..b1fbe6cd --- /dev/null +++ b/providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled/cloudtrail_multi_region_enabled.metadata.json @@ -0,0 +1,46 @@ +{ + "Provider": "aws", + "CheckID": "cloudtrail_multi_region_enabled", + "CheckTitle": "Ensure CloudTrail is enabled in all regions", + "CheckType": ["Software and Configuration Checks", "Industry and Regulatory Standards" ,"CIS AWS Foundations Benchmark"], + "ServiceName": "cloudtrail", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "high", + "ResourceType": "AwsCloudTrailTrail", + "Description": "Ensure CloudTrail is enabled in all regions", + "Risk": "AWS CloudTrail is a web service that records AWS API calls for your account and delivers log files to you. The recorded information includes the identity of the API caller; the time of the API call; the source IP address of the API caller; the request parameters; and the response elements returned by the AWS service.", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "aws cloudtrail create-trail --name --bucket-name --is-multi-region-trail aws cloudtrail update-trail --name --is-multi-region-trail ", + "NativeIaC": "https://docs.bridgecrew.io/docs/logging_1#cloudformation", + "Other": "https://docs.bridgecrew.io/docs/logging_1#aws-console", + "Terraform": "https://docs.bridgecrew.io/docs/logging_1#terraform" + }, + "Recommendation": { + "Text": "Ensure Logging is set to ON on all regions (even if they are not being used at the moment.", + "Url": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrailconcepts.html#cloudtrail-concepts-management-events" + } + }, + "Categories": [], + "Tags": { + "Tag1Key": "value", + "Tag2Key": "value" + }, + "DependsOn": [], + "RelatedTo": [], + "Notes": "", + "Compliance": [ + { + "Control": [ + "2.1" + ], + "Framework": "CIS-AWS", + "Group": [ + "level1" + ], + "Version": "1.4" + } + ] + } diff --git a/providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled/cloudtrail_multi_region_enabled.py b/providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled/cloudtrail_multi_region_enabled.py new file mode 100644 index 00000000..8e324c3b --- /dev/null +++ b/providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled/cloudtrail_multi_region_enabled.py @@ -0,0 +1,47 @@ +from lib.check.models import Check, Check_Report +from providers.aws.services.cloudtrail.cloudtrail_client import cloudtrail_client + + +class cloudtrail_multi_region_enabled(Check): + def execute(self): + findings = [] + actual_region = None + for trail in cloudtrail_client.trails: + report = Check_Report(self.metadata) + report.region = trail.region + if trail.name: # Check if there are trails in region + # Check if region has changed and add report of previous region + if actual_region != trail.region: + if report: # Check if it not the beginning + findings.append(report) + trail_in_region = False + if not trail_in_region: + if trail.is_logging: + report.status = "PASS" + if trail.is_multiregion: + report.status_extended = ( + f"Trail {trail.name} is multiregion and it is logging" + ) + else: + report.status_extended = f"Trail {trail.name} is not multiregion and it is logging" + report.resource_id = trail.name + report.resource_arn = trail.trail_arn + trail_in_region = True # Trail enabled in region + else: + report.status = "FAIL" + report.status_extended = ( + f"No CloudTrail trails enabled and logging were found" + ) + report.resource_arn = "No trails" + report.resource_id = "No trails" + actual_region = trail.region + else: + report.status = "FAIL" + report.status_extended = ( + "No CloudTrail trails enabled and logging were found" + ) + report.resource_arn = "No trails" + report.resource_id = "No trails" + findings.append(report) + + return findings diff --git a/providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled/cloudtrail_multi_region_enabled_test.py b/providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled/cloudtrail_multi_region_enabled_test.py new file mode 100644 index 00000000..838a3f3e --- /dev/null +++ b/providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled/cloudtrail_multi_region_enabled_test.py @@ -0,0 +1,155 @@ +from re import search +from unittest import mock + +from boto3 import client +from moto import mock_cloudtrail, mock_s3 + + +class Test_cloudtrail_multi_region_enabled: + @mock_cloudtrail + def test_no_trails(self): + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.cloudtrail.cloudtrail_service import Cloudtrail + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.cloudtrail.cloudtrail_multi_region_enabled.cloudtrail_multi_region_enabled.cloudtrail_client", + new=Cloudtrail(current_audit_info), + ) as service_client: + # Test Check + from providers.aws.services.cloudtrail.cloudtrail_multi_region_enabled.cloudtrail_multi_region_enabled import ( + cloudtrail_multi_region_enabled, + ) + + regions = [] + for region in service_client.regional_clients.keys(): + regions.append(region) + + check = cloudtrail_multi_region_enabled() + result = check.execute() + assert len(result) == len(regions) + for report in result: + assert report.status == "FAIL" + assert search( + "No CloudTrail trails enabled and logging were found", + report.status_extended, + ) + assert report.resource_id == "No trails" + assert report.resource_arn == "No trails" + + @mock_cloudtrail + @mock_s3 + def test_various_trails_no_login(self): + cloudtrail_client_us_east_1 = client("cloudtrail", region_name="us-east-1") + s3_client_us_east_1 = client("s3", region_name="us-east-1") + cloudtrail_client_eu_west_1 = client("cloudtrail", region_name="eu-west-1") + s3_client_eu_west_1 = client("s3", region_name="eu-west-1") + trail_name_us = "trail_test_us" + bucket_name_us = "bucket_test_us" + trail_name_eu = "trail_test_eu" + bucket_name_eu = "bucket_test_eu" + s3_client_us_east_1.create_bucket(Bucket=bucket_name_us) + s3_client_eu_west_1.create_bucket( + Bucket=bucket_name_eu, + CreateBucketConfiguration={"LocationConstraint": "eu-west-1"}, + ) + trail_us = cloudtrail_client_us_east_1.create_trail( + Name=trail_name_us, S3BucketName=bucket_name_us, IsMultiRegionTrail=False + ) + trail_eu = cloudtrail_client_eu_west_1.create_trail( + Name=trail_name_eu, S3BucketName=bucket_name_eu, IsMultiRegionTrail=False + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.cloudtrail.cloudtrail_service import Cloudtrail + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.cloudtrail.cloudtrail_multi_region_enabled.cloudtrail_multi_region_enabled.cloudtrail_client", + new=Cloudtrail(current_audit_info), + ) as service_client: + # Test Check + from providers.aws.services.cloudtrail.cloudtrail_multi_region_enabled.cloudtrail_multi_region_enabled import ( + cloudtrail_multi_region_enabled, + ) + + regions = [] + for region in service_client.regional_clients.keys(): + regions.append(region) + + check = cloudtrail_multi_region_enabled() + result = check.execute() + assert len(result) == len(regions) + for report in result: + assert report.status == "FAIL" + assert search( + "No CloudTrail trails enabled and logging were found", + report.status_extended, + ) + assert report.resource_id == "No trails" + assert report.resource_arn == "No trails" + + @mock_cloudtrail + @mock_s3 + def test_various_trails_with_and_without_login(self): + cloudtrail_client_us_east_1 = client("cloudtrail", region_name="us-east-1") + s3_client_us_east_1 = client("s3", region_name="us-east-1") + cloudtrail_client_eu_west_1 = client("cloudtrail", region_name="eu-west-1") + s3_client_eu_west_1 = client("s3", region_name="eu-west-1") + trail_name_us = "trail_test_us" + bucket_name_us = "bucket_test_us" + trail_name_eu = "trail_test_eu" + bucket_name_eu = "bucket_test_eu" + s3_client_us_east_1.create_bucket(Bucket=bucket_name_us) + s3_client_eu_west_1.create_bucket( + Bucket=bucket_name_eu, + CreateBucketConfiguration={"LocationConstraint": "eu-west-1"}, + ) + trail_us = cloudtrail_client_us_east_1.create_trail( + Name=trail_name_us, S3BucketName=bucket_name_us, IsMultiRegionTrail=False + ) + cloudtrail_client_eu_west_1.create_trail( + Name=trail_name_eu, S3BucketName=bucket_name_eu, IsMultiRegionTrail=False + ) + response = cloudtrail_client_us_east_1.start_logging(Name=trail_name_us) + status = cloudtrail_client_us_east_1.get_trail_status(Name=trail_name_us) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.cloudtrail.cloudtrail_service import Cloudtrail + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.cloudtrail.cloudtrail_multi_region_enabled.cloudtrail_multi_region_enabled.cloudtrail_client", + new=Cloudtrail(current_audit_info), + ) as service_client: + # Test Check + from providers.aws.services.cloudtrail.cloudtrail_multi_region_enabled.cloudtrail_multi_region_enabled import ( + cloudtrail_multi_region_enabled, + ) + + regions = [] + for region in service_client.regional_clients.keys(): + regions.append(region) + + check = cloudtrail_multi_region_enabled() + result = check.execute() + assert len(result) == len(regions) + for report in result: + if report.resource_id == trail_name_us: + assert report.status == "PASS" + assert search( + "is not multiregion and it is logging", report.status_extended + ) + assert report.resource_id == trail_name_us + assert report.resource_arn == trail_us["TrailARN"] + else: + assert report.status == "FAIL" + assert search( + "No CloudTrail trails enabled and logging were found", + report.status_extended, + ) + assert report.resource_id == "No trails" + assert report.resource_arn == "No trails" diff --git a/providers/aws/services/cloudtrail/cloudtrail_service.py b/providers/aws/services/cloudtrail/cloudtrail_service.py new file mode 100644 index 00000000..75ab11e3 --- /dev/null +++ b/providers/aws/services/cloudtrail/cloudtrail_service.py @@ -0,0 +1,131 @@ +import datetime +import threading +from dataclasses import dataclass + +from lib.logger import logger +from providers.aws.aws_provider import generate_regional_clients + + +################### CLOUDTRAIL +class Cloudtrail: + def __init__(self, audit_info): + self.service = "cloudtrail" + self.session = audit_info.audit_session + self.audited_account = audit_info.audited_account + self.regional_clients = generate_regional_clients(self.service, audit_info) + self.trails = [] + self.__threading_call__(self.__get_trails__) + self.__get_trail_status__() + + def __get_session__(self): + return self.session + + def __threading_call__(self, call): + threads = [] + for regional_client in self.regional_clients.values(): + threads.append(threading.Thread(target=call, args=(regional_client,))) + for t in threads: + t.start() + for t in threads: + t.join() + + def __get_trails__(self, regional_client): + logger.info("Cloudtrail - Getting trails...") + try: + describe_trails = regional_client.describe_trails()["trailList"] + if describe_trails: + for trail in describe_trails: + if "KmsKeyId" in trail: + kms_key_id = trail["KmsKeyId"] + else: + kms_key_id = None + self.trails.append( + Trail( + name=trail["Name"], + is_multiregion=trail["IsMultiRegionTrail"], + home_region=trail["HomeRegion"], + trail_arn=trail["TrailARN"], + region=regional_client.region, + is_logging=False, + log_file_validation_enabled=trail[ + "LogFileValidationEnabled" + ], + latest_cloudwatch_delivery_time=None, + s3_bucket=trail["S3BucketName"], + kms_key=kms_key_id, + ) + ) + else: + self.trails.append( + Trail( + name=None, + is_multiregion=None, + home_region=None, + trail_arn=None, + region=regional_client.region, + is_logging=None, + log_file_validation_enabled=None, + latest_cloudwatch_delivery_time=None, + s3_bucket=None, + kms_key=None, + ) + ) + + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}: {error}" + ) + + def __get_trail_status__(self): + logger.info("Cloudtrail - Getting trail status") + try: + for trail in self.trails: + for region, client in self.regional_clients.items(): + if trail.region == region and trail.name: + status = client.get_trail_status(Name=trail.trail_arn) + trail.is_logging = status["IsLogging"] + if "LatestCloudWatchLogsDeliveryTime" in status: + trail.latest_cloudwatch_delivery_time = status[ + "LatestCloudWatchLogsDeliveryTime" + ] + + except Exception as error: + logger.error(f"{client.region} -- {error.__class__.__name__}: {error}") + + +@dataclass +class Trail: + name: str + is_multiregion: bool + home_region: str + trail_arn: str + region: str + is_logging: bool + log_file_validation_enabled: bool + latest_cloudwatch_delivery_time: datetime + s3_bucket: str + kms_key: str + + def __init__( + self, + name, + is_multiregion, + home_region, + trail_arn, + region, + is_logging, + log_file_validation_enabled, + latest_cloudwatch_delivery_time, + s3_bucket, + kms_key, + ): + self.name = name + self.is_multiregion = is_multiregion + self.home_region = home_region + self.trail_arn = trail_arn + self.region = region + self.is_logging = is_logging + self.log_file_validation_enabled = log_file_validation_enabled + self.latest_cloudwatch_delivery_time = latest_cloudwatch_delivery_time + self.s3_bucket = s3_bucket + self.kms_key = kms_key diff --git a/providers/aws/services/cloudtrail/cloudtrail_service_test.py b/providers/aws/services/cloudtrail/cloudtrail_service_test.py new file mode 100644 index 00000000..0d9afdcc --- /dev/null +++ b/providers/aws/services/cloudtrail/cloudtrail_service_test.py @@ -0,0 +1,145 @@ +from boto3 import client, session +from moto import mock_cloudtrail, mock_s3 + +from providers.aws.lib.audit_info.models import AWS_Audit_Info +from providers.aws.services.cloudtrail.cloudtrail_service import Cloudtrail + +AWS_ACCOUNT_NUMBER = 123456789012 + + +class Test_Cloudtrail_Service: + # Mocked Audit Info + def set_mocked_audit_info(self): + audit_info = AWS_Audit_Info( + original_session=None, + audit_session=session.Session( + profile_name=None, + botocore_session=None, + ), + audited_account=AWS_ACCOUNT_NUMBER, + audited_user_id=None, + audited_partition="aws", + audited_identity_arn=None, + profile=None, + profile_region=None, + credentials=None, + assumed_role_info=None, + audited_regions=None, + organizations_metadata=None, + ) + return audit_info + + # Test Cloudtrail Service + @mock_cloudtrail + def test_service(self): + audit_info = self.set_mocked_audit_info() + cloudtrail = Cloudtrail(audit_info) + assert cloudtrail.service == "cloudtrail" + + # Test Cloudtrail client + @mock_cloudtrail + def test_client(self): + audit_info = self.set_mocked_audit_info() + cloudtrail = Cloudtrail(audit_info) + for client in cloudtrail.regional_clients.values(): + assert client.__class__.__name__ == "CloudTrail" + + # Test Cloudtrail session + @mock_cloudtrail + def test__get_session__(self): + audit_info = self.set_mocked_audit_info() + cloudtrail = Cloudtrail(audit_info) + assert cloudtrail.session.__class__.__name__ == "Session" + + # Test Cloudtrail Session + @mock_cloudtrail + def test_audited_account(self): + audit_info = self.set_mocked_audit_info() + cloudtrail = Cloudtrail(audit_info) + assert cloudtrail.audited_account == AWS_ACCOUNT_NUMBER + + # WAITING FOR MOTO PR TO BE APPROVED (https://github.com/spulec/moto/pull/5607) + + @mock_cloudtrail + @mock_s3 + def test_describe_trails(self): + + cloudtrail_client_us_east_1 = client("cloudtrail", region_name="us-east-1") + s3_client_us_east_1 = client("s3", region_name="us-east-1") + cloudtrail_client_eu_west_1 = client("cloudtrail", region_name="eu-west-1") + s3_client_eu_west_1 = client("s3", region_name="eu-west-1") + trail_name_us = "trail_test_us" + bucket_name_us = "bucket_test_us" + trail_name_eu = "trail_test_eu" + bucket_name_eu = "bucket_test_eu" + s3_client_us_east_1.create_bucket(Bucket=bucket_name_us) + s3_client_eu_west_1.create_bucket( + Bucket=bucket_name_eu, + CreateBucketConfiguration={"LocationConstraint": "eu-west-1"}, + ) + cloudtrail_client_us_east_1.create_trail( + Name=trail_name_us, S3BucketName=bucket_name_us, IsMultiRegionTrail=False + ) + cloudtrail_client_eu_west_1.create_trail( + Name=trail_name_eu, S3BucketName=bucket_name_eu, IsMultiRegionTrail=False + ) + audit_info = self.set_mocked_audit_info() + cloudtrail = Cloudtrail(audit_info) + # Here we are expecting 2, but moto does something weird and return 46 records + assert len(cloudtrail.trails) == 23 + for trail in cloudtrail.trails: + if trail.name: + assert trail.name == trail_name_us or trail.name == trail_name_eu + assert not trail.is_multiregion + assert ( + trail.home_region == "us-east-1" or trail.home_region == "eu-west-1" + ) + assert trail.region == "us-east-1" or trail.region == "eu-west-1" + assert not trail.is_logging + assert not trail.log_file_validation_enabled + assert not trail.latest_cloudwatch_delivery_time + assert ( + trail.s3_bucket == bucket_name_eu + or trail.s3_bucket == bucket_name_us + ) + + @mock_cloudtrail + @mock_s3 + def test_status_trails(self): + cloudtrail_client_us_east_1 = client("cloudtrail", region_name="us-east-1") + s3_client_us_east_1 = client("s3", region_name="us-east-1") + cloudtrail_client_eu_west_1 = client("cloudtrail", region_name="eu-west-1") + s3_client_eu_west_1 = client("s3", region_name="eu-west-1") + trail_name_us = "trail_test_us" + bucket_name_us = "bucket_test_us" + trail_name_eu = "trail_test_eu" + bucket_name_eu = "bucket_test_eu" + s3_client_us_east_1.create_bucket(Bucket=bucket_name_us) + s3_client_eu_west_1.create_bucket( + Bucket=bucket_name_eu, + CreateBucketConfiguration={"LocationConstraint": "eu-west-1"}, + ) + cloudtrail_client_us_east_1.create_trail( + Name=trail_name_us, + S3BucketName=bucket_name_us, + IsMultiRegionTrail=False, + EnableLogFileValidation=True, + ) + cloudtrail_client_us_east_1.start_logging(Name=trail_name_us) + cloudtrail_client_eu_west_1.create_trail( + Name=trail_name_eu, S3BucketName=bucket_name_eu, IsMultiRegionTrail=False + ) + audit_info = self.set_mocked_audit_info() + cloudtrail = Cloudtrail(audit_info) + # Here we are expecting 2, but moto does something weird and return 46 records + assert len(cloudtrail.trails) == 23 + for trail in cloudtrail.trails: + if trail.name: + if trail.name == trail_name_us: + assert not trail.is_multiregion + assert trail.home_region == "us-east-1" + assert trail.region == "us-east-1" + assert trail.is_logging + assert trail.log_file_validation_enabled + assert not trail.latest_cloudwatch_delivery_time + assert trail.s3_bucket == bucket_name_us diff --git a/providers/aws/services/s3/s3_service.py b/providers/aws/services/s3/s3_service.py index 7404a5d5..dc0a2355 100644 --- a/providers/aws/services/s3/s3_service.py +++ b/providers/aws/services/s3/s3_service.py @@ -16,6 +16,7 @@ class S3: self.buckets = self.__list_buckets__() self.__threading_call__(self.__get_bucket_versioning__) self.__threading_call__(self.__get_bucket_logging__) + self.__threading_call__(self.__get_bucket_acl__) def __get_session__(self): return self.session @@ -74,11 +75,52 @@ class S3: bucket_logging = regional_client.get_bucket_logging(Bucket=bucket.name) if "LoggingEnabled" in bucket_logging: bucket.logging = True + bucket.logging_target_bucket = bucket_logging["LoggingEnabled"][ + "TargetBucket" + ] except Exception as error: logger.error( f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + def __get_bucket_acl__(self, bucket): + logger.info("S3 - Get buckets acl...") + try: + grantees = [] + regional_client = self.regional_clients[bucket.region] + acl_grants = regional_client.get_bucket_acl(Bucket=bucket.name)["Grants"] + for grant in acl_grants: + grantee = ACL_Grantee(grantee_type=grant["Grantee"]) + if "DisplayName" in grant["Grantee"]: + grantee.display_name = grant["Grantee"]["DisplayName"] + if "Type" in grant["Grantee"]: + grantee.grantee_type = grant["Grantee"]["Type"] + if "ID" in grant["Grantee"]: + grantee.ID = grant["Grantee"]["ID"] + if "URI" in grant["Grantee"]: + grantee.URI = grant["Grantee"]["URI"] + grantees.append(grantee) + + bucket.acl_grantee = grantees + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + +@dataclass +class ACL_Grantee: + display_name: str + ID: str + grantee_type: str + URI: str + + def __init__(self, grantee_type): + self.display_name = None + self.ID = None + self.grantee_type = grantee_type + self.URI = None + @dataclass class Bucket: @@ -86,9 +128,13 @@ class Bucket: versioning: bool logging: bool region: str + acl_grantee: list[ACL_Grantee] + logging_target_bucket: str def __init__(self, name, region): self.name = name self.versioning = False self.logging = False self.region = region + self.acl_grantee = None + self.logging_target_bucket = None diff --git a/providers/aws/services/s3/s3_service_test.py b/providers/aws/services/s3/s3_service_test.py index 7524379e..f1d75239 100644 --- a/providers/aws/services/s3/s3_service_test.py +++ b/providers/aws/services/s3/s3_service_test.py @@ -105,6 +105,41 @@ class Test_S3_Service: assert s3.buckets[0].name == bucket_name assert s3.buckets[0].versioning == True + # Test S3 Get Bucket Versioning + @mock_s3 + def test__get_bucket_acl__(self): + s3_client = client("s3") + bucket_name = "test-bucket" + s3_client.create_bucket(Bucket=bucket_name) + s3_client.put_bucket_acl( + AccessControlPolicy={ + "Grants": [ + { + "Grantee": { + "DisplayName": "test", + "ID": "test_ID", + "Type": "Group", + "URI": "http://acs.amazonaws.com/groups/global/AllUsers", + }, + "Permission": "READ", + }, + ], + "Owner": {"DisplayName": "test", "ID": "test_id"}, + }, + Bucket=bucket_name, + ) + audit_info = self.set_mocked_audit_info() + s3 = S3(audit_info) + assert len(s3.buckets) == 1 + assert s3.buckets[0].name == bucket_name + assert s3.buckets[0].acl_grantee[0].display_name == "test" + assert s3.buckets[0].acl_grantee[0].ID == "test_ID" + assert s3.buckets[0].acl_grantee[0].grantee_type == "Group" + assert ( + s3.buckets[0].acl_grantee[0].URI + == "http://acs.amazonaws.com/groups/global/AllUsers" + ) + # Test S3 Get Bucket Versioning # @mock_s3 # def test__get_bucket_logging__(self):