diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6adc8cc3ac..bda9830ca1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: - id: pretty-format-json args: ['--autofix'] - - repo: https://github.com/koalaman/shellcheck-precommit - rev: v0.8.0 - hooks: - - id: shellcheck + # - repo: https://github.com/koalaman/shellcheck-precommit + # rev: v0.8.0 + # hooks: + # - id: shellcheck diff --git a/groups/group10_hipaa b/groups/group10_hipaa index b38aa23c1d..000a537730 100644 --- a/groups/group10_hipaa +++ b/groups/group10_hipaa @@ -14,7 +14,6 @@ GROUP_ID[10]='hipaa' GROUP_NUMBER[10]='10.0' GROUP_TITLE[10]='HIPAA Compliance - ONLY AS REFERENCE - [hipaa] ****************' -GROUP_RUN_BY_DEFAULT[10]='N' # run it when execute_all is called GROUP_CHECKS[10]='check12,check113,check23,check26,check27,check29,extra718,extra725,extra72,extra75,extra717,extra729,extra734,check38,extra73,extra740,extra735,check112,check13,check15,check16,check17,check18,check19,check21,check24,check28,check31,check310,check311,check312,check313,check314,check32,check33,check34,check35,check36,check37,check39,extra792' # Resources: diff --git a/groups/group11_secrets b/groups/group11_secrets index 76d4605611..297a719880 100644 --- a/groups/group11_secrets +++ b/groups/group11_secrets @@ -14,9 +14,7 @@ GROUP_ID[11]='secrets' GROUP_NUMBER[11]='11.0' GROUP_TITLE[11]='Look for keys secrets or passwords around resources - [secrets]' -GROUP_RUN_BY_DEFAULT[11]='N' # but it runs when execute_all is called (default) GROUP_CHECKS[11]='extra741,extra742,extra759,extra760,extra768,extra775,extra7141' # requires https://github.com/Yelp/detect-secrets -# `pip install detect-secrets` - +# `pip install detect-secrets` diff --git a/groups/group12_apigateway b/groups/group12_apigateway index fbecc23cb8..a9556adef0 100644 --- a/groups/group12_apigateway +++ b/groups/group12_apigateway @@ -14,6 +14,4 @@ GROUP_ID[12]='apigateway' GROUP_NUMBER[12]='12.0' GROUP_TITLE[12]='API Gateway security checks - [apigateway] ********************' -GROUP_RUN_BY_DEFAULT[12]='N' # run it when execute_all is called GROUP_CHECKS[12]='extra722,extra743,extra744,extra745,extra746' - diff --git a/groups/group13_rds b/groups/group13_rds index d3aad2040b..515f3c032f 100644 --- a/groups/group13_rds +++ b/groups/group13_rds @@ -14,5 +14,4 @@ GROUP_ID[13]='rds' GROUP_NUMBER[13]='13.0' GROUP_TITLE[13]='RDS security checks - [rds] ***********************************' -GROUP_RUN_BY_DEFAULT[13]='N' # run it when execute_all is called GROUP_CHECKS[13]='extra78,extra723,extra735,extra739,extra747,extra7113,extra7131,extra7132,extra7133' diff --git a/groups/group14_elasticsearch b/groups/group14_elasticsearch index b4cf9b744a..d8ac43af6d 100644 --- a/groups/group14_elasticsearch +++ b/groups/group14_elasticsearch @@ -14,5 +14,4 @@ GROUP_ID[14]='elasticsearch' GROUP_NUMBER[14]='14.0' GROUP_TITLE[14]='Elasticsearch related security checks - [elasticsearch] *******' -GROUP_RUN_BY_DEFAULT[14]='N' # run it when execute_all is called -GROUP_CHECKS[14]='extra715,extra716,extra779,extra780,extra781,extra782,extra783,extra784,extra785,extra787,extra788,extra7101' \ No newline at end of file +GROUP_CHECKS[14]='extra715,extra716,extra779,extra780,extra781,extra782,extra783,extra784,extra785,extra787,extra788,extra7101' diff --git a/groups/group15_pci b/groups/group15_pci index 82f64bb2dc..6c0562f2fd 100644 --- a/groups/group15_pci +++ b/groups/group15_pci @@ -14,7 +14,6 @@ GROUP_ID[15]='pci' GROUP_NUMBER[15]='15.0' GROUP_TITLE[15]='PCI-DSS v3.2.1 Readiness - ONLY AS REFERENCE - [pci] **********' -GROUP_RUN_BY_DEFAULT[15]='N' # run it when execute_all is called GROUP_CHECKS[15]='check11,check12,check13,check14,check15,check16,check17,check18,check19,check110,check112,check113,check114,check116,check21,check23,check25,check26,check27,check28,check29,check314,check36,check38,check43,extra711,extra713,extra717,extra718,extra72,extra729,extra735,extra738,extra740,extra744,extra748,extra75,extra750,extra751,extra753,extra754,extra755,extra773,extra78,extra780,extra781,extra782,extra783,extra784,extra785,extra787,extra788,extra798' # Resources: diff --git a/groups/group16_trustboundaries b/groups/group16_trustboundaries index 93aa07baea..9e8e91d765 100644 --- a/groups/group16_trustboundaries +++ b/groups/group16_trustboundaries @@ -14,7 +14,6 @@ GROUP_ID[16]='trustboundaries' GROUP_NUMBER[16]='16.0' GROUP_TITLE[16]='Find cross-account trust boundaries - [trustboundaries] *******' -GROUP_RUN_BY_DEFAULT[16]='N' # run it when execute_all is called GROUP_CHECKS[16]='extra789,extra790' # Single account environment: No action required. The AWS account number will be automatically added by the checks. diff --git a/groups/group17_internetexposed b/groups/group17_internetexposed index bb482a0f16..a9e1cada2f 100644 --- a/groups/group17_internetexposed +++ b/groups/group17_internetexposed @@ -14,7 +14,6 @@ GROUP_ID[17]='internet-exposed' GROUP_NUMBER[17]='17.0' GROUP_TITLE[17]='Find resources exposed to the internet - [internet-exposed] ***' -GROUP_RUN_BY_DEFAULT[17]='N' # run it when execute_all is called GROUP_CHECKS[17]='check41,check42,check45,check46,extra72,extra73,extra74,extra76,extra77,extra78,extra79,extra710,extra711,extra716,extra723,extra727,extra731,extra736,extra738,extra745,extra748,extra749,extra750,extra751,extra752,extra753,extra754,extra755,extra770,extra771,extra778,extra779,extra787,extra788,extra795,extra796,extra798,extra7102,extra7134,extra7135,extra7136,extra7137,extra7138' # 4.1 [check41] Ensure no security groups allow ingress from 0.0.0.0/0 or ::/0 to port 22 (Scored) [group4, cislevel1, cislevel2] diff --git a/groups/group18_iso27001 b/groups/group18_iso27001 index fe8ba0eaf6..602b311d97 100644 --- a/groups/group18_iso27001 +++ b/groups/group18_iso27001 @@ -14,7 +14,6 @@ GROUP_ID[18]='iso27001' GROUP_NUMBER[18]='18.0' GROUP_TITLE[18]='ISO 27001:2013 Readiness - ONLY AS REFERENCE - [iso27001] *****' -GROUP_RUN_BY_DEFAULT[18]='N' # run it when execute_all is called GROUP_CHECKS[18]='check11,check110,check111,check112,check113,check114,check115,check116,check119,check12,check122,check13,check14,check15,check16,check17,check18,check19,check21,check22,check23,check24,check25,check26,check27,check28,check29,check31,check310,check311,check312,check313,check314,check32,check33,check34,check35,check36,check37,check38,check39,check41,check42,check43,check44,extra71,extra710,extra7100,extra711,extra7113,extra7123,extra7125,extra7126,extra7128,extra7129,extra713,extra714,extra7130,extra718,extra719,extra72,extra720,extra721,extra722,extra723,extra724,extra725,extra726,extra727,extra728,extra729,extra731,extra73,extra731,extra735,extra739,extra74,extra741,extra747,extra748,extra75,extra757,extra758,extra759,extra76,extra760,extra761,extra762,extra763,extra764,extra765,extra767,extra768,extra769,extra77,extra771,extra772,extra774,extra776,extra777,extra778,extra78,extra789,extra79,extra790,extra792,extra793,extra794,extra795,extra796,extra798' # # Category Objective ID Objective Name Prowler check ID Check Summary @@ -103,7 +102,7 @@ GROUP_CHECKS[18]='check11,check110,check111,check112,check113,check114,check115, # 83 A.12 Operations Security A.12.4 Logging and Monitoring check39 Ensure a log metric filter and alarm exist for AWS Config configuration changes # 84 A.12 Operations Security A.12.4 Logging and Monitoring check39 Check if CloudFront distributions have logging enabled # 85 A.12 Operations Security A.12.4 Logging and Monitoring extra719 Check if Route53 public hosted zones are logging queries to CloudWatch Logs -# 86 A.12 Operations Security A.12.4 Logging and Monitoring extra720 Check if Lambda functions invoke API operations are being recorded by CloudTrail +# 86 A.12 Operations Security A.12.4 Logging and Monitoring extra720 Check if Lambda functions invoke API operations are being recorded by CloudTrail # 87 A.12 Operations Security A.12.4 Logging and Monitoring extra722 Check if API Gateway has logging enabled # 88 A.12 Operations Security A.12.4 Logging and Monitoring check38 Ensure a log metric filter and alarm exist for S3 bucket policy changes # 89 A.12 Operations Security A.12.4 Logging and Monitoring check37 Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs @@ -136,7 +135,7 @@ GROUP_CHECKS[18]='check11,check110,check111,check112,check113,check114,check115, #116 A.12 Operations Security A.12.6 Technical Vulnerability Management check23 Ensure the S3 bucket CloudTrail logs to is not publicly accessible #117 A.12 Operations Security A.12.6 Technical Vulnerability Management extra713 Check if GuardDuty is enabled #118 A.12 Operations Security A.12.6 Technical Vulnerability Management extra726 Check Trusted Advisor for errors and warnings -#119 A.12 Operations Security A.12.6 Technical Vulnerability Management extra776 Check if ECR image scan found vulnerabilities in the newest image version +#119 A.12 Operations Security A.12.6 Technical Vulnerability Management extra776 Check if ECR image scan found vulnerabilities in the newest image version #120 A.13 Communications Security A.13.1 Network Security Management check43 Ensure the default security group of every VPC restricts all traffic #121 A.13 Communications Security A.13.1 Network Security Management check42 Ensure no security groups allow ingress from 0.0.0.0/0 to port 3389 #122 A.13 Communications Security A.13.1 Network Security Management check41 Ensure no security groups allow ingress from 0.0.0.0/0 to port 22 diff --git a/groups/group19_eks-cis b/groups/group19_eks-cis index 8c31932b0a..6ca3522d1b 100644 --- a/groups/group19_eks-cis +++ b/groups/group19_eks-cis @@ -11,5 +11,4 @@ GROUP_ID[19]='eks-cis' GROUP_NUMBER[19]='19.0' GROUP_TITLE[19]='CIS EKS Benchmark - [eks-cis] *********************************' -GROUP_RUN_BY_DEFAULT[19]='N' # run it when execute_all is called GROUP_CHECKS[19]='extra765,extra794,extra795,extra796,extra797' diff --git a/groups/group1_iam b/groups/group1_iam index 86da563c6b..ca0e66ef64 100644 --- a/groups/group1_iam +++ b/groups/group1_iam @@ -11,5 +11,4 @@ GROUP_ID[1]='group1' GROUP_NUMBER[1]='1.0' GROUP_TITLE[1]='Identity and Access Management - CIS only - [group1] ***********' -GROUP_RUN_BY_DEFAULT[1]='Y' # run it when execute_all is called GROUP_CHECKS[1]='check11,check12,check13,check14,check15,check16,check17,check18,check19,check110,check111,check112,check113,check114,check115,check116,check117,check118,check119,check120,check121,check122' diff --git a/groups/group20_ffiec b/groups/group20_ffiec index 03b35c6f62..e24515ec1a 100644 --- a/groups/group20_ffiec +++ b/groups/group20_ffiec @@ -14,7 +14,6 @@ GROUP_ID[20]='ffiec' GROUP_NUMBER[20]='20.0' GROUP_TITLE[20]='FFIEC Cybersecurity Readiness - ONLY AS REFERENCE - [ffiec] ***' -GROUP_RUN_BY_DEFAULT[20]='N' # run it when execute_all is called GROUP_CHECKS[20]='check11,check12,check13,check14,check16,check18,check19,check21,check23,check25,check29,check29,check31,check32,check33,check34,check35,check36,check37,check37,check38,check39,check41,check42,check43,check110,check112,check113,check116,check310,check311,check312,check313,check314,extra72,extra76,extra78,extra711,extra723,extra729,extra731,extra734,extra735,extra763,extra792' # References: diff --git a/groups/group21_soc2 b/groups/group21_soc2 index 84f2cac3a7..0f75534efb 100644 --- a/groups/group21_soc2 +++ b/groups/group21_soc2 @@ -14,7 +14,6 @@ GROUP_ID[21]='soc2' GROUP_NUMBER[21]='21.0' GROUP_TITLE[21]='SOC2 Readiness - ONLY AS REFERENCE - [soc2] *******************' -GROUP_RUN_BY_DEFAULT[21]='N' # run it when execute_all is called GROUP_CHECKS[21]='check110,check111,check113,check12,check122,check13,check15,check16,check17,check18,check19,check21,check31,check310,check32,check33,check34,check35,check36,check37,check38,check39,check41,check42,check43,extra711,extra72,extra723,extra729,extra731,extra734,extra735,extra739,extra76,extra78,extra792' # References: diff --git a/groups/group22_sagemaker b/groups/group22_sagemaker index 7a4d62abe1..5523e44dc6 100644 --- a/groups/group22_sagemaker +++ b/groups/group22_sagemaker @@ -14,6 +14,4 @@ GROUP_ID[22]='sagemaker' GROUP_NUMBER[22]='22.0' GROUP_TITLE[22]='Amazon SageMaker related security checks - [sagemaker] ********' -GROUP_RUN_BY_DEFAULT[22]='N' # run it when execute_all is called GROUP_CHECKS[22]='extra7103,extra7104,extra7111,extra7112,extra7105,extra7106,extra7107,extra7108,extra7109,extra7110' - diff --git a/groups/group23_ens b/groups/group23_ens index 098ddc61a8..341b9759eb 100644 --- a/groups/group23_ens +++ b/groups/group23_ens @@ -14,7 +14,6 @@ GROUP_ID[23]='ens' GROUP_NUMBER[23]='23.0' GROUP_TITLE[23]='ENS Esquema Nacional de Seguridad security checks - [ens] *****' -GROUP_RUN_BY_DEFAULT[23]='N' # run it when execute_all is called GROUP_CHECKS[23]='extra733,extra7123,check13,check14,check121,extra7100,check120,check116,extra7124,check12,extra7125,check14,check13,check21,check25,extra7127,check35,check24,check31,check36,check32,check33,check34,check22,extra71,check23,check23,check27,check37,extra736,check28,extra713,check21,check29,extra793,extra792,extra764,extra738,check43,extra74,extra710,extra75,check41,check42,extra749,extra750,extra751,extra752,extra753,extra754,extra755,extra7128,extra729,extra761,extra740,extra735,extra734,extra728,extra781,extra773,extra744,extra7126,extra7129' # ENS Control ID for AWS;Prowler checks that apply diff --git a/groups/group24_glue b/groups/group24_glue index a9b8c31329..8adeabfb9a 100644 --- a/groups/group24_glue +++ b/groups/group24_glue @@ -14,5 +14,4 @@ GROUP_ID[24]='glue' GROUP_NUMBER[24]='24.0' GROUP_TITLE[24]='Amazon Glue related security checks - [glue] ******************' -GROUP_RUN_BY_DEFAULT[24]='N' # run it when execute_all is called -GROUP_CHECKS[24]='extra7114,extra7115,extra7116,extra7117,extra7118,extra7119,extra7120,extra7121,extra7122' \ No newline at end of file +GROUP_CHECKS[24]='extra7114,extra7115,extra7116,extra7117,extra7118,extra7119,extra7120,extra7121,extra7122' diff --git a/groups/group25_FTR b/groups/group25_FTR index 501597a355..e7ccf3d147 100644 --- a/groups/group25_FTR +++ b/groups/group25_FTR @@ -14,7 +14,6 @@ GROUP_ID[25]='ftr' GROUP_NUMBER[25]='25.0' GROUP_TITLE[25]='Amazon FTR related security checks - [ftr] ********************' -GROUP_RUN_BY_DEFAULT[25]='N' # run it when execute_all is called GROUP_CHECKS[25]='check11,check12,check13,check14,check15,check16,check17,check18,check19,check110,check111,check111,check112,check113,check117,check118,check122,check21,check22,extra759,extra760,extra768,extra775,extra797,extra7141,extra73' # Checks from AWS FTR https://apn-checklists.s3.amazonaws.com/foundational/partner-hosted/partner-hosted/CVLHEC5X7.html @@ -43,4 +42,3 @@ GROUP_CHECKS[25]='check11,check12,check13,check14,check15,check16,check17,check1 # 7.97 [extra797] Ensure Kubernetes Secrets are encrypted using Customer Master Keys (CMKs) - eks [Medium] # 7.141 [extra7141] Find secrets in SSM Documents - ssm [Critical] # 7.3 [extra73] Ensure there are no S3 buckets open to Everyone or Any AWS user - s3 [Critical] - diff --git a/groups/group26_ds b/groups/group26_ds index c77ee2af80..dc1ac8d88c 100644 --- a/groups/group26_ds +++ b/groups/group26_ds @@ -13,6 +13,5 @@ GROUP_ID[26]='ds' GROUP_NUMBER[26]='26.0' -GROUP_TITLE[26]='Amazon Directory Service related security checks - [ds] ******************' -GROUP_RUN_BY_DEFAULT[26]='N' # run it when execute_all is called -GROUP_CHECKS[26]='extra7181,extra7182,extra7183,extra7184' \ No newline at end of file +GROUP_TITLE[26]='Amazon Directory Service related security checks - [ds] *******' +GROUP_CHECKS[26]='extra7181,extra7182,extra7183,extra7184' diff --git a/groups/group2_logging b/groups/group2_logging index a9d4db0b3d..5482bb6011 100644 --- a/groups/group2_logging +++ b/groups/group2_logging @@ -11,5 +11,4 @@ GROUP_ID[2]='group2' GROUP_NUMBER[2]='2.0' GROUP_TITLE[2]='Logging - CIS only - [group2] **********************************' -GROUP_RUN_BY_DEFAULT[2]='Y' # run it when execute_all is called GROUP_CHECKS[2]='check21,check22,check23,check24,check25,check26,check27,check28,check29' diff --git a/groups/group3_monitoring b/groups/group3_monitoring index 058939a1a8..33ead9ca2b 100644 --- a/groups/group3_monitoring +++ b/groups/group3_monitoring @@ -11,5 +11,4 @@ GROUP_ID[3]='group3' GROUP_NUMBER[3]='3.0' GROUP_TITLE[3]='Monitoring - CIS only - [group3] *******************************' -GROUP_RUN_BY_DEFAULT[3]='Y' # run it when execute_all is called GROUP_CHECKS[3]='check31,check32,check33,check34,check35,check36,check37,check38,check39,check310,check311,check312,check313,check314' diff --git a/groups/group4_networking b/groups/group4_networking index ca1244658a..7efcbc87d1 100644 --- a/groups/group4_networking +++ b/groups/group4_networking @@ -11,5 +11,4 @@ GROUP_ID[4]='group4' GROUP_NUMBER[4]='4.0' GROUP_TITLE[4]='Networking - CIS only - [group4] *******************************' -GROUP_RUN_BY_DEFAULT[4]='Y' # run it when execute_all is called GROUP_CHECKS[4]='check41,check42,check43,check44,check45,check46' diff --git a/groups/group5_cislevel1 b/groups/group5_cislevel1 index 3fe3a084a9..1cd05337af 100644 --- a/groups/group5_cislevel1 +++ b/groups/group5_cislevel1 @@ -11,5 +11,4 @@ GROUP_ID[5]='cislevel1' GROUP_NUMBER[5]='5.0' GROUP_TITLE[5]='CIS Level 1 - CIS only - [cislevel1] ***************************' -GROUP_RUN_BY_DEFAULT[5]='N' # run it when execute_all is called GROUP_CHECKS[5]='check11,check12,check13,check14,check15,check16,check17,check18,check19,check110,check111,check112,check113,check115,check116,check117,check118,check119,check120,check122,check21,check23,check24,check25,check26,check31,check32,check33,check34,check35,check38,check312,check313,check314,check41,check42' diff --git a/groups/group6_cislevel2 b/groups/group6_cislevel2 index 67fdc1e141..337d38a7d1 100644 --- a/groups/group6_cislevel2 +++ b/groups/group6_cislevel2 @@ -11,5 +11,4 @@ GROUP_ID[6]='cislevel2' GROUP_NUMBER[6]='6.0' GROUP_TITLE[6]='CIS Level 2 - CIS only - [cislevel2] ***************************' -GROUP_RUN_BY_DEFAULT[6]='N' # run it when execute_all is called GROUP_CHECKS[6]='check11,check12,check13,check14,check15,check16,check17,check18,check19,check110,check111,check112,check113,check114,check115,check116,check117,check118,check119,check120,check121,check122,check21,check22,check23,check24,check25,check26,check27,check28,check29,check31,check32,check33,check34,check35,check36,check37,check38,check39,check310,check311,check312,check313,check314,check41,check42,check43,check44' diff --git a/groups/group7_extras b/groups/group7_extras index b85f20d33f..75a5cda776 100644 --- a/groups/group7_extras +++ b/groups/group7_extras @@ -14,8 +14,7 @@ GROUP_ID[7]='extras' GROUP_NUMBER[7]='7.0' GROUP_TITLE[7]='Extras - all non CIS specific checks - [extras] ****************' -GROUP_RUN_BY_DEFAULT[7]='Y' # run it when execute_all is called -GROUP_CHECKS[7]='extra71,extra72,extra73,extra74,extra75,extra76,extra77,extra78,extra79,extra710,extra711,extra712,extra713,extra714,extra715,extra716,extra717,extra718,extra719,extra720,extra721,extra722,extra723,extra724,extra725,extra726,extra727,extra728,extra729,extra730,extra731,extra732,extra733,extra734,extra735,extra736,extra738,extra739,extra740,extra741,extra742,extra743,extra744,extra745,extra746,extra747,extra748,extra749,extra750,extra751,extra752,extra753,extra754,extra755,extra757,extra758,extra761,extra762,extra763,extra764,extra765,extra767,extra768,extra769,extra770,extra771,extra772,extra773,extra774,extra775,extra776,extra777,extra778,extra779,extra780,extra781,extra782,extra783,extra784,extra785,extra786,extra787,extra788,extra791,extra792,extra793,extra794,extra795,extra796,extra797,extra798,extra799,extra7100,extra7101,extra7102,extra7103,extra7104,extra7105,extra7106,extra7107,extra7108,extra7109,extra7110,extra7111,extra7112,extra7113,extra7114,extra7115,extra7116,extra7117,extra7118,extra7119,extra7120,extra7121,extra7122,extra7123,extra7124,extra7125,extra7126,extra7127,extra7128,extra7129,extra7130,extra7131,extra7132,extra7133,extra7134,extra7135,extra7136,extra7137,extra7138,extra7139,extra7140,extra7141,extra7142,extra7143,extra7144,extra7145,extra7146,extra7147,extra7148,extra7149,extra7150,extra7151,extra7152,extra7153,extra7154,extra7155,extra7156,extra7157,extra7158,extra7159,extra7160,extra7161,extra7162,extra7163,extra7164,extra7165,extra7166,extra7167,extra7168,extra7169,extra7170,extra7171,extra7172,extra7173,extra7174,extra7175,extra7176,extra7177,extra7178,extra7179,extra7180,extra7181,extra7182,extra7183,extra7184,extra7186,extra7187' +GROUP_CHECKS[7]='extra71,extra72,extra73,extra74,extra75,extra76,extra77,extra78,extra79,extra710,extra711,extra712,extra713,extra714,extra715,extra716,extra717,extra718,extra719,extra720,extra721,extra722,extra723,extra724,extra725,extra726,extra727,extra728,extra729,extra730,extra731,extra732,extra733,extra734,extra735,extra736,extra738,extra739,extra740,extra741,extra742,extra743,extra744,extra745,extra746,extra747,extra748,extra749,extra750,extra751,extra752,extra753,extra754,extra755,extra757,extra758,extra761,extra762,extra763,extra764,extra765,extra767,extra768,extra769,extra770,extra771,extra772,extra773,extra774,extra775,extra776,extra777,extra778,extra779,extra780,extra781,extra782,extra783,extra784,extra785,extra786,extra787,extra788,extra791,extra792,extra793,extra794,extra795,extra796,extra797,extra798,extra799,extra7100,extra7101,extra7102,extra7103,extra7104,extra7105,extra7106,extra7107,extra7108,extra7109,extra7110,extra7111,extra7112,extra7113,extra7114,extra7115,extra7116,extra7117,extra7118,extra7119,extra7120,extra7121,extra7122,extra7123,extra7124,extra7125,extra7126,extra7127,extra7128,extra7129,extra7130,extra7131,extra7132,extra7133,extra7134,extra7135,extra7136,extra7137,extra7138,extra7139,extra7140,extra7141,extra7142,extra7143,extra7144,extra7145,extra7146,extra7147,extra7148,extra7149,extra7150,extra7151,extra7152,extra7153,extra7154,extra7155,extra7156,extra7157,extra7158,extra7159,extra7160,extra7161,extra7162,extra7163,extra7164,extra7165,extra7166,extra7167,extra7168,extra7169,extra7170,extra7171,extra7172,extra7173,extra7174,extra7175,extra7176,extra7177,extra7178,extra7179,extra7180,extra7181,extra7182,extra7183,extra7184,extra7185,extra7186,extra7187' # Extras 759 and 760 (lambda variables and code secrets finder are not included) # to run detect-secrets use `./prowler -g secrets` diff --git a/groups/group8_forensics b/groups/group8_forensics index 3e035c89b3..29f12ff156 100644 --- a/groups/group8_forensics +++ b/groups/group8_forensics @@ -14,5 +14,4 @@ GROUP_ID[8]='forensics-ready' GROUP_NUMBER[8]='8.0' GROUP_TITLE[8]='Forensics Readiness - [forensics-ready] ************************' -GROUP_RUN_BY_DEFAULT[8]='N' # run it when execute_all is called GROUP_CHECKS[8]='check21,check22,check23,check24,check25,check26,check27,check29,extra712,extra713,extra714,extra715,extra717,extra718,extra719,extra720,extra721,extra722,extra725,extra7101,extra794' diff --git a/groups/group9_gdpr b/groups/group9_gdpr index cef487f1f2..e258341eb9 100644 --- a/groups/group9_gdpr +++ b/groups/group9_gdpr @@ -14,7 +14,6 @@ GROUP_ID[9]='gdpr' GROUP_NUMBER[9]='9.0' GROUP_TITLE[9]='GDPR Readiness - ONLY AS REFERENCE - [gdpr] ********************' -GROUP_RUN_BY_DEFAULT[9]='N' # run it when execute_all is called GROUP_CHECKS[9]='extra718,extra725,extra727,check12,check113,check114,extra71,extra731,extra732,extra733,check25,check39,check21,check22,check23,check24,check26,check27,check35,extra726,extra714,extra715,extra717,extra719,extra720,extra721,extra722,check43,check25,extra714,extra729,extra734,extra735,extra736,extra738,extra740,extra761,check11,check110,check111,check112,check116,check120,check122,check13,check14,check15,check16,check17,check18,check19,check28,check29,check31,check310,check311,check312,check313,check314,check32,check33,check34,check36,check37,check38,check41,check42,extra711,extra72,extra723,extra730,extra739,extra76,extra763,extra778,extra78,extra792,extra798' # Resources: diff --git a/groups/groupN_sample b/groups/groupN_sample index ceff97025c..2abc0d9032 100644 --- a/groups/groupN_sample +++ b/groups/groupN_sample @@ -14,5 +14,4 @@ GROUP_ID[9]='my-custom-group' GROUP_NUMBER[9]='9.0' GROUP_TITLE[9]='My Custom Group - [my-custom-group] ****************************' -GROUP_RUN_BY_DEFAULT[9]='N' # run it when execute_all is called GROUP_CHECKS[9]='checkNN,checkMM' diff --git a/include/aws_profile_loader b/include/aws_profile_loader index e79cea87c0..656fd09b27 100644 --- a/include/aws_profile_loader +++ b/include/aws_profile_loader @@ -16,43 +16,60 @@ # check environment variables and if not, it checks and loads credentials from # instance profile (metadata server) if runs in an EC2 instance -INSTANCE_PROFILE=$(curl -s -m 1 http://169.254.169.254/latest/meta-data/iam/security-credentials/) -if echo "$INSTANCE_PROFILE" | grep -q '404 - Not Found'; then - INSTANCE_PROFILE= -fi +aws_profile_loader() { + INSTANCE_PROFILE=$(curl -s -m 1 http://169.254.169.254/latest/meta-data/iam/security-credentials/) + if echo "$INSTANCE_PROFILE" | grep -q '404 - Not Found'; then + INSTANCE_PROFILE= + fi -if [[ $PROFILE ]]; then - PROFILE_OPT="--profile $PROFILE" -elif [[ $AWS_ACCESS_KEY_ID && $AWS_SECRET_ACCESS_KEY || $AWS_SESSION_TOKEN || $AWS_PROFILE ]];then - PROFILE="$AWS_PROFILE" - PROFILE_OPT="" -elif [[ -n $AWS_CONTAINER_CREDENTIALS_RELATIVE_URI ]] && [[ -z $INSTANCE_PROFILE ]]; then - PROFILE="INSTANCE-PROFILE" - AWS_ACCESS_KEY_ID=$(curl -s 169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI | grep AccessKeyId | cut -d':' -f2 | sed 's/[^0-9A-Z]*//g') - AWS_SECRET_ACCESS_KEY_ID=$(curl -s 169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI | grep SecretAccessKey | cut -d':' -f2 | sed 's/[^0-9A-Za-z/+=]*//g') - AWS_SESSION_TOKEN=$(curl -s 169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI grep Token| cut -d':' -f2 | sed 's/[^0-9A-Za-z/+=]*//g') -elif [[ $AWS_WEB_IDENTITY_TOKEN_FILE ]]; then - PROFILE="" - PROFILE_OPT="" -elif [[ $INSTANCE_PROFILE ]]; then - PROFILE="INSTANCE-PROFILE" - AWS_ACCESS_KEY_ID=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/${INSTANCE_PROFILE} | grep AccessKeyId | cut -d':' -f2 | sed 's/[^0-9A-Z]*//g') - AWS_SECRET_ACCESS_KEY_ID=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/${INSTANCE_PROFILE} | grep SecretAccessKey | cut -d':' -f2 | sed 's/[^0-9A-Za-z/+=]*//g') - AWS_SESSION_TOKEN=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/${INSTANCE_PROFILE} grep Token| cut -d':' -f2 | sed 's/[^0-9A-Za-z/+=]*//g') -elif [[ $AWS_EXECUTION_ENV == "CloudShell" ]]; then - PROFILE_OPT="" -else - PROFILE="default" + if [[ $PROFILE ]]; then PROFILE_OPT="--profile $PROFILE" -fi -# Backing up $PROFILE_OPT needed to renew assume_role -PROFILE_OPT_BAK=$PROFILE_OPT -# Set default region by aws config, fall back to us-east-1 -REGION_CONFIG=$(aws configure get region) -if [[ $REGION_OPT ]]; then - REGION="$REGION_OPT" -elif [[ $REGION_CONFIG ]]; then - REGION="$REGION_CONFIG" -else - REGION="us-east-1" -fi + elif [[ $AWS_ACCESS_KEY_ID && $AWS_SECRET_ACCESS_KEY || $AWS_SESSION_TOKEN || $AWS_PROFILE ]];then + PROFILE="$AWS_PROFILE" + PROFILE_OPT="" + elif [[ -n $AWS_CONTAINER_CREDENTIALS_RELATIVE_URI ]] && [[ -z $INSTANCE_PROFILE ]]; then + PROFILE="INSTANCE-PROFILE" + AWS_ACCESS_KEY_ID=$(curl -s 169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI | grep AccessKeyId | cut -d':' -f2 | sed 's/[^0-9A-Z]*//g') + AWS_SECRET_ACCESS_KEY_ID=$(curl -s 169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI | grep SecretAccessKey | cut -d':' -f2 | sed 's/[^0-9A-Za-z/+=]*//g') + AWS_SESSION_TOKEN=$(curl -s 169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI grep Token| cut -d':' -f2 | sed 's/[^0-9A-Za-z/+=]*//g') + elif [[ $AWS_WEB_IDENTITY_TOKEN_FILE ]]; then + PROFILE="" + PROFILE_OPT="" + elif [[ $INSTANCE_PROFILE ]]; then + PROFILE="INSTANCE-PROFILE" + AWS_ACCESS_KEY_ID=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/${INSTANCE_PROFILE} | grep AccessKeyId | cut -d':' -f2 | sed 's/[^0-9A-Z]*//g') + AWS_SECRET_ACCESS_KEY_ID=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/${INSTANCE_PROFILE} | grep SecretAccessKey | cut -d':' -f2 | sed 's/[^0-9A-Za-z/+=]*//g') + AWS_SESSION_TOKEN=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/${INSTANCE_PROFILE} grep Token| cut -d':' -f2 | sed 's/[^0-9A-Za-z/+=]*//g') + elif [[ $AWS_EXECUTION_ENV == "CloudShell" ]]; then + PROFILE_OPT="" + else + PROFILE="default" + PROFILE_OPT="--profile $PROFILE" + fi + # Backing up $PROFILE_OPT needed to renew assume_role + PROFILE_OPT_BAK=$PROFILE_OPT + # Set default region by aws config, fall back to us-east-1 + REGION_CONFIG=$(aws configure get region) + if [[ $REGION_OPT ]]; then + REGION="$REGION_OPT" + elif [[ $REGION_CONFIG ]]; then + REGION="$REGION_CONFIG" + else + REGION="us-east-1" + fi +} + +# Function to get all regions +get_regions() { + # Get list of regions based on include/whoami + if ! REGIONS=$($AWSCLI ec2 describe-regions \ + --query 'Regions[].RegionName' \ + --output text ${PROFILE_OPT} \ + --region "${REGION_FOR_STS}" \ + --region-names ${FILTERREGION//[,]/ } 2>&1) + then + echo "$OPTRED Access Denied trying to describe regions! Review permissions as described here: https://github.com/prowler-cloud/prowler/#requirements-and-installation $OPTNORMAL" + EXITCODE=1 + exit $EXITCODE + fi +} diff --git a/include/awscli_detector b/include/awscli_detector index 6a1fd6b146..9be126aaa2 100644 --- a/include/awscli_detector +++ b/include/awscli_detector @@ -12,12 +12,25 @@ # specific language governing permissions and limitations under the License. # AWS-CLI detector variable -if [ ! -z $(which aws) ]; then - AWSCLI=$(which aws) -elif [ ! -z $(type -p aws) ]; then - AWSCLI=$(type -p aws) -else - echo -e "\n$RED ERROR!$NORMAL AWS-CLI (aws command) not found. Make sure it is installed correctly and in your \$PATH\n" - EXITCODE=1 - exit $EXITCODE -fi +aws_cli_detector() { + if [ ! -z $(which aws) ]; then + AWSCLI=$(which aws) + elif [ ! -z $(type -p aws) ]; then + AWSCLI=$(type -p aws) + else + echo -e "\n$RED ERROR!$NORMAL AWS-CLI (aws command) not found. Make sure it is installed correctly and in your \$PATH\n" + EXITCODE=1 + exit $EXITCODE + fi +} + +set_aws_default_output() { + # Ensures command output will always be set to JSON. + # If the default value is already set, ORIGINAL_OUTPUT will be used to store it and reset it at cleanup + if [[ -z "${AWS_DEFAULT_OUTPUT}" ]]; then + ORIGINAL_OUTPUT=$AWS_DEFAULT_OUTPUT + export AWS_DEFAULT_OUTPUT="json" + else + export AWS_DEFAULT_OUTPUT="json" + fi +} diff --git a/include/banner b/include/banner index f28c85f2b5..a9bf4dcdf4 100644 --- a/include/banner +++ b/include/banner @@ -19,7 +19,7 @@ prowlerBanner() { echo -e " | |_) | | | (_) \ V V /| | __/ |" echo -e " | .__/|_| \___/ \_/\_/ |_|\___|_|v$PROWLER_VERSION" echo -e " |_|$NORMAL$BLUE the handy cloud security tool$NORMAL\n" - echo -e "$YELLOW Date: $(date)" + echo -e "$YELLOW Date: $(date)$NORMAL" printColorsCode fi } diff --git a/include/colors b/include/colors index 637bfd8684..d82145b007 100644 --- a/include/colors +++ b/include/colors @@ -11,23 +11,40 @@ # CONDITIONS OF ANY KIND, either express or implied. See the License for the # specific language governing permissions and limitations under the License. - # Colors # NOTE: Your editor may NOT show the 0x1b / escape character left of the '[' -NORMAL="" -WARNING="" # Warning (brown) -NOTICE="" # Notice (yellow) -OK="" # Ok (green) -BAD="" # Bad (red) -CYAN="" -BLUE="" -BROWN="" -MAGENTA="" -RED="" -YELLOW="" +set_colors() { + if [[ "${MODE}" =~ "mono" ]] + then + NORMAL="" + WARNING="" + NOTICE="" + OK="" + BAD="" + CYAN="" + BLUE="" + BROWN="" + MAGENTA="" + RED="" + YELLOW="" + else + NORMAL="" + WARNING="" # Warning (brown) + NOTICE="" # Notice (yellow) + OK="" # Ok (green) + BAD="" # Bad (red) + CYAN="" + BLUE="" + BROWN="" + MAGENTA="" + RED="" + YELLOW="" + fi +} printColorsCode(){ - if [[ $MONOCHROME -eq 0 ]]; then + if [[ ! "$MODE" =~ "mono" ]] + then echo -e "\n$NORMAL Color code for results: " echo -e " - $NOTICE INFO (Information)$NORMAL" echo -e " - $OK PASS (Recommended value)$NORMAL" diff --git a/include/credentials_report b/include/credentials_report index 51f145f982..9b6398c09b 100644 --- a/include/credentials_report +++ b/include/credentials_report @@ -37,14 +37,3 @@ saveReport(){ textInfo "IAM Credential Report saved in $TEMP_REPORT_FILE" fi } - -# Delete temporary report file -cleanTemp(){ - if [[ $KEEPCREDREPORT -ne 1 ]]; then - rm -fr $TEMP_REPORT_FILE - fi - cleanSTSAssumeFile -} - -# Delete the temporary report file if we get interrupted/terminated -trap cleanTemp EXIT diff --git a/include/custom_checks b/include/custom_checks index adaaade9c1..30a274a71b 100644 --- a/include/custom_checks +++ b/include/custom_checks @@ -14,39 +14,39 @@ custom_checks(){ # check if the path is an S3 URI if grep -q -E "^s3://([^/]+)/?(.*?([^/]+)/?)?$" <<< "$EXTERNAL_CHECKS_PATH"; then - if grep -q "check*" <<< "$("${AWSCLI}" s3 ls "${EXTERNAL_CHECKS_PATH}" $PROFILE_OPT)"; then + if grep -q "check*" <<< "$("${AWSCLI}" s3 ls "${EXTERNAL_CHECKS_PATH}" "${PROFILE_OPT}")"; then # download s3 object - echo -e "$NOTICE Downloading custom checks from S3 URI $EXTERNAL_CHECKS_PATH...$NORMAL" - S3_CHECKS_TEMP_FOLDER="$PROWLER_DIR/s3-custom-checks" + echo -e "${NOTICE} Downloading custom checks from S3 URI ${EXTERNAL_CHECKS_PATH}...${NORMAL}" + S3_CHECKS_TEMP_FOLDER="${PROWLER_DIR}/s3-custom-checks" mkdir "${S3_CHECKS_TEMP_FOLDER}" - $AWSCLI s3 sync "$EXTERNAL_CHECKS_PATH" "${S3_CHECKS_TEMP_FOLDER}" $PROFILE_OPT > /dev/null + ${AWSCLI} s3 sync "${EXTERNAL_CHECKS_PATH}" "${S3_CHECKS_TEMP_FOLDER}" "${PROFILE_OPT}" > /dev/null # verify if there are checks - for checks in "${S3_CHECKS_TEMP_FOLDER}"/check*; do - . "$checks" - echo -e "$OK Check $(basename "$checks") was included!$NORMAL" + for CHECKS in "${S3_CHECKS_TEMP_FOLDER}"/check*; do + . "${CHECKS}" + echo -e "${OK} Check $(basename "${CHECKS}") was included!${NORMAL}" done - echo -e "$OK Success! Custom checks were downloaded and included, starting Prowler...$NORMAL" + echo -e "${OK} Success! Custom checks were downloaded and included, starting Prowler...${NORMAL}" # remove temporary dir rm -rf "${S3_CHECKS_TEMP_FOLDER}" else - echo "$BAD FAIL! Access Denied trying to download custom checks or $EXTERNAL_CHECKS_PATH does not contain any checks, please make sure it is correct and/or you have permissions to get the S3 objects.$NORMAL" + echo "${BAD} FAIL! Access Denied trying to download custom checks or ${EXTERNAL_CHECKS_PATH} does not contain any checks, please make sure it is correct and/or you have permissions to get the S3 objects.${NORMAL}" EXITCODE=1 # remove temporary dir rm -rf "${S3_CHECKS_TEMP_FOLDER}" - exit $EXITCODE + exit "${EXITCODE}" fi else # verify if input directory exists with checks if ls "${EXTERNAL_CHECKS_PATH}"/check* > /dev/null 2>&1; then - for checks in "${EXTERNAL_CHECKS_PATH}"/check*; do - . "$checks" - echo -e "$OK Check $(basename "$checks") was included!$NORMAL" + for CHECKS in "${EXTERNAL_CHECKS_PATH}"/check*; do + . "${CHECKS}" + echo -e "${OK} Check $(basename "${CHECKS}") was included!${NORMAL}" done - echo -e "$OK Success! Custom checks were included, starting Prowler...$NORMAL" + echo -e "${OK} Success! Custom checks were included, starting Prowler...${NORMAL}" else - echo "$BAD FAIL! $EXTERNAL_CHECKS_PATH does not exist or not contain checks, please input a valid custom checks path.$NORMAL" + echo "${BAD} FAIL! ${EXTERNAL_CHECKS_PATH} does not exist or not contain checks, please input a valid custom checks path.${NORMAL}" EXITCODE=1 - exit $EXITCODE + exit "${EXITCODE}" fi fi } diff --git a/include/jq_detector b/include/default_variables similarity index 60% rename from include/jq_detector rename to include/default_variables index 544ebbef57..f5d905531b 100644 --- a/include/jq_detector +++ b/include/default_variables @@ -11,10 +11,30 @@ # CONDITIONS OF ANY KIND, either express or implied. See the License for the # specific language governing permissions and limitations under the License. -# AWS-CLI detector variable -JQ=$(which jq) -if [ -z "${JQ}" ]; then - echo -e "\n$RED ERROR!$NORMAL jq not found. Make sure it is installed correctly and in your \$PATH\n" - EXITCODE=1 - exit $EXITCODE -fi +# Output Colors +OPTRED="" +OPTNORMAL="" + +# AWS Region +REGION="" +FILTERREGION="" + +# Max AWS CLI items +MAXITEMS=100 + +# Default output mode +MODE="text" + +# Security Hub +SEND_TO_SECURITY_HUB=0 + +# Date & Time +PROWLER_START_TIME=$( date -u +"%Y-%m-%dT%H:%M:%S%z" ) +OUTPUT_DATE=$(date -u +"%Y%m%d%H%M%S") + +# Default Prowler Options +QUIET=0 +SEP=',' +KEEPCREDREPORT=0 +EXITCODE=0 +FAILED_CHECK_FAILED_SCAN=1 diff --git a/include/execute_check b/include/execute_check new file mode 100644 index 0000000000..d67e564cea --- /dev/null +++ b/include/execute_check @@ -0,0 +1,131 @@ +#!/usr/bin/env bash + +# Copyright 2018 Toni de la Fuente + +# Prowler is a tool that provides automate auditing and hardening guidance of an +# AWS account. It is based on AWS-CLI commands. It follows some guidelines +# present in the CIS Amazon Web Services Foundations Benchmark at: +# https://d0.awsstatic.com/whitepapers/compliance/AWS_CIS_Foundations_Benchmark.pdf + +# Contact the author at https://blyx.com/contact +# and open issues or ask questions at https://github.com/prowler-cloud/prowler + +# Code is licensed as Apache License 2.0 as specified in +# each file. You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 + +# Function to execute the check +execute_check() { + if [[ -n "${ACCOUNT_TO_ASSUME}" || -n "${ROLE_TO_ASSUME}" ]]; then + # echo ******* I am here again to check on my role ******* + # Following logic looks for time remaining in the session and review it + # if it is less than 600 seconds, 10 minutes. + CURRENT_TIMESTAMP=$(date -u "+%s") + SESSION_TIME_REMAINING=$(expr $AWS_SESSION_EXPIRATION - $CURRENT_TIMESTAMP) + # echo SESSION TIME REMAINING IN SECONDS: $SESSION_TIME_REMAINING + MINIMUM_REMAINING_TIME_ALLOWED=600 + if (( $MINIMUM_REMAINING_TIME_ALLOWED > $SESSION_TIME_REMAINING )); then + # echo LESS THAN 10 MIN LEFT: RE-ASSUMING... + unset AWS_ACCESS_KEY_ID + unset AWS_SECRET_ACCESS_KEY + unset AWS_SESSION_TOKEN + assume_role + fi + fi + + CHECK_ID="$1" + + # See if this is an alternate name for a check + # for example, we might have been passed 1.01 which is another name for 1.1 + local alternate_name_var=CHECK_ALTERNATE_$1 + local alternate_name=${!alternate_name_var} + # See if this check defines an ASFF Type, if so, use this, falling back to a sane default + # For a list of Types Taxonomy, see: https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-findings-format-type-taxonomy.html + local asff_type_var=CHECK_ASFF_TYPE_$1 + CHECK_ASFF_TYPE="${!asff_type_var:-Software and Configuration Checks}" + + local asff_compliance_type_var=CHECK_ASFF_COMPLIANCE_TYPE_$1 + CHECK_ASFF_COMPLIANCE_TYPE="${!asff_compliance_type_var:-Software and Configuration Checks}" + + # See if this check defines an ASFF Resource Type, if so, use this, falling back to a sane default + # For a list of Resource Types, see: https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-findings-format.html#asff-resources + local asff_resource_type_var=CHECK_ASFF_RESOURCE_TYPE_$1 + CHECK_ASFF_RESOURCE_TYPE="${!asff_resource_type_var:-AwsAccount}" + + local severity_var=CHECK_SEVERITY_$1 + CHECK_SEVERITY="${!severity_var}" + + local servicename_var=CHECK_SERVICENAME_$1 + CHECK_SERVICENAME="${!servicename_var}" + + local risk_var=CHECK_RISK_$1 + CHECK_RISK="${!risk_var}" + + local remediation_var=CHECK_REMEDIATION_$1 + CHECK_REMEDIATION="${!remediation_var}" + + local doc_var=CHECK_DOC_$1 + CHECK_DOC="${!doc_var}" + + local caf_epic_var=CHECK_CAF_EPIC_$1 + CHECK_CAF_EPIC="${!caf_epic_var}" + + SECURITYHUB_NEW_FINDINGS_IDS=() + + # Generate the credential report, only if it is group1 related which checks we + # run so that the checks can safely assume it's available + # set the custom ignores list for this check + ignores="$(awk "/${1}/{print}" <(echo "${ALLOWLIST}"))" + + if [ ${alternate_name} ];then + if [[ ${alternate_name} == check1* || ${alternate_name} == extra71 || ${alternate_name} == extra774 || ${alternate_name} == extra7123 ]];then + if [ ! -s $TEMP_REPORT_FILE ];then + genCredReport + saveReport + fi + fi + show_check_title ${alternate_name} + if is_junit_output_enabled; then + prepare_junit_check_output "$1" + fi + # Execute the check + IGNORES="${ignores}" CHECK_NAME="$1" ${alternate_name} + if is_junit_output_enabled; then + finalise_junit_check_output "$1" + fi + + if [[ "$SEND_TO_SECURITY_HUB" -eq 1 ]]; then + resolveSecurityHubPreviousFails "$1" + fi + else + # Check to see if this is a real check + local check_id_var=CHECK_ID_$1 + local check_id=${!check_id_var} + if [ ${check_id} ]; then + if [[ ${check_id} == 1* || ${check_id} == 7.1 || ${check_id} == 7.74 || ${check_id} == 7.123 ]];then + if [ ! -s $TEMP_REPORT_FILE ];then + genCredReport + saveReport + fi + fi + show_check_title "$1" + if is_junit_output_enabled; then + prepare_junit_check_output "$1" + fi + # Execute the check + IGNORES="${ignores}" CHECK_NAME="$1" $1 + + if is_junit_output_enabled; then + finalise_junit_check_output "$1" + fi + + if [[ "$SEND_TO_SECURITY_HUB" -eq 1 ]]; then + resolveSecurityHubPreviousFails "$1" + fi + + else + textFail "Check ${CHECK_ID} does not exist. Use a valid check name (i.e. check41 or extra71)"; + exit $EXITCODE + fi + fi +} diff --git a/include/junit_integration b/include/junit_integration index dc1bdbc630..3cb7031189 100644 --- a/include/junit_integration +++ b/include/junit_integration @@ -21,7 +21,7 @@ JUNIT_SKIPPED_COUNT="0" JUNIT_ERRORS_COUNT="0" is_junit_output_enabled() { - if [[ " ${MODES[@]} " =~ " junit-xml " ]]; then + if [[ " ${MODE} " =~ "junit-xml" ]]; then true else false diff --git a/include/loader b/include/loader new file mode 100644 index 0000000000..99f3d291dd --- /dev/null +++ b/include/loader @@ -0,0 +1,137 @@ +#!/usr/bin/env bash + +# Copyright 2018 Toni de la Fuente + +# Prowler is a tool that provides automate auditing and hardening guidance of an +# AWS account. It is based on AWS-CLI commands. It follows some guidelines +# present in the CIS Amazon Web Services Foundations Benchmark at: +# https://d0.awsstatic.com/whitepapers/compliance/AWS_CIS_Foundations_Benchmark.pdf + +# Contact the author at https://blyx.com/contact +# and open issues or ask questions at https://github.com/prowler-cloud/prowler + +# Code is licensed as Apache License 2.0 as specified in +# each file. You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 + +# Load all of the groups of checks inside groups folder named as "groupNumber*" +load_groups() { +for group in "${PROWLER_DIR}"/groups/group[0-9]*; do + # shellcheck source=/dev/null + . "${group}" +done +} + +# Load a single check +load_check(){ + CHECK=${1} + # shellcheck source=/dev/null + . "$PROWLER_DIR/checks/${CHECK}" +} + +get_checks() { + # Parses the check file into CHECK_ID's. + if [[ -n "$CHECK_FILE" ]] + then + if [[ -f $CHECK_FILE ]] + then + # Parses the file, converting it to a comma seperated list. Ignores all # comments and removes extra blank spaces + # REVIEW THIS OPTION -C + CHECKS_FROM_FILE="$(awk '!/^[[:space:]]*#/{print }' <(cat ${CHECK_FILE} | sed 's/[[:space:]]*#.*$//g;/^$/d' | sed 'H;1h;$!d;x;y/\n/,/' | tr -d ' '))" + IFS=',' read -ra TOTAL_CHECKS <<< "${CHECKS_FROM_FILE}" + else + # If the file doesn't exist, exits Prowler + echo "$CHECK_FILE does not exist" + EXITCODE=1 + exit $EXITCODE + fi + + # If '-g ' has been specified, only show the titles of checks within the specified group + # reading groups from GROUP_ID_READ + elif [[ "${GROUP_ID_READ}" ]] + then + for GROUP in "${GROUP_ID[@]}" + do + if [[ "${GROUP}" == "${GROUP_ID_READ}" ]] + then + IS_GROUP=1 + fi + done + if [[ IS_GROUP -eq 0 ]] + then + textFail "Group ${GROUP_ID_READ} does not exist. Valid check groups are: ${GROUP_ID[*]}" + exit $EXITCODE + fi + # Iterate over every group removing echar comma + for GROUP_IDENTIFIER in ${GROUP_ID_READ//,/ } + do + # Iterate over every GroupID to find the belongin checks + for I in "${!GROUP_ID[@]}" + do + if [[ "${EXTRAS}" -eq 1 && "${GROUP_ID[I]}" == "extras" ]] + then + continue + else + if [[ "${GROUP_ID[I]}" == "${GROUP_IDENTIFIER}" ]] + then + # shellcheck disable=SC2068 + for CHECK_IDENTIFIER in ${GROUP_CHECKS[I]//,/ } + do + # Include every check if not present + if [[ ! "${CHECK_LIST_BY_GROUP[*]}" =~ ${CHECK_IDENTIFIER} ]] && ! grep -E -w -q "${EXCLUDE_CHECK_ID//,/|}" <<< "${CHECK_IDENTIFIER}" + then + CHECK_LIST_BY_GROUP+=("${CHECK_IDENTIFIER}") + fi + done + fi + fi + done + done + TOTAL_CHECKS=("${CHECK_LIST_BY_GROUP[@]}") + + # If -c option is set with checks + elif [ -n "${CHECK_ID}" ] + then + IFS=',' read -ra TOTAL_CHECKS <<< "${CHECK_ID}" + + # If no option input to include/exclude checks + else + # if -e is passed we dont want extra checks + if [[ "${EXTRAS}" -eq 1 ]] + then + if [[ -n "$EXCLUDE_CHECK_ID" ]] + then + EXCLUDE_CHECK_ID="${EXCLUDE_CHECK_ID},${GROUP_CHECKS[7]}" + else + EXCLUDE_CHECK_ID="${GROUP_CHECKS[7]}" + fi + fi + # Always ignore the following checks if not supplied with -C + IGNORED_CHECKS="sample,extra9999" + EXCLUDE_CHECK_ID="${EXCLUDE_CHECK_ID/%/,}${IGNORED_CHECKS}" + + for CHECK in "${PROWLER_DIR}"/checks/check*; do + # Relative path required to load every check + CHECK_DIR_NAME=$(basename "${CHECK}") + # Name of the check + CHECK_NAME=${CHECK_DIR_NAME//check_/} + # If a group identifier is passed + if ! grep -E -w -q "${EXCLUDE_CHECK_ID//,/|}" <<< "${CHECK_NAME}" + then + TOTAL_CHECKS+=("${CHECK_NAME}") + fi + + done + fi + + # Iterate over the final list of checks after being parsed all the input options to load the selected checks + for LOAD_PATH_CHECK in "${TOTAL_CHECKS[@]}" + do + # If the check is extra, the path needs to add check_ after the check name + if [[ "${LOAD_PATH_CHECK}" =~ 'extra' ]] + then + LOAD_PATH_CHECK=${LOAD_PATH_CHECK/#/check_} + fi + load_check "${LOAD_PATH_CHECK}" + done +} diff --git a/include/os_detector b/include/os_detector index cf440c84b8..c5cfed6082 100644 --- a/include/os_detector +++ b/include/os_detector @@ -145,65 +145,21 @@ bsd_replace_sed(){ sed -i '' $1 $2 } -# Functions to manage dates depending on OS -if [ "$OSTYPE" == "linux-gnu" ] || [ "$OSTYPE" == "linux-musl" ]; then - TEMP_REPORT_FILE=$(mktemp -t -p /tmp prowler.cred_report-XXXXXX) - # function to compare in days, usage how_older_from_today date - # date format %Y-%m-%d - how_older_from_today() { - gnu_how_older_from_today "$1" - } - timestamp_to_date() { - gnu_timestamp_to_date "$1" - } - decode_report() { - gnu_decode_report - } - how_many_days_from_today() { - gnu_how_many_days_from_today "$1" - } - get_date_previous_than_months() { - gnu_get_date_previous_than_months "$1" - } - get_time_in_milliseconds() { - gnu_get_time_in_milliseconds - } - get_iso8601_timestamp() { - gnu_get_iso8601_timestamp - } - test_tcp_connectivity() { - gnu_test_tcp_connectivity "$1" "$2" "$3" - } - convert_date_to_timestamp() { - gnu_convert_date_to_timestamp "$1" - } - replace_sed() { - gnu_replace_sed $1 $2 - } - -elif [[ "$OSTYPE" == "darwin"* ]] || [[ "$OSTYPE" == "freebsd"* ]]; then - # BSD/OSX commands compatibility - TEMP_REPORT_FILE=$(mktemp -t prowler.cred_report-XXXXXX) - # It is possible that the user has installed GNU coreutils on OS X. By default, this will make GNU commands - # available with a 'g' prefix, e.g. 'gdate'. Test if this is present, and use it if so, as it supports more features. - # The user also may have replaced the default Mac OS X BSD tools with the GNU coreutils equivalents. - # Only GNU date/base64 allows --version as a valid argument, so use the validity of this argument - # as a means to detect that coreutils is installed and is overriding the default tools - GDATE=$(which gdate) - if [ -n "${GDATE}" ]; then - DATE_CMD="gdate" - fi - GBASE64=$(which gbase64) - if [ -n "${GBASE64}" ]; then - BASE64_CMD="gbase64" - fi - if "$DATE_CMD" --version >/dev/null 2>&1 ; then +os_detector() { + # Functions to manage dates depending on OS + if [ "$OSTYPE" == "linux-gnu" ] || [ "$OSTYPE" == "linux-musl" ]; then + TEMP_REPORT_FILE=$(mktemp -t -p /tmp prowler.cred_report-XXXXXX) + # function to compare in days, usage how_older_from_today date + # date format %Y-%m-%d how_older_from_today() { gnu_how_older_from_today "$1" } timestamp_to_date() { gnu_timestamp_to_date "$1" } + decode_report() { + gnu_decode_report + } how_many_days_from_today() { gnu_how_many_days_from_today "$1" } @@ -216,83 +172,129 @@ elif [[ "$OSTYPE" == "darwin"* ]] || [[ "$OSTYPE" == "freebsd"* ]]; then get_iso8601_timestamp() { gnu_get_iso8601_timestamp } + test_tcp_connectivity() { + gnu_test_tcp_connectivity "$1" "$2" "$3" + } convert_date_to_timestamp() { gnu_convert_date_to_timestamp "$1" } - else + replace_sed() { + gnu_replace_sed $1 $2 + } + + elif [[ "$OSTYPE" == "darwin"* ]] || [[ "$OSTYPE" == "freebsd"* ]]; then + # BSD/OSX commands compatibility + TEMP_REPORT_FILE=$(mktemp -t prowler.cred_report-XXXXXX) + # It is possible that the user has installed GNU coreutils on OS X. By default, this will make GNU commands + # available with a 'g' prefix, e.g. 'gdate'. Test if this is present, and use it if so, as it supports more features. + # The user also may have replaced the default Mac OS X BSD tools with the GNU coreutils equivalents. + # Only GNU date/base64 allows --version as a valid argument, so use the validity of this argument + # as a means to detect that coreutils is installed and is overriding the default tools + GDATE=$(which gdate) + if [ -n "${GDATE}" ]; then + DATE_CMD="gdate" + fi + GBASE64=$(which gbase64) + if [ -n "${GBASE64}" ]; then + BASE64_CMD="gbase64" + fi + if "$DATE_CMD" --version >/dev/null 2>&1 ; then + how_older_from_today() { + gnu_how_older_from_today "$1" + } + timestamp_to_date() { + gnu_timestamp_to_date "$1" + } + how_many_days_from_today() { + gnu_how_many_days_from_today "$1" + } + get_date_previous_than_months() { + gnu_get_date_previous_than_months "$1" + } + get_time_in_milliseconds() { + gnu_get_time_in_milliseconds + } + get_iso8601_timestamp() { + gnu_get_iso8601_timestamp + } + convert_date_to_timestamp() { + gnu_convert_date_to_timestamp "$1" + } + else + how_older_from_today() { + bsd_how_older_from_today "$1" + } + timestamp_to_date() { + bsd_timestamp_to_date "$1" + } + how_many_days_from_today() { + bsd_how_many_days_from_today "$1" + } + get_date_previous_than_months() { + bsd_get_date_previous_than_months "$1" + } + get_time_in_milliseconds() { + bsd_get_time_in_milliseconds + } + get_iso8601_timestamp() { + bsd_get_iso8601_timestamp + } + convert_date_to_timestamp() { + bsd_convert_date_to_timestamp "$1" + } + fi + if "$BASE64_CMD" --version >/dev/null 2>&1 ; then + decode_report() { + gnu_decode_report + } + else + decode_report() { + bsd_decode_report + } + fi + test_tcp_connectivity() { + bsd_test_tcp_connectivity "$1" "$2" "$3" + } + replace_sed() { + bsd_replace_sed $1 $2 + } + elif [[ "$OSTYPE" == "cygwin" ]]; then + # POSIX compatibility layer and Linux environment emulation for Windows + TEMP_REPORT_FILE=$(mktemp -t -p /tmp prowler.cred_report-XXXXXX) how_older_from_today() { - bsd_how_older_from_today "$1" + gnu_how_older_from_today "$1" } timestamp_to_date() { - bsd_timestamp_to_date "$1" + gnu_timestamp_to_date "$1" } - how_many_days_from_today() { - bsd_how_many_days_from_today "$1" - } - get_date_previous_than_months() { - bsd_get_date_previous_than_months "$1" - } - get_time_in_milliseconds() { - bsd_get_time_in_milliseconds - } - get_iso8601_timestamp() { - bsd_get_iso8601_timestamp - } - convert_date_to_timestamp() { - bsd_convert_date_to_timestamp "$1" - } - fi - if "$BASE64_CMD" --version >/dev/null 2>&1 ; then decode_report() { gnu_decode_report } - else - decode_report() { - bsd_decode_report + how_many_days_from_today() { + gnu_how_many_days_from_today "$1" } + get_date_previous_than_months() { + gnu_get_date_previous_than_months "$1" + } + get_time_in_milliseconds() { + gnu_get_time_in_milliseconds + } + get_iso8601_timestamp() { + gnu_get_iso8601_timestamp + } + test_tcp_connectivity() { + gnu_test_tcp_connectivity "$1" "$2" "$3" + } + convert_date_to_timestamp() { + gnu_convert_date_to_timestamp "$1" + } + replace_sed() { + gnu_replace_sed $1 $2 + } + else + echo "Unknown Operating System! Valid \$OSTYPE: linux-gnu, linux-musl, darwin* or cygwin" + echo "Found: $OSTYPE" + EXITCODE=1 + exit $EXITCODE fi - test_tcp_connectivity() { - bsd_test_tcp_connectivity "$1" "$2" "$3" - } - replace_sed() { - bsd_replace_sed $1 $2 - } -elif [[ "$OSTYPE" == "cygwin" ]]; then - # POSIX compatibility layer and Linux environment emulation for Windows - TEMP_REPORT_FILE=$(mktemp -t -p /tmp prowler.cred_report-XXXXXX) - how_older_from_today() { - gnu_how_older_from_today "$1" - } - timestamp_to_date() { - gnu_timestamp_to_date "$1" - } - decode_report() { - gnu_decode_report - } - how_many_days_from_today() { - gnu_how_many_days_from_today "$1" - } - get_date_previous_than_months() { - gnu_get_date_previous_than_months "$1" - } - get_time_in_milliseconds() { - gnu_get_time_in_milliseconds - } - get_iso8601_timestamp() { - gnu_get_iso8601_timestamp - } - test_tcp_connectivity() { - gnu_test_tcp_connectivity "$1" "$2" "$3" - } - convert_date_to_timestamp() { - gnu_convert_date_to_timestamp "$1" - } - replace_sed() { - gnu_replace_sed $1 $2 - } -else - echo "Unknown Operating System! Valid \$OSTYPE: linux-gnu, linux-musl, darwin* or cygwin" - echo "Found: $OSTYPE" - EXITCODE=1 - exit $EXITCODE -fi +} \ No newline at end of file diff --git a/include/outputs b/include/outputs index ff3e3e05cf..65dc6b16c4 100644 --- a/include/outputs +++ b/include/outputs @@ -45,7 +45,6 @@ EXTENSION_TEXT="txt" EXTENSION_HTML="html" HTML_LOGO_URL="https://github.com/prowler-cloud/prowler/" HTML_LOGO_IMG="https://github.com/prowler-cloud/prowler/raw/master/util/html/prowler-logo-new.png" -TIMESTAMP=$(get_iso8601_timestamp) PROWLER_PARAMETERS=$@ @@ -65,19 +64,29 @@ set_exitcode () { # Add header to certain output files readed from mode output_files_init() { - for MODE in "${MODES[@]}"; + OIFS="${IFS}" + IFS=',' + for MODE_TYPE in ${MODE} do - if [ "${MODE}" == 'html' ] + if [ "${MODE_TYPE}" == 'html' ] then addHtmlHeader HTML_REPORT_INIT="1" export HTML_REPORT_INIT fi - if [ "${MODE}" == 'csv' ] + if [ "${MODE_TYPE}" == 'csv' ] then printCsvHeader fi done + IFS="${OIFS}" +} + +# Close HTML output file +output_files_end() { + if [[ "${MODE}" =~ "html" ]]; then + addHtmlFooter >> "${OUTPUT_FILE_NAME}.${EXTENSION_HTML}" + fi } # Check if resource checked is allowlisted @@ -91,7 +100,7 @@ allowlist_check() { # IGNORE_CHECK_NAME is the check with resources allowlisted # RESOURCE_VALUE is what it comes after 'CHECK_NAME:' IGNORE_CHECK_NAME=$(awk -F ":" '{print $1}' <<< "${excluded_item}") - RESOURCE_VALUE=$(awk -F "${CHECK_NAME}:" '{print $2}' <<< "${excluded_item}") + RESOURCE_VALUE=$(awk -F "${CHECK_NAME}:" '{print $2}' <<< "${excluded_item}") if [[ "${IGNORE_CHECK_NAME}" == "${CHECK_NAME}" ]] then if [[ "${CHECKED_VALUE}" =~ ${RESOURCE_VALUE} ]] @@ -100,7 +109,7 @@ allowlist_check() { break fi fi - + done <<< "$IGNORES" echo "${CHECK_RESULT}" } @@ -140,23 +149,26 @@ general_output() { REGION_FROM_CHECK=$REGION fi # Iterating over input modes - for MODE in "${MODES[@]}"; + OIFS="${IFS}" + IFS=',' + for MODE_TYPE in ${MODE} do - if [ "${MODE}" == 'html' ] + IFS="${OIFS}" + if [ "${MODE_TYPE}" == 'html' ] then generateHtmlOutput "${CHECK_RESULT_EXTENDED}" "${CHECK_RESULT}" - elif [ "${MODE}" == 'csv' ] + elif [ "${MODE_TYPE}" == 'csv' ] then echo "${CSV_LINE}" >> "${OUTPUT_FILE_NAME}"."${EXTENSION_CSV}" - elif [ "${MODE}" == 'json' ] + elif [ "${MODE_TYPE}" == 'json' ] then generateJsonOutput "${CHECK_RESULT_EXTENDED}" "${CHECK_RESULT}" "$CHECK_RESOURCE_ID" >> "${OUTPUT_FILE_NAME}"."${EXTENSION_JSON}" - elif [ "${MODE}" == 'json-asff' ] + elif [ "${MODE_TYPE}" == 'json-asff' ] then JSON_ASFF_OUTPUT=$(generateJsonAsffOutput "${CHECK_RESULT_EXTENDED}" "PASSED" "$CHECK_RESOURCE_ID") echo "${JSON_ASFF_OUTPUT}" >> "${OUTPUT_FILE_NAME}"."${EXTENSION_ASFF}" if [[ "${SEND_TO_SECURITY_HUB}" -eq 1 ]]; then - sendToSecurityHub "${JSON_ASFF_OUTPUT}" "${REGION_FROM_CHECK}" + sendToSecurityHub "${JSON_ASFF_OUTPUT}" "${REGION_FROM_CHECK}" fi elif is_junit_output_enabled then @@ -164,7 +176,7 @@ general_output() { then output_junit_success "${CHECK_RESULT_EXTENDED}" elif [ "${CHECK_RESULT}" == 'INFO' ] - then + then output_junit_info "${CHECK_RESULT_EXTENDED}" elif [ "${CHECK_RESULT}" == 'FAIL' ] then @@ -173,7 +185,7 @@ general_output() { then output_junit_skipped "${CHECK_RESULT_EXTENDED}" fi - elif [ "${MODE}" == 'mono' ] + elif [ "${MODE_TYPE}" == 'mono' ] then echo " $COLOR_CODE ${CHECK_RESULT}! $NORMAL ${CHECK_RESULT_EXTENDED}">> "${OUTPUT_FILE_NAME}"."${EXTENSION_TEXT}" fi @@ -195,11 +207,13 @@ textPass(){ CHECK_RESOURCE_ID="${3}" CHECK_REGION="${2}" + PASS_COUNTER=$((PASS_COUNTER+1)) + if is_quiet "${QUIET}" then return fi - + general_output "${CHECK_RESULT}" "${CHECK_RESULT_EXTENDED}" "${CHECK_RESOURCE_ID}" "${CHECK_REGION}" } @@ -218,15 +232,17 @@ textInfo(){ } textFail(){ - + CHECK_RESULT='FAIL' CHECK_RESULT_EXTENDED="${1}" CHECK_RESOURCE_ID="${3}" CHECK_REGION="${2}" + FAIL_COUNTER=$((FAIL_COUNTER+1)) + # Check if resources are whitelisted CHECK_RESULT=$(allowlist_check "${CHECK_RESULT_EXTENDED}") - + # only set non-0 exit code on FAIL mode, WARN is ok if [[ "${CHECK_RESULT}" == "FAIL" ]] then @@ -237,11 +253,13 @@ textFail(){ } textTitle(){ - + TITLE_ID="${1}" TITLE_TEXT="${2}" - CHECK_ASFF_COMP_TYPE="${6}" + CHECK_ASFF_COMP_TYPE="${6}" + CHECKS_COUNTER=$((CHECKS_COUNTER+1)) + if [[ $NUMERAL ]]; then # Left-pad the check ID with zeros to simplify sorting, e.g. 1.1 -> 1.01 TITLE_ID=$(awk -F'.' '{ printf "%d.%02d", $1, $2 }' <<< "$TITLE_ID") @@ -257,7 +275,7 @@ textTitle(){ SUPPORT) ITEM_CIS_LEVEL="Support";; *) ITEM_CIS_LEVEL="Unspecified or Invalid";; esac - + echo -e "$TITLE_ID $TITLE_TEXT - $CHECK_SERVICENAME $CHECK_SEVERITY" } @@ -321,14 +339,14 @@ generateJsonAsffOutput(){ # Replace any successive non-conforming characters with a single underscore local message=$1 local status=$2 - + #Checks to determine if the rule passes in a resource name that prowler uses to track the AWS Resource for allowlisting purposes if [[ -z $3 ]]; then local resource_id="NONE_PROVIDED" else local resource_id=$3 fi - + if [[ "$status" == "FAIL" ]]; then status="FAILED" fi @@ -384,7 +402,7 @@ generateJsonAsffOutput(){ "Status": $STATUS, "RelatedRequirements": [ $COMPLIANCE_RELATED_REQUIREMENTS ] } - + }' } @@ -404,7 +422,7 @@ generateHtmlOutput(){ if [[ $status == "WARN" ]];then local ROW_CLASS='table-warning' fi - + local CHECK_SEVERITY="$(echo $CHECK_SEVERITY | sed 's/[][]//g')" echo '' >> ${OUTPUT_FILE_NAME}.$EXTENSION_HTML @@ -425,4 +443,4 @@ generateHtmlOutput(){ echo ' '$CHECK_RESOURCE_ID'' >> ${OUTPUT_FILE_NAME}.$EXTENSION_HTML echo '' >> ${OUTPUT_FILE_NAME}.$EXTENSION_HTML echo '' >> ${OUTPUT_FILE_NAME}.$EXTENSION_HTML -} \ No newline at end of file +} diff --git a/include/outputs_bucket b/include/outputs_bucket index d6a433cdbe..6749865086 100644 --- a/include/outputs_bucket +++ b/include/outputs_bucket @@ -11,21 +11,8 @@ # CONDITIONS OF ANY KIND, either express or implied. See the License for the # specific language governing permissions and limitations under the License. -if [[ $OUTPUT_BUCKET ]]; then - # output mode has to be set to other than text - if [[ "${MODES[*]}" =~ "text" ]]; then - echo "$OPTRED ERROR!$OPTNORMAL - Mode (-M) can't be text when using custom output bucket. Use -h for help." - exit 1 - else - # need to make sure last / is not set to avoid // in S3 - if [[ $OUTPUT_BUCKET == *"/" ]]; then - OUTPUT_BUCKET=${OUTPUT_BUCKET::-1} - fi - fi -fi - copyToS3() { - # Prowler will copy each format to its own folder in S3, that is for better handling + # Prowler will copy each format to its own folder in S3, that is for better handling # and processing by Quicksight or others. # Also, check if -F was introduced if [ -n "${OUTPUT_FILE_NAME+x}" ]; then @@ -34,9 +21,12 @@ copyToS3() { OUTPUT_PATH="$OUTPUT_DIR/prowler-output-${ACCOUNT_NUM}-${OUTPUT_DATE}" fi - for output_format in "${MODES[@]}"; + OIFS="${IFS}" + IFS=',' + for MODE_TYPE in ${MODE} do - case ${output_format} in + IFS="${OIFS}" + case ${MODE_TYPE} in csv) s3cp "${OUTPUT_PATH}" "${EXTENSION_CSV}" ;; diff --git a/include/python_detector b/include/python_detector deleted file mode 100644 index 712bf84aa4..0000000000 --- a/include/python_detector +++ /dev/null @@ -1,32 +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. - -# detector of python and boto3 -pythonDetector(){ - PYTHON_BIN=$(which python) - PYTHON_PIP_BOTO3=$(pip list|grep boto3) - if [ -z "${PYTHON_BIN}" ]; then - echo -e "\n$RED ERROR!$NORMAL python not found. Make sure it is installed correctly and in your \$PATH\n" - EXITCODE=1 - exit $EXITCODE - else - PYTHON_INSTALLED=1 - if [ -z "${PYTHON_PIP_BOTO3}" ]; then - echo -e "\n$RED ERROR!$NORMAL python library boto3 not found. Make sure it is installed correctly\n" - EXITCODE=1 - exit $EXITCODE - else - PYTHON_PIP_BOTO3_INSTALLED=1 - fi - fi -} \ No newline at end of file diff --git a/include/scoring b/include/scoring index bf258d2e80..18388ecafb 100644 --- a/include/scoring +++ b/include/scoring @@ -54,8 +54,8 @@ scoring(){ echo -e "$BLUE------------------------------------------------------------------ $NORMAL" echo -e " * the higher the better (0 to 100)$NORMAL" echo -e " Prowler scoring uses any check, including CIS not scored checks$NORMAL" - fi - if [[ "${MODES[@]}" =~ "html" ]]; then + fi + if [[ "${MODE}" =~ "html" ]]; then replace_sed 's/PROWLER_SCORE/'$PROWLER_SCORE'/g' ${OUTPUT_FILE_NAME}.$EXTENSION_HTML replace_sed 's/PASS_COUNTER/'$PASS_COUNTER'/g' ${OUTPUT_FILE_NAME}.$EXTENSION_HTML replace_sed 's/TOTAL_RESOURCES/'$TOTAL_RESOURCES'/g' ${OUTPUT_FILE_NAME}.$EXTENSION_HTML diff --git a/include/secrets_detector b/include/secrets_detector index 9acea2f4a1..2f20516ae0 100644 --- a/include/secrets_detector +++ b/include/secrets_detector @@ -13,41 +13,40 @@ # detector and configuration of detect-secrets secretsDetector(){ - PYTHON_PIP_DETECTSECRETS=$(which detect-secrets) - if [ -z "${PYTHON_PIP_DETECTSECRETS}" ]; then + if ! command -v "detect-secrets" &>/dev/null + then echo -e "\n$RED ERROR!$NORMAL python library detect-secrets not found. Make sure it is installed correctly and in your \$PATH\n" EXITCODE=241 exit $EXITCODE else SECRETS_TEMP_FOLDER="$PROWLER_DIR/secrets-$ACCOUNT_NUM" - if [[ ! -d $SECRETS_TEMP_FOLDER ]]; then - mkdir $SECRETS_TEMP_FOLDER + if [[ ! -d "${SECRETS_TEMP_FOLDER}" ]]; then + mkdir "${SECRETS_TEMP_FOLDER}" fi - PYTHON_PIP_DETECTSECRETS_INSTALLED=1 - # Sets the entropy limit for high entropy base64 strings from - # environment variable BASE64_LIMIT. + + # Sets the entropy limit for high entropy base64 strings from environment variable BASE64_LIMIT. # Value must be between 0.0 and 8.0, defaults is 4.5. - # Sets the entropy limit for high entropy hex strings from - # environment variable HEX_LIMIT. + # Sets the entropy limit for high entropy hex strings from environment variable HEX_LIMIT. # Value must be between 0.0 and 8.0, defaults is 3.0. case $1 in file ) # this is to scan a file - detect-secrets scan --hex-limit ${HEX_LIMIT:-3.0} --base64-limit ${BASE64_LIMIT:-4.5} $2 | \ - jq -r '.results[]|.[] | [.line_number, .type]|@csv' | wc -l - #jq -r '.results[] | .[] | "\(.line_number)\t\(.type)"' + detect-secrets scan --hex-limit "${HEX_LIMIT:-3.0}" --base64-limit "${BASE64_LIMIT:-4.5}" "${2}" \ + | jq -r '.results[]|.[] | [.line_number, .type]|@csv' \ + | wc -l + # jq -r '.results[] | .[] | "\(.line_number)\t\(.type)"' # this command must return values in two colums: # line in file and type of secrets found ;; string ) # this is to scan a given string - detect-secrets scan --hex-limit ${HEX_LIMIT:-3.0} --base64-limit ${BASE64_LIMIT:-4.5} --string $2 | \ - grep True| wc -l + detect-secrets scan --hex-limit "${HEX_LIMIT:-3.0}" --base64-limit "${BASE64_LIMIT:-4.5}" --string "${2}" \ + | grep True | wc -l ;; folder ) # this is to scan a given folder with all lambda files - detect-secrets scan --hex-limit ${HEX_LIMIT:-3.0} --base64-limit ${BASE64_LIMIT:-4.5} --all-files $2 | \ - jq -r '.results[]|.[] | [.line_number, .type]|@csv' | wc -l + detect-secrets scan --hex-limit "${HEX_LIMIT:-3.0}" --base64-limit "${BASE64_LIMIT:-4.5}" --all-files "${2}" \ + | jq -r '.results[]|.[] | [.line_number, .type]|@csv' | wc -l ;; esac fi diff --git a/include/securityhub_integration b/include/securityhub_integration index 6824f892e8..c5802d03e8 100644 --- a/include/securityhub_integration +++ b/include/securityhub_integration @@ -16,7 +16,7 @@ checkSecurityHubCompatibility(){ local regx - if [[ "${MODE}" != "json-asff" ]]; then + if [[ ! "${MODE}" =~ "json-asff" ]]; then echo -e "\n$RED ERROR!$NORMAL Output can only be sent to Security Hub when the output mode is json-asff, i.e. -M json-asff -S\n" EXITCODE=1 exit $EXITCODE @@ -71,7 +71,7 @@ resolveSecurityHubPreviousFails(){ if [[ $SECURITY_HUB_PREVIOUS_FINDINGS != "[]" ]]; then FINDINGS_COUNT=$(echo $SECURITY_HUB_PREVIOUS_FINDINGS | jq '. | length') for i in $(seq 0 50 $FINDINGS_COUNT); - do + do BATCH_FINDINGS=$(echo $SECURITY_HUB_PREVIOUS_FINDINGS | jq -c '.['"$i:$i+50"']') BATCH_FINDINGS_COUNT=$(echo $BATCH_FINDINGS | jq '. | length') if [ "$BATCH_FINDINGS_COUNT" -gt 0 ]; then @@ -94,7 +94,6 @@ sendToSecurityHub(){ local finding_id=$(echo ${findings} | jq -r .Id ) SECURITYHUB_NEW_FINDINGS_IDS+=( "$finding_id" ) BATCH_IMPORT_RESULT=$($AWSCLI securityhub --region "$region" $PROFILE_OPT batch-import-findings --findings "${findings}" 2>&1) - # Check for success if imported if [[ -z "${BATCH_IMPORT_RESULT}" ]] || ! jq -e '.SuccessCount == 1' <<< "${BATCH_IMPORT_RESULT}" > /dev/null 2>&1; then echo "${BAD} ERROR! Failed to send check output to AWS Security Hub.${NORMAL}" diff --git a/include/show_titles b/include/show_titles new file mode 100644 index 0000000000..0a19485dab --- /dev/null +++ b/include/show_titles @@ -0,0 +1,60 @@ +#!/usr/bin/env bash + +# Copyright 2018 Toni de la Fuente + +# Prowler is a tool that provides automate auditing and hardening guidance of an +# AWS account. It is based on AWS-CLI commands. It follows some guidelines +# present in the CIS Amazon Web Services Foundations Benchmark at: +# https://d0.awsstatic.com/whitepapers/compliance/AWS_CIS_Foundations_Benchmark.pdf + +# Contact the author at https://blyx.com/contact +# and open issues or ask questions at https://github.com/prowler-cloud/prowler + +# Code is licensed as Apache License 2.0 as specified in +# each file. You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 + + +# Show the title of a group, by their numerical id +show_group_title() { + textTitle "${GROUP_NUMBER[$1]}" "${GROUP_TITLE[$1]}" +} + +# Show al group titles +show_all_group_titles() { + local group_index + for group_index in "${!GROUP_TITLE[@]}"; do + show_group_title "$group_index" + done + exit 0 +} + +# Show the titles of either all checks or only those in the specified group +show_all_check_titles() { + for CHECK_ID in ${TOTAL_CHECKS[*]} + do + show_check_title "${CHECK_ID}" + done + exit 0 +} + +# Function to show the title of the check, and optionally which group(s) it belongs to +# using this way instead of arrays to keep bash3 (osx) and bash4(linux) compatibility +show_check_title() { + local check_id=CHECK_ID_$1 + local check_title=CHECK_TITLE_$1 + local check_scored=CHECK_SCORED_$1 + local check_cis_level=CHECK_CIS_LEVEL_$1 + local check_asff_compliance_type=CHECK_ASFF_COMPLIANCE_TYPE_$1 + local check_severity=CHECK_SEVERITY_$1 + local check_servicename=CHECK_SERVICENAME_$1 + local group_ids + local group_index + + # This shows ASFF_COMPLIANCE_TYPE if group used is ens, this si used to show ENS compliance ID control, can be used for other compliance groups as well. + if [[ ${GROUP_ID_READ} == "ens" ]];then + textTitle "${!check_id}" "${!check_title}" "${!check_scored}" "${!check_cis_level}" "$group_ids" "(${!check_asff_compliance_type})" + else + textTitle "${!check_id}" "${!check_title}" "${!check_servicename}" "${!check_severity}" "$group_ids" "${!check_cis_level}" + fi +} diff --git a/include/tools_detector b/include/tools_detector new file mode 100644 index 0000000000..f401e79ef2 --- /dev/null +++ b/include/tools_detector @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# Prowler - the handy cloud security tool (copyright 2018) 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. + +jq_detector() { + if ! command -v jq &>/dev/null + then + echo -e "\n$RED ERROR!$NORMAL jq not found. Make sure it is installed correctly and in your \$PATH\n" + EXITCODE=1 + exit $EXITCODE + fi +} + +curl_detector() { + if ! command -v curl &>/dev/null + then + echo -e "\n$RED ERROR!$NORMAL curl not found. Make sure it is installed correctly and in your \$PATH\n" + EXITCODE=1 + exit $EXITCODE + fi +} diff --git a/include/traps b/include/traps new file mode 100644 index 0000000000..1e4ae7d8cc --- /dev/null +++ b/include/traps @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +# Copyright 2018 Toni de la Fuente + +# Prowler is a tool that provides automate auditing and hardening guidance of an +# AWS account. It is based on AWS-CLI commands. It follows some guidelines +# present in the CIS Amazon Web Services Foundations Benchmark at: +# https://d0.awsstatic.com/whitepapers/compliance/AWS_CIS_Foundations_Benchmark.pdf + +# Contact the author at https://blyx.com/contact +# and open issues or ask questions at https://github.com/prowler-cloud/prowler + +# Code is licensed as Apache License 2.0 as specified in +# each file. You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 + +clean_up() { + rm -f /tmp/prowler*.policy.* + # puts the AWS_DEFAULT_OUTPUT back to what it was at the start + if [ -z "$ORIGINAL_OUTPUT" ]; then + export AWS_DEFAULT_OUTPUT="$ORIGINAL_OUTPUT" + else + unset AWS_DEFAULT_OUTPUT + fi + # Delete temporary report file + + if [[ $KEEPCREDREPORT -ne 1 ]]; then + rm -fr $TEMP_REPORT_FILE + fi + cleanSTSAssumeFile +} + +handle_ctrl_c() { + clean_up + scoring + exit $EXITCODE +} diff --git a/include/validate_options b/include/validate_options new file mode 100644 index 0000000000..a7e2d5bd8d --- /dev/null +++ b/include/validate_options @@ -0,0 +1,233 @@ +#!/usr/bin/env bash + +# Prowler - the handy cloud security tool (copyright 2018) 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. + +# Copy to S3 without assuming role with -D +validate_copy_to_s3_no_assume() { + if [[ $OUTPUT_BUCKET_NOASSUME ]]; then + restoreInitialAWSCredentials + fi +} + +# Copy to S3 assuming role with -B +copy_to_s3_assume() { + if [[ $OUTPUT_BUCKET ]]; then + copyToS3 + fi +} + +# Validate custom checks with -x +validate_custom_checks() { + if [[ $EXTERNAL_CHECKS_PATH ]] + then + custom_checks + fi +} + +# AllowList option input with -w +validate_allowlist() { + # Pre-process allowlist file if supplied + if [[ -n "$ALLOWLIST_FILE" ]] + then + allowlist + fi +} + +# List checks input with -l +validate_list_checks() { + if [[ ${LIST_CHECKS} -eq 1 ]] + then + show_all_check_titles + fi +} + +# List groups input with -g +validate_list_groups() { + if [[ ${LIST_GROUPS} -eq 1 ]] + then + show_all_group_titles + fi +} + +# Extract organizations information with -O +validate_organizations() { + # First, check AWS Organizations Metadata + if [[ -n "${MANAGEMENT_ACCOUNT_ID}" && -n "${ROLE_TO_ASSUME}" ]] + then + # Backing up initial credentials + backupInitialAWSCredentials + + # Backing up initial account to assume + INITIAL_ACCOUNT_TO_ASSUME="${ACCOUNT_TO_ASSUME}" + + # Set the new account to assume to recover AWS Organizations Metadata + ACCOUNT_TO_ASSUME="${MANAGEMENT_ACCOUNT_ID}" + + # Recover AWS Organizations Metadata + get_orgs_account_details + + # Restoring account to assume to -A field after getting account metadata + ACCOUNT_TO_ASSUME="${INITIAL_ACCOUNT_TO_ASSUME}" + + # Restoring initial credentials + restoreInitialAWSCredentials + fi + + if [[ -n "${ACCOUNT_TO_ASSUME}" || -n "${ROLE_TO_ASSUME}" ]] + then + backupInitialAWSCredentials + assume_role + fi +} + +# JUnit output if -M junit-xml +validate_junit_output() { + if is_junit_output_enabled + then + prepare_junit_output + fi +} + +# Validate Security Hub with -S +validate_security_hub() { + if [[ "$SEND_TO_SECURITY_HUB" -eq 1 ]] + then + checkSecurityHubCompatibility + fi +} + +# Validate output bucket with -B +validate_output_bucket() { + # Output bucket parsing input checking + if [[ $OUTPUT_BUCKET ]] + then + # output mode has to be set to other than text + if [[ "${MODE}" =~ "text" ]] + then + echo "$OPTRED ERROR!$OPTNORMAL - Mode (-M) can't be text when using custom output bucket. Use -h for help." + exit 1 + else + # need to make sure last / is not set to avoid // in S3 + if [[ $OUTPUT_BUCKET == *"/" ]] + then + OUTPUT_BUCKET=${OUTPUT_BUCKET::-1} + fi + fi + fi +} + +# Validate custom output filename with -F +validate_custom_output_filename() { + # Create output file name + if [ -n "${OUTPUT_FILE_NAME}" ] + then + OUTPUT_FILE_NAME="${OUTPUT_DIR}/$OUTPUT_FILE_NAME" + else + OUTPUT_FILE_NAME="${OUTPUT_DIR}/prowler-output-${ACCOUNT_NUM}-${OUTPUT_DATE}" + fi +} + +# Validate custom output directory with -o +validate_custom_output_directory() { + # Check output dir custom + if [ -n "${OUTPUT_DIR_CUSTOM}" ] + then + # If dir custom, mode has to be enabled (if you provide a custom directory it's because you want to store something) + if [[ ! "${MODE}" =~ 'text' ]] + then + # Check if custom folder exists + if [ ! -d "${OUTPUT_DIR_CUSTOM}" ] + then + echo "$OPTRED ERROR!$OPTNORMAL directory \"$OUTPUT_DIR_CUSTOM\" does not exist." + exit 1 + else + OUTPUT_DIR=$OUTPUT_DIR_CUSTOM + fi + else + echo "$OPTRED ERROR!$OPTNORMAL - When using custom directory output Mode (-M) has to be set as well. Use -h for help." + exit 1 + fi + # If custom output dir is not provided output dir -> default + else + OUTPUT_DIR="${PROWLER_DIR}/output" + if [ ! -d "${OUTPUT_DIR}" ] + then + mkdir -p "${OUTPUT_DIR}" + fi + fi +} + +# Validate output modes with -M +validate_modes() { + if [ -n "${MODE}" ] + then + AVAILABLE_OUTPUT_MODES="mono|text|csv|json|json-asff|junit-xml|html" + OIFS="${IFS}" + IFS=',' + for MODE_TYPE in ${MODE} + do + if ! grep -w -q -E "${AVAILABLE_OUTPUT_MODES}" <<< "${MODE_TYPE}" + then + echo -e "${OPTRED}ERROR!$OPTNORMAL Invalid output mode. Choose text, mono, csv, json, json-asff, junit-xml or html. ./prowler -h for help" + EXITCODE=1 + exit $EXITCODE + fi + done + IFS="${OIFS}" + fi +} + +# Validate database with -d +validate_database() { + # Check database providers +if [[ ${DATABASE_PROVIDER} ]] +then + # Check if input provider is supported + if ! grep -w -q -E "${DATABASE_PROVIDER}" <<< "${SUPPORTED_DB_PROVIDERS}" + then + db_exit_abnormally "${DATABASE_PROVIDER}" "DB provider not supported, providers supported: ${SUPPORTED_DB_PROVIDERS} - EXITING!" + elif ! command -v "psql" > /dev/null 2>&1 + then + db_exit_abnormally "postgresql" "psql tool not installed or not found- EXITING!" + elif [[ $(find "${HOME}/.pgpass" -perm 600 2>/dev/null) != "$HOME/.pgpass" ]] + then + db_exit_abnormally "postgresql" ".pgpass file not found at $HOME/.pgpass or .pgpass file permissions not matching 600 - EXITING!" + else + IFS=':' read -r PSQL_HOSTNAME PSQL_PORT PSQL_DATABASE PSQL_USER PSQL_PASSWORD PSQL_TABLE < "$HOME/.pgpass" + if [[ ! ${PSQL_HOSTNAME} ]] || [[ ! ${PSQL_PORT} ]] || [[ ! ${PSQL_DATABASE} ]] || [[ ! ${PSQL_USER} ]] || [[ ! ${PSQL_PASSWORD} ]] || [[ ! ${PSQL_TABLE} ]] + then + db_exit_abnormally "postgresql" "Empty field into ${HOME}/.pgpass file, all fields must be filled. Please check Prowler README about .pgpass file format - EXITING!" + fi + export PSQL_USER + export PSQL_HOSTNAME + export PSQL_TABLE + + # Once all the variables are defined and exported test if database instance is reachable + if ! pg_isready -q -U "${PSQL_USER}" -h "${PSQL_HOSTNAME}" -p "${PSQL_PORT}" + then + db_exit_abnormally "postgresql" "Database listening on host ${PSQL_HOSTNAME} on port ${PSQL_PORT} connected as user ${PSQL_USER} is unreachable - EXITING!" + # If database instance is ready time to check credentials + elif ! PGPASSWORD="${PSQL_PASSWORD}" psql -U "${PSQL_USER}" -h "${PSQL_HOSTNAME}" -c "\q" > /dev/null 2>&1 + then + db_exit_abnormally "postgresql" "User ${PSQL_USER} invalid or invalid credentials, please check ${HOME}/.pgpass file - EXITING!" + # If credentials are ok -> database exists? + elif ! PGPASSWORD="${PSQL_PASSWORD}" psql -U "${PSQL_USER}" -h "${PSQL_HOSTNAME}" "${PSQL_DATABASE}" -c "\q" > /dev/null 2>&1 + then + db_exit_abnormally "postgresql" "Database not exists, please check ${HOME}/.pgpass file - EXITING!" + # and finally, if database exists -> table exists ? + elif ! PGPASSWORD="${PSQL_PASSWORD}" psql -U "${PSQL_USER}" -h "${PSQL_HOSTNAME}" "${PSQL_DATABASE}" -c "SELECT * FROM ${PSQL_TABLE};" > /dev/null 2>&1 + then + db_exit_abnormally "postgresql" "Table ${PSQL_TABLE} not exists, please check ${HOME}/.pgpass file - EXITING!" + fi + fi + fi +} diff --git a/include/whoami b/include/whoami index eb61f6b6bc..edb2e98b30 100644 --- a/include/whoami +++ b/include/whoami @@ -19,77 +19,41 @@ # when invoking regions with -r, those regions with STS disabled make GETCALLER fail then # this if will filter them out (Africa (Cape Town), Asia Pacific (Hong Kong), Europe (Milan) and Middle East (Bahrain) ): +print_whoami(){ + echo -e "\n This report is being generated using credentials below:\n" + echo -e " AWS-CLI Profile: ${NOTICE}[${PROFILE}]${NORMAL} AWS API Region: ${NOTICE}[${REGION}]${NORMAL} AWS Filter Region: ${NOTICE}[${FILTERREGION:-all}]${NORMAL}" + echo -e " AWS Account: ${NOTICE}[${ACCOUNT_NUM}]${NORMAL} UserId: ${NOTICE}[${USER_ID}]${NORMAL}" + echo -e " Caller Identity ARN: ${NOTICE}[${CALLER_ARN}]${NORMAL}\n" +} -case "$REGION" in - me-south-1|eu-south-1|ap-east-1|af-south-1) - REGION_FOR_STS="us-east-1" - ;; - *) - REGION_FOR_STS=$REGION - ;; -esac +get_aws_credentials() { + case "$REGION" in + me-south-1|eu-south-1|ap-east-1|af-south-1) + REGION_FOR_STS="us-east-1" + ;; + *) + REGION_FOR_STS=$REGION + ;; + esac -GETCALLER=$($AWSCLI sts get-caller-identity $PROFILE_OPT --output json --region $REGION_FOR_STS 2>&1) -ret=$? -if [[ $ret -ne 0 ]]; then - if [[ $PRINTCHECKSONLY || $PRINTGROUPSONLY ]]; then - echo Listing... - else - # Failed to get own identity ... exit + if ! GETCALLER=$("${AWSCLI}" sts get-caller-identity \ + ${PROFILE_OPT} \ + --output text \ + --region "${REGION_FOR_STS}" \ + --query '[Arn,UserId,Account]' 2>&1) + then echo -e "$RED ERROR Getting credentials to run Prowler - EXITING! $NORMAL" EXITCODE=2 exit $EXITCODE fi -fi + read -r CALLER_ARN USER_ID ACCOUNT_NUM <<< "${GETCALLER}" -if [[ $ACCOUNT_TO_ASSUME ]]; then - ACCOUNT_NUM=$ACCOUNT_TO_ASSUME -else - ACCOUNT_NUM=$(echo $GETCALLER | jq -r '.Account' 2>&1) -fi - -CALLER_ARN=$(echo $GETCALLER | jq -r '.Arn' 2>&1) -USER_ID=$(echo $GETCALLER | jq -r '.UserId' 2>&1) -AWS_PARTITION=$(echo $CALLER_ARN| cut -d: -f2) - -getWhoami(){ - if [[ "$MODE" == "csv" ]]; then - if [[ 255 -eq $? ]]; then - # Failed to get own identity ... exit - echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!" - >&2 echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!" - EXITCODE=2 - exit $EXITCODE - fi - # textTitle "0.0" "Show report generation info" "NOT_SCORED" "SUPPORT" - # textInfo "ARN: $CALLER_ARN TIMESTAMP: $SCRIPT_START_TIME" - elif [[ "$MODE" == "json" || "$MODE" == "json-asff" ]]; then - : - else - echo "" - echo -e " This report is being generated using credentials below:\n" - echo -e " AWS-CLI Profile: $NOTICE[$PROFILE]$NORMAL AWS API Region: $NOTICE[$REGION]$NORMAL AWS Filter Region: $NOTICE[${FILTERREGION:-all}]$NORMAL" - if [[ $MONOCHROME -eq 1 ]]; then - echo -e " AWS Account: $NOTICE[$ACCOUNT_NUM]$NORMAL UserId: $NOTICE[$USER_ID]$NORMAL" - echo -e " Caller Identity ARN: $NOTICE[$CALLER_ARN]$NORMAL" - if [[ 255 -eq $? ]]; then - # Failed to get own identity ... exit - echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!" - >&2 echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!" - exit 2 - fi - else - echo -e " AWS Account: $NOTICE[$ACCOUNT_NUM]$NORMAL UserId: $NOTICE[$USER_ID]$NORMAL" - echo -e " Caller Identity ARN: $NOTICE[$CALLER_ARN]$NORMAL" - if [[ 255 -eq $? ]]; then - # Failed to get own identity ... exit - echo variable $PROFILE_OPT - echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!" - >&2 echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!" - EXITCODE=2 - exit $EXITCODE - fi - echo "" - fi + if [[ $ACCOUNT_TO_ASSUME ]] + then + ACCOUNT_NUM=$ACCOUNT_TO_ASSUME fi + + # Set AWS partition + AWS_PARTITION=$(cut -d: -f2 <<< "${CALLER_ARN}") + export AWS_PARTITION } diff --git a/prowler b/prowler index fa05cf4101..69bc3eefb9 100755 --- a/prowler +++ b/prowler @@ -22,62 +22,70 @@ # All there legs and lashes # I've just got to find my way... -OPTRED="" -OPTNORMAL="" - # Set the defaults variables PROWLER_VERSION=2.10.0-25May2022 PROWLER_DIR=$(dirname "$0") -REGION="" -FILTERREGION="" -MAXITEMS=100 -MONOCHROME=0 -MODE="text" -QUIET=0 -SEP=',' -KEEPCREDREPORT=0 -EXITCODE=0 -SEND_TO_SECURITY_HUB=0 -FAILED_CHECK_FAILED_SCAN=1 -PROWLER_START_TIME=$( date -u +"%Y-%m-%dT%H:%M:%S%z" ) -OUTPUT_DATE=$(date -u +"%Y%m%d%H%M%S") -TITLE_ID="" -TITLE_TEXT="CALLER ERROR - UNSET TITLE" -ALLOWLIST_FILE="" -TOTAL_CHECKS=() +############################################################ +# DEPENDENCIES +. $PROWLER_DIR/include/default_variables +. $PROWLER_DIR/include/colors +. $PROWLER_DIR/include/os_detector +. $PROWLER_DIR/include/aws_profile_loader +. $PROWLER_DIR/include/awscli_detector +. $PROWLER_DIR/include/whoami +. $PROWLER_DIR/include/assume_role +. $PROWLER_DIR/include/csv_header +. $PROWLER_DIR/include/banner +. $PROWLER_DIR/include/html_report +. $PROWLER_DIR/include/tools_detector +. $PROWLER_DIR/include/outputs_bucket +. $PROWLER_DIR/include/outputs +. $PROWLER_DIR/include/credentials_report +. $PROWLER_DIR/include/scoring +. $PROWLER_DIR/include/secrets_detector +. $PROWLER_DIR/include/check_creds_last_used +. $PROWLER_DIR/include/check3x +. $PROWLER_DIR/include/connection_tests +. $PROWLER_DIR/include/securityhub_integration +. $PROWLER_DIR/include/junit_integration +. $PROWLER_DIR/include/organizations_metadata +. $PROWLER_DIR/include/custom_checks +. $PROWLER_DIR/include/allowlist +. $PROWLER_DIR/include/db_connector +. $PROWLER_DIR/include/show_titles +. $PROWLER_DIR/include/loader +. $PROWLER_DIR/include/execute_check +. $PROWLER_DIR/include/validate_options +. $PROWLER_DIR/include/traps +############################################################ +# Check external tools +jq_detector +curl_detector -# Ensures command output will always be set to JSON. -# If the default value is already set, ORIGINAL_OUTPUT will be used to store it and reset it at cleanup -if [[ -z "${AWS_DEFAULT_OUTPUT}" ]]; then - ORIGINAL_OUTPUT=$AWS_DEFAULT_OUTPUT - export AWS_DEFAULT_OUTPUT="json" -else - export AWS_DEFAULT_OUTPUT="json" -fi - -# Command usage menu +############################################################ +# USAGE usage(){ echo " USAGE: - `basename $0` [ -p -r -h ] + $(basename "${0}") [ -p -r -h ] Options: -p Specify your AWS profile to use. (i.e.: default) -r Specify an AWS region to direct API requests to. (i.e.: us-east-1), all regions are checked anyway if the check requires it. - -c Specify one or multiple check ids separated by commas, to see all available checks use "-l" option. - (i.e.: "check11" for check 1.1 or "extra71,extra72" for extra check 71 and extra check 72) + -c Specify one or multiple check ids separated by commas, to see all available checks use \"-l\" option. + (i.e.: \"check11\" for check 1.1 or \"extra71,extra72\" for extra check 71 and extra check 72) -C Checklist file. See checklist.txt for reference and format. (i.e.: checklist.txt) - -g Specify a group of checks by id, to see all available group of checks use "-L". - (i.e.: "group3" for entire section 3, "cislevel1" for CIS Level 1 Profile Definitions or "forensics-ready") + -g Specify a group of checks by id, to see all available group of checks use \"-L\". + (i.e.: \"group3\" for entire section 3, \"cislevel1\" for CIS Level 1 Profile Definitions or \"forensics-ready\") -f Specify an AWS region to run checks against. (i.e.: us-west-1 or for multiple regions use single quote like 'us-west-1 us-west-2') -m Specify the maximum number of items to return for long-running requests (default: 100). -M Output or report mode: text (default), mono, html, json, json-asff, junit-xml, csv. They can be used combined comma separated. - (i.e.: "html,json"; files created in background; progress on stdout) + (i.e.: \"html,json\"; files created in background; progress on stdout) -k Keep the credential report for debugging. -n Show check numbers to sort easier. (i.e.: 1.01 instead of 1.1) @@ -89,9 +97,9 @@ USAGE: -b Do not print Prowler banner. -s Show scoring report (it is included by default in the html report). -S Send check output to AWS Security Hub. Only valid when the output mode is json-asff - (i.e. "-M json-asff -S"). + (i.e. \"-M json-asff -S\"). -x Specify external directory with custom checks. S3 URI is supported. - (i.e. /my/own/checks or s3://bucket/prefix/checks, files must start by "check"). + (i.e. /my/own/checks or s3://bucket/prefix/checks, files must start by \"check\"). -q Get only FAIL findings, will show WARNINGS when a resource is excluded. -A Account id for the account where to assume a role, requires -R. (i.e.: 123456789012) @@ -100,8 +108,8 @@ USAGE: -T Session duration given to that role credentials in seconds, default 1h (3600) recommended 12h, optional with -R and -A. (i.e.: 43200) -I External ID to be used when assuming roles (not mandatory), requires -A and -R. - -w Allowlist file. See allowlist_sample.txt for reference and format. S3 URI is supported. - (i.e.: allowlist_sample.txt or s3://bucket/prefix/allowlist_sample.txt) + -w Allowlist file. See allowlist_sample.txt for reference and format. S3 URI and DynamoDB table ARNs are also supported as allowlist file. + (i.e.: allowlist_sample.txt or s3://bucket/prefix/allowlist_sample.txt or arn:aws:dynamodb:us-east-1:111111222222:table/allowlist) -N Shodan API key used by check extra7102. -o Custom output directory, if not specified will use default prowler/output, requires -M . (i.e.: -M csv -o /tmp/reports/) @@ -110,11 +118,11 @@ USAGE: -D Same as -B but do not use the assumed role credentials to put objects to the bucket, instead uses the initial credentials. -F Custom output report name, if not specified will use default output/prowler-output-ACCOUNT_NUM-OUTPUT_DATE.format. -z Failed checks do not trigger exit code 3. - -Z Specify one or multiple check ids separated by commas that will trigger exit code 3 if they fail. Unspecified checks will not trigger exit code 3. This will override "-z". - (i.e.: "-Z check11,check12" will cause check11 and/or check12 to trigger exit code 3) + -Z Specify one or multiple check ids separated by commas that will trigger exit code 3 if they fail. Unspecified checks will not trigger exit code 3. This will override \"-z\". + (i.e.: \"-Z check11,check12\" will cause check11 and/or check12 to trigger exit code 3) -O Specify AWS Organizations management account ID. Used to get account details, requires -R. (requires organizations:ListAccounts* and organizations:ListTagsForResource) - -a Build your own on-the-fly custom check by specifying the AWS CLI command to execute. Requires "-c extra9999". Omit the "aws" command and only use its parameters within quotes. + -a Build your own on-the-fly custom check by specifying the AWS CLI command to execute. Requires \"-c extra9999\". Omit the \"aws\" command and only use its parameters within quotes. Do not nest quotes in the aws parameter. Note that --output text is already included in the check. i,e. -a 'ec2 describe-security-groups --filters Name=ip-permission.to-port,Values=80 --query SecurityGroups[*].GroupId[]]' -V Show version number & exit. @@ -124,6 +132,11 @@ USAGE: exit } +############################################################ +# This function comes from include/awscli_detector +set_aws_default_output + +# Parse Prowler command line options while getopts ":hlLkqp:r:c:C:g:f:m:M:E:x:enbVsSI:A:R:T:w:N:o:B:D:F:zZ:O:a:d:" OPTION; do case $OPTION in h ) @@ -132,10 +145,10 @@ while getopts ":hlLkqp:r:c:C:g:f:m:M:E:x:enbVsSI:A:R:T:w:N:o:B:D:F:zZ:O:a:d:" OP exit $EXITCODE ;; l ) - PRINTCHECKSONLY=1 + LIST_CHECKS=1 ;; L ) - PRINTGROUPSONLY=1 + LIST_GROUPS=1 ;; k ) KEEPCREDREPORT=1 @@ -160,7 +173,7 @@ while getopts ":hlLkqp:r:c:C:g:f:m:M:E:x:enbVsSI:A:R:T:w:N:o:B:D:F:zZ:O:a:d:" OP FILTERREGION=$OPTARG ;; m ) - MAXITEMS=$OPTARG + export MAXITEMS=$OPTARG ;; M ) MODE=$OPTARG @@ -210,7 +223,7 @@ while getopts ":hlLkqp:r:c:C:g:f:m:M:E:x:enbVsSI:A:R:T:w:N:o:B:D:F:zZ:O:a:d:" OP ALLOWLIST_FILE=$OPTARG ;; N ) - SHODAN_API_KEY=$OPTARG + export SHODAN_API_KEY=$OPTARG ;; o ) OUTPUT_DIR_CUSTOM=$OPTARG @@ -235,7 +248,7 @@ while getopts ":hlLkqp:r:c:C:g:f:m:M:E:x:enbVsSI:A:R:T:w:N:o:B:D:F:zZ:O:a:d:" OP MANAGEMENT_ACCOUNT_ID=$OPTARG ;; a ) - CUSTOM_CMD=$OPTARG + export CUSTOM_CMD=$OPTARG ;; d ) DATABASE_PROVIDER=$OPTARG @@ -256,25 +269,19 @@ while getopts ":hlLkqp:r:c:C:g:f:m:M:E:x:enbVsSI:A:R:T:w:N:o:B:D:F:zZ:O:a:d:" OP ;; esac done +############################################################ +# PROWLER MAIN -clean_up() { - rm -f /tmp/prowler*.policy.* - # in case html output is used, make sure it closes html file properly - if [[ "${MODES[@]}" =~ "html" ]]; then - addHtmlFooter >> ${OUTPUT_FILE_NAME}.$EXTENSION_HTML - fi - # puts the AWS_DEFAULT_OUTPUT back to what it was at the start - if [ -z "$ORIGINAL_OUTPUT" ]; then - export AWS_DEFAULT_OUTPUT="$ORIGINAL_OUTPUT" - else - unset AWS_DEFAULT_OUTPUT - fi -} +# Set Prowler Colors +set_colors -handle_ctrl_c() { - clean_up - exit $EXITCODE -} +# Show Prowler Banner +prowlerBanner + +# Load Prowler Groups +load_groups +# Load Prowler Checks +get_checks # Clean up any temp files when prowler quits unexpectedly trap clean_up EXIT @@ -284,583 +291,55 @@ trap handle_ctrl_c INT # Environment variable takes precedence over command line unset AWS_DEFAULT_OUTPUT -. $PROWLER_DIR/include/colors -. $PROWLER_DIR/include/os_detector -. $PROWLER_DIR/include/aws_profile_loader -. $PROWLER_DIR/include/awscli_detector -. $PROWLER_DIR/include/whoami -. $PROWLER_DIR/include/assume_role -. $PROWLER_DIR/include/csv_header -. $PROWLER_DIR/include/banner -. $PROWLER_DIR/include/html_report -. $PROWLER_DIR/include/outputs_bucket -. $PROWLER_DIR/include/outputs -. $PROWLER_DIR/include/credentials_report -. $PROWLER_DIR/include/scoring -. $PROWLER_DIR/include/python_detector -. $PROWLER_DIR/include/secrets_detector -. $PROWLER_DIR/include/check_creds_last_used -. $PROWLER_DIR/include/check3x -. $PROWLER_DIR/include/connection_tests -. $PROWLER_DIR/include/securityhub_integration -. $PROWLER_DIR/include/junit_integration -. $PROWLER_DIR/include/organizations_metadata -. $PROWLER_DIR/include/custom_checks -. $PROWLER_DIR/include/allowlist -. $PROWLER_DIR/include/db_connector +# Check OS +os_detector +TIMESTAMP=$(get_iso8601_timestamp) +# Validate command line options that not requires AWS +validate_list_checks +validate_list_groups +validate_database +validate_modes +validate_output_bucket -#Parsing input options -# Parses the check file into CHECK_ID's. -if [[ -n "$CHECK_FILE" ]]; then - if [[ -f $CHECK_FILE ]]; then - # Parses the file, converting it to a comma seperated list. Ignores all # comments and removes extra blank spaces - CHECK_ID="$(awk '!/^[[:space:]]*#/{print }' <(cat $CHECK_FILE | sed 's/[[:space:]]*#.*$//g;/^$/d' | sed 'H;1h;$!d;x;y/\n/,/' | tr -d ' '))" - else - # If the file doesn't exist, exits Prowler - echo "$CHECK_FILE does not exist" - EXITCODE=1 - exit $EXITCODE - fi -fi - -# Check database providers -if [[ ${DATABASE_PROVIDER} ]] -then - # Check if input provider is supported - if ! grep -w -q -E "${DATABASE_PROVIDER}" <<< "${SUPPORTED_DB_PROVIDERS}" - then - db_exit_abnormally "${DATABASE_PROVIDER}" "DB provider not supported, providers supported: ${SUPPORTED_DB_PROVIDERS} - EXITING!" - elif ! command -v "psql" > /dev/null 2>&1 - then - db_exit_abnormally "postgresql" "psql tool not installed or not found- EXITING!" - elif [[ $(find "${HOME}/.pgpass" -perm 600 2>/dev/null) != "$HOME/.pgpass" ]] - then - db_exit_abnormally "postgresql" ".pgpass file not found at $HOME/.pgpass or .pgpass file permissions not matching 600 - EXITING!" - else - IFS=':' read -r PSQL_HOSTNAME PSQL_PORT PSQL_DATABASE PSQL_USER PSQL_PASSWORD PSQL_TABLE < "$HOME/.pgpass" - if [[ ! ${PSQL_HOSTNAME} ]] || [[ ! ${PSQL_PORT} ]] || [[ ! ${PSQL_DATABASE} ]] || [[ ! ${PSQL_USER} ]] || [[ ! ${PSQL_PASSWORD} ]] || [[ ! ${PSQL_TABLE} ]] - then - db_exit_abnormally "postgresql" "Empty field into ${HOME}/.pgpass file, all fields must be filled. Please check Prowler README about .pgpass file format - EXITING!" - fi - export PSQL_USER - export PSQL_HOSTNAME - export PSQL_TABLE - - # Once all the variables are defined and exported test if database instance is reachable - if ! pg_isready -q -U "${PSQL_USER}" -h "${PSQL_HOSTNAME}" -p "${PSQL_PORT}" - then - db_exit_abnormally "postgresql" "Database listening on host ${PSQL_HOSTNAME} on port ${PSQL_PORT} connected as user ${PSQL_USER} is unreachable - EXITING!" - # If database instance is ready time to check credentials - elif ! PGPASSWORD="${PSQL_PASSWORD}" psql -U "${PSQL_USER}" -h "${PSQL_HOSTNAME}" -c "\q" > /dev/null 2>&1 - then - db_exit_abnormally "postgresql" "User ${PSQL_USER} invalid or invalid credentials, please check ${HOME}/.pgpass file - EXITING!" - # If credentials are ok -> database exists? - elif ! PGPASSWORD="${PSQL_PASSWORD}" psql -U "${PSQL_USER}" -h "${PSQL_HOSTNAME}" "${PSQL_DATABASE}" -c "\q" > /dev/null 2>&1 - then - db_exit_abnormally "postgresql" "Database not exists, please check ${HOME}/.pgpass file - EXITING!" - # and finally, if database exists -> table exists ? - elif ! PGPASSWORD="${PSQL_PASSWORD}" psql -U "${PSQL_USER}" -h "${PSQL_HOSTNAME}" "${PSQL_DATABASE}" -c "SELECT * FROM ${PSQL_TABLE};" > /dev/null 2>&1 - then - db_exit_abnormally "postgresql" "Table ${PSQL_TABLE} not exists, please check ${HOME}/.pgpass file - EXITING!" - fi - - - fi -fi - -# Check modes -if [ -n "${MODE}" ] -then - IFS=',' read -ra MODES <<< "${MODE}" - for MODE in "${MODES[@]}"; do - if [[ "$MODE" != "mono" && "$MODE" != "text" && "$MODE" != "csv" && "$MODE" != "json" && "$MODE" != "json-asff" && "$MODE" != "junit-xml" && "$MODE" != "html" ]]; then - echo -e "${OPTRED}ERROR!$OPTNORMAL Invalid output mode. Choose text, mono, csv, json, json-asff, junit-xml or html. ./prowler -h for help" - EXITCODE=1 - exit $EXITCODE - fi - done -fi - -#Check output dir custom -if [ -n "${OUTPUT_DIR_CUSTOM}" ] -then - # If dir custom, mode has to be enabled (if you provide a custom directory it's because you want to store something) - if [ "${MODE}" != 'text' ] - then - # Check if custom folder exists - if [ ! -d "${OUTPUT_DIR_CUSTOM}" ] - then - echo "$OPTRED ERROR!$OPTNORMAL directory \"$OUTPUT_DIR_CUSTOM\" does not exist." - exit 1 - else - OUTPUT_DIR=$OUTPUT_DIR_CUSTOM - fi - else - echo "$OPTRED ERROR!$OPTNORMAL - When using custom directory output Mode (-M) has to be set as well. Use -h for help." - exit 1 - fi - # If custom output dir is not provided output dir -> default - else - OUTPUT_DIR="${PROWLER_DIR}/output" - if [ ! -d "${OUTPUT_DIR}" ] - then - mkdir -p "${OUTPUT_DIR}" - fi - fi - - # End of input parameters tests - -# Pre-process allowlist file if supplied -if [[ -n "$ALLOWLIST_FILE" ]]; then - allowlist -fi - -# Create output file name -if [ -n "${OUTPUT_FILE_NAME}" ] -then - OUTPUT_FILE_NAME="${OUTPUT_DIR}/$OUTPUT_FILE_NAME" -else - OUTPUT_FILE_NAME="${OUTPUT_DIR}/prowler-output-${ACCOUNT_NUM}-${OUTPUT_DATE}" -fi - -# Add headers to certain output files -output_files_init - - - -# Load all of the groups of checks inside groups folder named as "groupNumber*" -for group in $(ls $PROWLER_DIR/groups/group[0-9]*|grep -v groupN_sample); do - . "$group" -done - -# Load all of the checks inside checks folder named as "check*" -# this includes also extra checks since they are "check_extraNN" -for checks in $(ls $PROWLER_DIR/checks/check*|grep -v check_sample); do - . "$checks" -done - -# include checks if external folder is specified -if [[ $EXTERNAL_CHECKS_PATH ]]; then - custom_checks -fi - -# Get a list of total checks available by ID -for i in "${!GROUP_TITLE[@]}"; do - IFS=',' read -ra CHECKS <<< "${GROUP_CHECKS[$i]}" - for j in "${CHECKS[@]}"; do - TOTAL_CHECKS+=("$CHECK_ID_$j") - done -done -# Remove duplicates, sort checks numerically, and store the result as an array -# Note: the sort mechanism relies on the fact that the check ID prefixes 'check' and 'extra' are both 5 characters long. -# 6th character is the section number, 7th character onwards is the individual ID (e.g. check110 = check 1 10) -TOTAL_CHECKS=($(echo "${TOTAL_CHECKS[*]}" | tr ' ' '\n' | awk '!seen[$0]++' | sort -k 1.6,1.6n -k 1.7n)) - -# Function to get all regions -get_regions() { - # Get list of regions based on include/whoami - REGIONS=$($AWSCLI ec2 describe-regions --query 'Regions[].RegionName' --output text $PROFILE_OPT --region $REGION_FOR_STS --region-names ${FILTERREGION//[,]/ } 2>&1) - ret=$? - if [[ $ret -ne 0 ]]; then - echo "$OPTRED Access Denied trying to describe regions! Review permissions as described here: https://github.com/prowler-cloud/prowler/#requirements-and-installation $OPTNORMAL" - EXITCODE=1 - exit $EXITCODE - fi -} - -# Function to show the title of the check, and optionally which group(s) it belongs to -# using this way instead of arrays to keep bash3 (osx) and bash4(linux) compatibility -show_check_title() { - local check_id=CHECK_ID_$1 - local check_title=CHECK_TITLE_$1 - local check_scored=CHECK_SCORED_$1 - local check_cis_level=CHECK_CIS_LEVEL_$1 - local check_asff_compliance_type=CHECK_ASFF_COMPLIANCE_TYPE_$1 - local check_severity=CHECK_SEVERITY_$1 - local check_servicename=CHECK_SERVICENAME_$1 - local group_ids - local group_index - local check_name - # If requested ($2 is any non-null value) iterate all GROUP_CHECKS and produce a comma-separated list of all - # the GROUP_IDs that include this particular check - if [[ -n "$2" ]]; then - for group_index in "${!GROUP_ID[@]}"; do - for check_name in $(echo "${GROUP_CHECKS[$group_index]}" | sed "s/,/ /g");do - if [[ "$check_name" == "$1" ]]; then - if [[ -n "$group_ids" ]]; then - group_ids+=", " - fi - group_ids+="${GROUP_ID[$group_index]}" - fi - done - done - fi - # This shows ASFF_COMPLIANCE_TYPE if group used is ens, this si used to show ENS compliance ID control, can be used for other compliance groups as well. - if [[ ${GROUP_ID_READ} == "ens" ]];then - textTitle "${!check_id}" "${!check_title}" "${!check_scored}" "${!check_cis_level}" "$group_ids" "(${!check_asff_compliance_type})" - else - textTitle "${!check_id}" "${!check_title}" "${!check_servicename}" "${!check_severity}" "$group_ids" "${!check_cis_level}" - fi -} - -# Function to show the title of a group, by numeric id -show_group_title() { - # when csv mode is used, no group title is shown - if [[ "$MODE" != "csv" ]]; then - textTitle "${GROUP_NUMBER[$1]}" "${GROUP_TITLE[$1]}" - fi -} - -# Function to execute the check -execute_check() { - if [[ -n "${ACCOUNT_TO_ASSUME}" || -n "${ROLE_TO_ASSUME}" ]]; then - # echo ******* I am here again to check on my role ******* - # Following logic looks for time remaining in the session and review it - # if it is less than 600 seconds, 10 minutes. - CURRENT_TIMESTAMP=$(date -u "+%s") - SESSION_TIME_REMAINING=$(expr $AWS_SESSION_EXPIRATION - $CURRENT_TIMESTAMP) - # echo SESSION TIME REMAINING IN SECONDS: $SESSION_TIME_REMAINING - MINIMUM_REMAINING_TIME_ALLOWED=600 - if (( $MINIMUM_REMAINING_TIME_ALLOWED > $SESSION_TIME_REMAINING )); then - # echo LESS THAN 10 MIN LEFT: RE-ASSUMING... - unset AWS_ACCESS_KEY_ID - unset AWS_SECRET_ACCESS_KEY - unset AWS_SESSION_TOKEN - assume_role - fi - fi - - CHECK_ID="$1" - - # See if this is an alternate name for a check - # for example, we might have been passed 1.01 which is another name for 1.1 - local alternate_name_var=CHECK_ALTERNATE_$1 - local alternate_name=${!alternate_name_var} - # See if this check defines an ASFF Type, if so, use this, falling back to a sane default - # For a list of Types Taxonomy, see: https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-findings-format-type-taxonomy.html - local asff_type_var=CHECK_ASFF_TYPE_$1 - CHECK_ASFF_TYPE="${!asff_type_var:-Software and Configuration Checks}" - - local asff_compliance_type_var=CHECK_ASFF_COMPLIANCE_TYPE_$1 - CHECK_ASFF_COMPLIANCE_TYPE="${!asff_compliance_type_var:-Software and Configuration Checks}" - - # See if this check defines an ASFF Resource Type, if so, use this, falling back to a sane default - # For a list of Resource Types, see: https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-findings-format.html#asff-resources - local asff_resource_type_var=CHECK_ASFF_RESOURCE_TYPE_$1 - CHECK_ASFF_RESOURCE_TYPE="${!asff_resource_type_var:-AwsAccount}" - - local severity_var=CHECK_SEVERITY_$1 - CHECK_SEVERITY="${!severity_var}" - - local servicename_var=CHECK_SERVICENAME_$1 - CHECK_SERVICENAME="${!servicename_var}" - - local risk_var=CHECK_RISK_$1 - CHECK_RISK="${!risk_var}" - - local remediation_var=CHECK_REMEDIATION_$1 - CHECK_REMEDIATION="${!remediation_var}" - - local doc_var=CHECK_DOC_$1 - CHECK_DOC="${!doc_var}" - - local caf_epic_var=CHECK_CAF_EPIC_$1 - CHECK_CAF_EPIC="${!caf_epic_var}" - - SECURITYHUB_NEW_FINDINGS_IDS=() - - # Generate the credential report, only if it is group1 related which checks we - # run so that the checks can safely assume it's available - # set the custom ignores list for this check - ignores="$(awk "/${1}/{print}" <(echo "${ALLOWLIST}"))" - - if [ ${alternate_name} ];then - if [[ ${alternate_name} == check1* || ${alternate_name} == extra71 || ${alternate_name} == extra774 || ${alternate_name} == extra7123 ]];then - if [ ! -s $TEMP_REPORT_FILE ];then - genCredReport - saveReport - fi - fi - show_check_title ${alternate_name} - if is_junit_output_enabled; then - prepare_junit_check_output "$1" - fi - # Execute the check - IGNORES="${ignores}" CHECK_NAME="$1" ${alternate_name} - if is_junit_output_enabled; then - finalise_junit_check_output "$1" - fi - - if [[ "$SEND_TO_SECURITY_HUB" -eq 1 ]]; then - resolveSecurityHubPreviousFails "$1" - fi - else - # Check to see if this is a real check - local check_id_var=CHECK_ID_$1 - local check_id=${!check_id_var} - if [ ${check_id} ]; then - if [[ ${check_id} == 1* || ${check_id} == 7.1 || ${check_id} == 7.74 || ${check_id} == 7.123 ]];then - if [ ! -s $TEMP_REPORT_FILE ];then - genCredReport - saveReport - fi - fi - show_check_title "$1" - if is_junit_output_enabled; then - prepare_junit_check_output "$1" - fi - # Execute the check - IGNORES="${ignores}" CHECK_NAME="$1" $1 - - if is_junit_output_enabled; then - finalise_junit_check_output "$1" - fi - - if [[ "$SEND_TO_SECURITY_HUB" -eq 1 ]]; then - resolveSecurityHubPreviousFails "$1" - fi - - else - textFail "Check ${CHECK_ID} does not exist. Use a valid check name (i.e. check41 or extra71)"; - exit $EXITCODE - fi - fi -} - -# Function to execute all checks in a group -execute_group() { - show_group_title $1 - # run the checks in the group - IFS=',' read -ra CHECKS <<< ${GROUP_CHECKS[$1]} - - # Exclude any checks specified - if [[ -n ${2} ]]; then - EXCLUDED_CHECKS=() - NEW_CHECKS=() - IFS=',' read -ra EXCLUDED_CHECKS <<< "${2}," - for exc in ${EXCLUDED_CHECKS[@]} ; do - for i in ${!CHECKS[@]} ; do - [[ ${CHECKS[i]} = ${exc} ]] && unset CHECKS[i] - done - done - unset EXCLUDED_CHECKS - fi - for i in ${CHECKS[@]}; do - execute_check ${i} - done -} - -# Function to execute group by name -execute_group_by_id() { - for i in "${!GROUP_ID[@]}"; do - if [ "${GROUP_ID[$i]}" == "$1" ]; then - execute_group ${i} $2 - fi - done -} - -# Function to execute all checks in all groups except extras if -e is invoked -execute_all() { - for i in "${!GROUP_TITLE[@]}"; do - if [[ $EXTRAS ]]; then - GROUP_RUN_BY_DEFAULT[7]='N' - fi - if [ "${GROUP_RUN_BY_DEFAULT[$i]}" == "Y" ]; then - execute_group $i - fi - done -} - -# Function to show the titles of either all checks or only those in the specified group -show_all_titles() { - local checks - local check_id - local group_index - # If '-g ' has been specified, only show the titles of checks within the specified group - if [[ $GROUP_ID_READ ]];then - if [[ " ${GROUP_ID[@]} " =~ " ${GROUP_ID_READ} " ]]; then - for group_index in "${!GROUP_ID[@]}"; do - if [ "${GROUP_ID[$group_index]}" == "${GROUP_ID_READ}" ]; then - show_group_title "$group_index" - IFS=',' read -ra checks <<< "${GROUP_CHECKS[$group_index]}" - for check_id in ${checks[@]}; do - show_check_title "$check_id" - done - fi - done - else - textFail "Group ${GROUP_ID_READ} does not exist. Use a valid check group ID i.e.: group1, extras, forensics-ready, etc." - show_all_group_titles - exit $EXITCODE - fi - else - for check_id in "${TOTAL_CHECKS[@]}"; do - # Pass 1 so that the group IDs that this check belongs to are printed - show_check_title "$check_id" 1 - done - fi -} - -show_all_group_titles() { - local group_index - for group_index in "${!GROUP_TITLE[@]}"; do - show_group_title "$group_index" - done -} - -# Function to execute all checks but exclude some of them -get_all_checks_without_exclusion() { - CHECKS_EXCLUDED=() - local CHECKS_TO_EXCLUDE=() - # Get a list of checks to exclude - IFS=',' read -ra E_CHECKS <<< "$1" - for E_CHECK in "${E_CHECKS[@]}"; do - CHECKS_TO_EXCLUDE+=($E_CHECK) - done - # Create a list that contains all checks but excluded ones - for i in "${TOTAL_CHECKS[@]}"; do - local COINCIDENCE=false - for x in "${CHECKS_TO_EXCLUDE[@]}"; do - if [[ "$i" == "$x" ]]; then - COINCIDENCE=true - fi - done - if [[ "$COINCIDENCE" = false ]]; then - CHECKS_EXCLUDED+=($i) - fi - done -} - -### All functions defined above ... run the workflow -#if [[ " ${MODES[@]} " =~ " mono " || " ${MODES[@]} " =~ " text " ]]; then - prowlerBanner -#fi - -# List only check tittles -if [[ $PRINTCHECKSONLY == "1" ]]; then - show_all_titles - exit $EXITCODE -fi - -# List only group tittles -if [[ $PRINTGROUPSONLY == "1" ]]; then - show_all_group_titles - exit $EXITCODE -fi - -# Check that jq is installed for JSON outputs -if [[ " ${MODES[@]} " =~ " json " || " ${MODES[@]} " =~ " json-asff " ]]; then - . $PROWLER_DIR/include/jq_detector -fi - -if [[ "$SEND_TO_SECURITY_HUB" -eq 1 ]]; then - checkSecurityHubCompatibility -fi - -if is_junit_output_enabled; then - prepare_junit_output -fi - -# First, check AWS Organizations Metadata -if [[ -n "${MANAGEMENT_ACCOUNT_ID}" && -n "${ROLE_TO_ASSUME}" ]] -then - # Backing up initial credentials - backupInitialAWSCredentials - - # Backing up initial account to assume - INITIAL_ACCOUNT_TO_ASSUME="${ACCOUNT_TO_ASSUME}" - - # Set the new account to assume to recover AWS Organizations Metadata - ACCOUNT_TO_ASSUME="${MANAGEMENT_ACCOUNT_ID}" - - # Recover AWS Organizations Metadata - get_orgs_account_details - - # Restoring account to assume to -A field after getting account metadata - ACCOUNT_TO_ASSUME="${INITIAL_ACCOUNT_TO_ASSUME}" - - # Restoring initial credentials - restoreInitialAWSCredentials -fi +# Check AWS CLI +aws_cli_detector +# load AWS profile +aws_profile_loader # Gather account data / test aws cli connectivity -getWhoami -if [[ -n "${ACCOUNT_TO_ASSUME}" || -n "${ROLE_TO_ASSUME}" ]]; then - backupInitialAWSCredentials - assume_role -fi +get_aws_credentials +print_whoami # List regions get_regions -# Execute group of checks if called with -g -if [[ $GROUP_ID_READ ]];then - if [[ " ${GROUP_ID[@]} " =~ " ${GROUP_ID_READ} " ]]; then +# Validate command line options that requires AWS +validate_custom_output_directory +validate_custom_output_filename +validate_security_hub +validate_organizations +validate_allowlist +validate_custom_checks - execute_group_by_id ${GROUP_ID_READ} ${EXCLUDE_CHECK_ID} - if [[ "${MODES[@]}" =~ "html" ]]; then - addHtmlFooter >> ${OUTPUT_FILE_NAME}.$EXTENSION_HTML - fi - cleanTemp - scoring - if [[ $OUTPUT_BUCKET_NOASSUME ]]; then - restoreInitialAWSCredentials - fi - if [[ $OUTPUT_BUCKET ]]; then - copyToS3 - fi - exit $EXITCODE - else - textFail "Group ${GROUP_ID_READ} does not exist. Use a valid check group ID i.e.: group1, extras, forensics-ready, etc." - show_all_group_titles - exit $EXITCODE - fi -fi +# Add headers to certain output files +output_files_init -# Get a list of total checks excluding a list provided by the user and overwrite CHECK_ID with the result -# if the list provided by the user contains an invalid check, this will be discarded. -# if the list provided by the user contains just one argument and is invalid, then it will be discarded and all tests will be executed -if [[ ${EXCLUDE_CHECK_ID} ]];then - get_all_checks_without_exclusion ${EXCLUDE_CHECK_ID} - function join { local IFS="$1"; shift; echo "$*"; } - CHECKS_EXCLUDED=$(join , "${CHECKS_EXCLUDED[@]}") - CHECK_ID=${CHECKS_EXCLUDED} -fi +# Execute checks +for CHECK in "${TOTAL_CHECKS[@]}" +do + execute_check "${CHECK}" +done -# Execute single check if called with -c -if [[ $CHECK_ID ]];then - IFS=',' read -ra CHECKS <<< "$CHECK_ID" - for CHECK in "${CHECKS[@]}"; do - execute_check $CHECK - done - if [[ "${MODES[@]}" =~ "html" ]]; then - addHtmlFooter >> ${OUTPUT_FILE_NAME}.$EXTENSION_HTML - fi - if [[ $OUTPUT_BUCKET_NOASSUME ]]; then - restoreInitialAWSCredentials - fi - if [[ $OUTPUT_BUCKET ]]; then - copyToS3 - fi - scoring - cleanTemp - exit $EXITCODE -fi +# Add bottom to certain output files +output_files_end -execute_all - -if [[ "${MODES[@]}" =~ "html" ]]; then - addHtmlFooter >> ${OUTPUT_FILE_NAME}.$EXTENSION_HTML -fi +# Validate output bucket without assuming role +validate_copy_to_s3_no_assume +# Copy Prowler Findings to S3 if required +copy_to_s3_assume +# Score if required scoring -cleanTemp -if [[ $OUTPUT_BUCKET_NOASSUME ]]; then - restoreInitialAWSCredentials -fi -if [[ $OUTPUT_BUCKET ]]; then - copyToS3 -fi exit $EXITCODE