mirror of
https://github.com/ghndrx/prowler.git
synced 2026-02-10 06:45:08 +00:00
feat(cloudtrail): cloudtrail service and checks (#1449)
Co-authored-by: sergargar <sergio@verica.io> Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -43,3 +43,6 @@ junit-reports/
|
|||||||
|
|
||||||
# Text
|
# Text
|
||||||
*.txt
|
*.txt
|
||||||
|
|
||||||
|
# .env
|
||||||
|
.env
|
||||||
|
|||||||
2
Pipfile
2
Pipfile
@@ -9,7 +9,7 @@ boto3 = "1.24.8"
|
|||||||
arnparse = "0.0.2"
|
arnparse = "0.0.2"
|
||||||
botocore = "1.27.8"
|
botocore = "1.27.8"
|
||||||
pydantic = "1.9.1"
|
pydantic = "1.9.1"
|
||||||
moto = "4.0.8"
|
moto = "4.0.9"
|
||||||
sure = "2.0.0"
|
sure = "2.0.0"
|
||||||
bandit = "1.7.4"
|
bandit = "1.7.4"
|
||||||
safety = "1.10.3"
|
safety = "1.10.3"
|
||||||
|
|||||||
239
Pipfile.lock
generated
239
Pipfile.lock
generated
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "02042e0bff2fe10e70314636dd3fe288654c6b0deb47a1270b0d1dbcf3660cd5"
|
"sha256": "17d457477683f58bd28bc11042edcc0374d620d037243490cc3c7803cb5a4536"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
@@ -42,25 +42,26 @@
|
|||||||
},
|
},
|
||||||
"boto3": {
|
"boto3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2284a107d43f73b6007c7c8b946a8fd6f9baa6c97b5c956edc67d9be864def58"
|
"sha256:0a19d07a39d69b8e84e24d75474bbf4e737b1749d0c665503dfc2446f321e1f0",
|
||||||
|
"sha256:8f0e4eb5c26f927c09bc03302d38156af578b816f1e4f8ca4f0f734d134b9d4f"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==1.25.3"
|
"version": "==1.26.0"
|
||||||
},
|
},
|
||||||
"botocore": {
|
"botocore": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2c2604262e5ab35ea83e9d5cf8be267e7fcdab6c815a432cfe15f23d92ce723d",
|
"sha256:c706640f8cf9297d2af87fc711394631afc14aaa225c7554e220964b5047b47d",
|
||||||
"sha256:4ea45626d8c5875c12e5767aa637388ce81871162f494eaf7b3888e875de84b7"
|
"sha256:f25dc0827005f81abf4b890a20c71f64ee40ea9e9795df38a242fdeed79e0a89"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==1.28.3"
|
"version": "==1.29.0"
|
||||||
},
|
},
|
||||||
"certifi": {
|
"certifi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14",
|
"sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14",
|
||||||
"sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"
|
"sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"markers": "python_full_version >= '3.6.0'",
|
||||||
"version": "==2022.9.24"
|
"version": "==2022.9.24"
|
||||||
},
|
},
|
||||||
"cffi": {
|
"cffi": {
|
||||||
@@ -221,51 +222,35 @@
|
|||||||
},
|
},
|
||||||
"cryptography": {
|
"cryptography": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a",
|
"sha256:068147f32fa662c81aebab95c74679b401b12b57494872886eb5c1139250ec5d",
|
||||||
"sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f",
|
"sha256:06fc3cc7b6f6cca87bd56ec80a580c88f1da5306f505876a71c8cfa7050257dd",
|
||||||
"sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0",
|
"sha256:25c1d1f19729fb09d42e06b4bf9895212292cb27bb50229f5aa64d039ab29146",
|
||||||
"sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407",
|
"sha256:402852a0aea73833d982cabb6d0c3bb582c15483d29fb7085ef2c42bfa7e38d7",
|
||||||
"sha256:1db3d807a14931fa317f96435695d9ec386be7b84b618cc61cfa5d08b0ae33d7",
|
"sha256:4e269dcd9b102c5a3d72be3c45d8ce20377b8076a43cbed6f660a1afe365e436",
|
||||||
"sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6",
|
"sha256:5419a127426084933076132d317911e3c6eb77568a1ce23c3ac1e12d111e61e0",
|
||||||
"sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153",
|
"sha256:554bec92ee7d1e9d10ded2f7e92a5d70c1f74ba9524947c0ba0c850c7b011828",
|
||||||
"sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750",
|
"sha256:5e89468fbd2fcd733b5899333bc54d0d06c80e04cd23d8c6f3e0542358c6060b",
|
||||||
"sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad",
|
"sha256:65535bc550b70bd6271984d9863a37741352b4aad6fb1b3344a54e6950249b55",
|
||||||
"sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6",
|
"sha256:6ab9516b85bebe7aa83f309bacc5f44a61eeb90d0b4ec125d2d003ce41932d36",
|
||||||
"sha256:52e7bee800ec869b4031093875279f1ff2ed12c1e2f74923e8f49c916afd1d3b",
|
"sha256:6addc3b6d593cd980989261dc1cce38263c76954d758c3c94de51f1e010c9a50",
|
||||||
"sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5",
|
"sha256:728f2694fa743a996d7784a6194da430f197d5c58e2f4e278612b359f455e4a2",
|
||||||
"sha256:765fa194a0f3372d83005ab83ab35d7c5526c4e22951e46059b8ac678b44fa5a",
|
"sha256:785e4056b5a8b28f05a533fab69febf5004458e20dad7e2e13a3120d8ecec75a",
|
||||||
"sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d",
|
"sha256:78cf5eefac2b52c10398a42765bfa981ce2372cbc0457e6bf9658f41ec3c41d8",
|
||||||
"sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d",
|
"sha256:7f836217000342d448e1c9a342e9163149e45d5b5eca76a30e84503a5a96cab0",
|
||||||
"sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294",
|
"sha256:8d41a46251bf0634e21fac50ffd643216ccecfaf3701a063257fe0b2be1b6548",
|
||||||
"sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0",
|
"sha256:984fe150f350a3c91e84de405fe49e688aa6092b3525f407a18b9646f6612320",
|
||||||
"sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a",
|
"sha256:9b24bcff7853ed18a63cfb0c2b008936a9554af24af2fb146e16d8e1aed75748",
|
||||||
"sha256:b0163a849b6f315bf52815e238bc2b2346604413fa7c1601eea84bcddb5fb9ac",
|
"sha256:b1b35d9d3a65542ed2e9d90115dfd16bbc027b3f07ee3304fc83580f26e43249",
|
||||||
"sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61",
|
"sha256:b1b52c9e5f8aa2b802d48bd693190341fae201ea51c7a167d69fc48b60e8a959",
|
||||||
"sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013",
|
"sha256:bbf203f1a814007ce24bd4d51362991d5cb90ba0c177a9c08825f2cc304d871f",
|
||||||
"sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e",
|
"sha256:be243c7e2bfcf6cc4cb350c0d5cdf15ca6383bbcb2a8ef51d3c9411a9d4386f0",
|
||||||
"sha256:d1a5bd52d684e49a36582193e0b89ff267704cd4025abefb9e26803adeb3e5fb",
|
"sha256:bfbe6ee19615b07a98b1d2287d6a6073f734735b49ee45b11324d85efc4d5cbd",
|
||||||
"sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9",
|
"sha256:c46837ea467ed1efea562bbeb543994c2d1f6e800785bd5a2c98bc096f5cb220",
|
||||||
"sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd",
|
"sha256:dfb4f4dd568de1b6af9f4cda334adf7d72cf5bc052516e1b2608b683375dd95c",
|
||||||
"sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818"
|
"sha256:ed7b00096790213e09eb11c97cc6e2b757f15f3d2f85833cd2d3ec3fe37c1722"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"markers": "python_full_version >= '3.6.0'",
|
||||||
"version": "==38.0.1"
|
"version": "==38.0.3"
|
||||||
},
|
|
||||||
"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"
|
|
||||||
},
|
},
|
||||||
"dparse": {
|
"dparse": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -296,7 +281,7 @@
|
|||||||
"sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd",
|
"sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd",
|
||||||
"sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"
|
"sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"markers": "python_full_version >= '3.6.0'",
|
||||||
"version": "==4.0.9"
|
"version": "==4.0.9"
|
||||||
},
|
},
|
||||||
"gitpython": {
|
"gitpython": {
|
||||||
@@ -315,14 +300,6 @@
|
|||||||
"markers": "python_version >= '3.5'",
|
"markers": "python_version >= '3.5'",
|
||||||
"version": "==3.4"
|
"version": "==3.4"
|
||||||
},
|
},
|
||||||
"importlib-resources": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:c01b1b94210d9849f286b86bb51bcea7cd56dde0600d8db721d7b81330711668",
|
|
||||||
"sha256:ee17ec648f85480d523596ce49eae8ead87d5631ae1551f913c0100b5edd3437"
|
|
||||||
],
|
|
||||||
"markers": "python_version >= '3.7'",
|
|
||||||
"version": "==5.10.0"
|
|
||||||
},
|
|
||||||
"iniconfig": {
|
"iniconfig": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
|
"sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
|
||||||
@@ -346,47 +323,6 @@
|
|||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==1.0.1"
|
"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": {
|
"markupsafe": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003",
|
"sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003",
|
||||||
@@ -438,52 +374,26 @@
|
|||||||
"sha256:122fcb64ee37cfad5b3f48d7a7d51875d7031aaf3d8be7c42e2bee25044eee62",
|
"sha256:122fcb64ee37cfad5b3f48d7a7d51875d7031aaf3d8be7c42e2bee25044eee62",
|
||||||
"sha256:7d3fbbde18228f4ff2f1f119a45cdffa458b4c0dee32eb4d2bb2f82554bac7bc"
|
"sha256:7d3fbbde18228f4ff2f1f119a45cdffa458b4c0dee32eb4d2bb2f82554bac7bc"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"markers": "python_full_version >= '3.6.0'",
|
||||||
"version": "==4.0.3"
|
"version": "==4.0.3"
|
||||||
},
|
},
|
||||||
"moto": {
|
"moto": {
|
||||||
"extras": [
|
"extras": [],
|
||||||
"iam"
|
|
||||||
],
|
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:3bd8a72dc385819c84ed3f9d18c386985634041c6ae544d957bd8ab88c6c15f1",
|
"sha256:2fb909d2ea1b732f89604e4268e2c2207c253e590a635a410c3c2aaebb34e113",
|
||||||
"sha256:8761880f5f3c3af27daac3882a56aae7e21fa2121f430cf917e55e977bddaf6b"
|
"sha256:ba03b638cf3b1cec64cbe9ac0d184ca898b69020c8e3c5b9b4961c1670629010"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==4.0.8"
|
"version": "==4.0.9"
|
||||||
},
|
|
||||||
"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"
|
|
||||||
},
|
},
|
||||||
"packaging": {
|
"packaging": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
|
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
|
||||||
"sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"
|
"sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"markers": "python_full_version >= '3.6.0'",
|
||||||
"version": "==21.3"
|
"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": {
|
"pbr": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:b97bc6695b2aff02144133c2e7399d5885223d42b7912ffaec2ca3898e673bfe",
|
"sha256:b97bc6695b2aff02144133c2e7399d5885223d42b7912ffaec2ca3898e673bfe",
|
||||||
@@ -497,7 +407,7 @@
|
|||||||
"sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
|
"sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
|
||||||
"sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
|
"sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"markers": "python_full_version >= '3.6.0'",
|
||||||
"version": "==1.0.0"
|
"version": "==1.0.0"
|
||||||
},
|
},
|
||||||
"pycparser": {
|
"pycparser": {
|
||||||
@@ -557,33 +467,6 @@
|
|||||||
"markers": "python_full_version >= '3.6.8'",
|
"markers": "python_full_version >= '3.6.8'",
|
||||||
"version": "==3.0.9"
|
"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": {
|
"pytest": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71",
|
"sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71",
|
||||||
@@ -605,15 +488,15 @@
|
|||||||
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
|
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
|
||||||
"sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
|
"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"
|
"version": "==2.8.2"
|
||||||
},
|
},
|
||||||
"pytz": {
|
"pytz": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:335ab46900b1465e714b4fda4963d87363264eb662aab5e65da039c25f1f5b22",
|
"sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427",
|
||||||
"sha256:c4d88f472f54d615e9cd582a5004d1e5f624854a6a27a6211591c251f22a6914"
|
"sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2"
|
||||||
],
|
],
|
||||||
"version": "==2022.5"
|
"version": "==2022.6"
|
||||||
},
|
},
|
||||||
"pyyaml": {
|
"pyyaml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -658,7 +541,7 @@
|
|||||||
"sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
|
"sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
|
||||||
"sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
|
"sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"markers": "python_full_version >= '3.6.0'",
|
||||||
"version": "==6.0"
|
"version": "==6.0"
|
||||||
},
|
},
|
||||||
"requests": {
|
"requests": {
|
||||||
@@ -721,7 +604,7 @@
|
|||||||
"sha256:f01da5790e95815eb5a8a138508c01c758e5f5bc0ce4286c4f7028b8dd7ac3d0",
|
"sha256:f01da5790e95815eb5a8a138508c01c758e5f5bc0ce4286c4f7028b8dd7ac3d0",
|
||||||
"sha256:f34019dced51047d6f70cb9383b2ae2853b7fc4dce65129a5acd49f4f9256646"
|
"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"
|
"version": "==0.2.7"
|
||||||
},
|
},
|
||||||
"s3transfer": {
|
"s3transfer": {
|
||||||
@@ -760,7 +643,7 @@
|
|||||||
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
|
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
|
||||||
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
|
"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"
|
"version": "==1.16.0"
|
||||||
},
|
},
|
||||||
"smmap": {
|
"smmap": {
|
||||||
@@ -768,7 +651,7 @@
|
|||||||
"sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94",
|
"sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94",
|
||||||
"sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"
|
"sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"markers": "python_full_version >= '3.6.0'",
|
||||||
"version": "==5.0.0"
|
"version": "==5.0.0"
|
||||||
},
|
},
|
||||||
"stevedore": {
|
"stevedore": {
|
||||||
@@ -791,7 +674,7 @@
|
|||||||
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
|
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
|
||||||
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
|
"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"
|
"version": "==0.10.2"
|
||||||
},
|
},
|
||||||
"tomli": {
|
"tomli": {
|
||||||
@@ -833,14 +716,6 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==2.6"
|
"version": "==2.6"
|
||||||
},
|
},
|
||||||
"websocket-client": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:398909eb7e261f44b8f4bd474785b6ec5f5b499d4953342fe9755e01ef624090",
|
|
||||||
"sha256:f9611eb65c8241a67fb373bef040b3cf8ad377a9f6546a12b620b6511e8ea9ef"
|
|
||||||
],
|
|
||||||
"markers": "python_version >= '3.7'",
|
|
||||||
"version": "==1.4.1"
|
|
||||||
},
|
|
||||||
"werkzeug": {
|
"werkzeug": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f",
|
"sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f",
|
||||||
@@ -864,14 +739,6 @@
|
|||||||
],
|
],
|
||||||
"markers": "python_version >= '3.4'",
|
"markers": "python_version >= '3.4'",
|
||||||
"version": "==0.13.0"
|
"version": "==0.13.0"
|
||||||
},
|
|
||||||
"zipp": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1",
|
|
||||||
"sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8"
|
|
||||||
],
|
|
||||||
"markers": "python_version < '3.10'",
|
|
||||||
"version": "==3.10.0"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"develop": {}
|
"develop": {}
|
||||||
|
|||||||
0
providers/aws/services/cloudtrail/__init__.py
Normal file
0
providers/aws/services/cloudtrail/__init__.py
Normal file
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
4
providers/aws/services/cloudtrail/cloudtrail_client.py
Normal file
4
providers/aws/services/cloudtrail/cloudtrail_client.py
Normal file
@@ -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)
|
||||||
@@ -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 <trail_name> --cloudwatch-logs-log-group- arn <cloudtrail_log_group_arn> --cloudwatch-logs-role-arn <cloudtrail_cloudwatchLogs_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": []
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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",
|
||||||
|
)
|
||||||
@@ -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 <trail_name> --kms-id <cloudtrail_kms_key> aws kms put-key-policy --key-id <cloudtrail_kms_key> --policy <cloudtrail_kms_key_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": []
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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"]
|
||||||
@@ -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 <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": []
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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"]
|
||||||
@@ -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": []
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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"]
|
||||||
@@ -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": []
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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",
|
||||||
|
)
|
||||||
@@ -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 <trail_name> --bucket-name <s3_bucket_for_cloudtrail> --is-multi-region-trail aws cloudtrail update-trail --name <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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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"
|
||||||
131
providers/aws/services/cloudtrail/cloudtrail_service.py
Normal file
131
providers/aws/services/cloudtrail/cloudtrail_service.py
Normal file
@@ -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
|
||||||
145
providers/aws/services/cloudtrail/cloudtrail_service_test.py
Normal file
145
providers/aws/services/cloudtrail/cloudtrail_service_test.py
Normal file
@@ -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
|
||||||
@@ -16,6 +16,7 @@ class S3:
|
|||||||
self.buckets = self.__list_buckets__()
|
self.buckets = self.__list_buckets__()
|
||||||
self.__threading_call__(self.__get_bucket_versioning__)
|
self.__threading_call__(self.__get_bucket_versioning__)
|
||||||
self.__threading_call__(self.__get_bucket_logging__)
|
self.__threading_call__(self.__get_bucket_logging__)
|
||||||
|
self.__threading_call__(self.__get_bucket_acl__)
|
||||||
|
|
||||||
def __get_session__(self):
|
def __get_session__(self):
|
||||||
return self.session
|
return self.session
|
||||||
@@ -74,11 +75,52 @@ class S3:
|
|||||||
bucket_logging = regional_client.get_bucket_logging(Bucket=bucket.name)
|
bucket_logging = regional_client.get_bucket_logging(Bucket=bucket.name)
|
||||||
if "LoggingEnabled" in bucket_logging:
|
if "LoggingEnabled" in bucket_logging:
|
||||||
bucket.logging = True
|
bucket.logging = True
|
||||||
|
bucket.logging_target_bucket = bucket_logging["LoggingEnabled"][
|
||||||
|
"TargetBucket"
|
||||||
|
]
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {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
|
@dataclass
|
||||||
class Bucket:
|
class Bucket:
|
||||||
@@ -86,9 +128,13 @@ class Bucket:
|
|||||||
versioning: bool
|
versioning: bool
|
||||||
logging: bool
|
logging: bool
|
||||||
region: str
|
region: str
|
||||||
|
acl_grantee: list[ACL_Grantee]
|
||||||
|
logging_target_bucket: str
|
||||||
|
|
||||||
def __init__(self, name, region):
|
def __init__(self, name, region):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.versioning = False
|
self.versioning = False
|
||||||
self.logging = False
|
self.logging = False
|
||||||
self.region = region
|
self.region = region
|
||||||
|
self.acl_grantee = None
|
||||||
|
self.logging_target_bucket = None
|
||||||
|
|||||||
@@ -105,6 +105,41 @@ class Test_S3_Service:
|
|||||||
assert s3.buckets[0].name == bucket_name
|
assert s3.buckets[0].name == bucket_name
|
||||||
assert s3.buckets[0].versioning == True
|
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
|
# Test S3 Get Bucket Versioning
|
||||||
# @mock_s3
|
# @mock_s3
|
||||||
# def test__get_bucket_logging__(self):
|
# def test__get_bucket_logging__(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user