mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 04:51:51 +00:00
refactor(Prowler): Main logic refactor (#1189)
* fix(aws_profile_loader): New functions * fix(shellcheck): Temporary remove Shellcheck * fix(aws_cli_detector): new function * fix(jq_detector): New function * fix(os_detector): New function * fix(output_bucket): Output bucket input check in main * fix(python_detector): deleted unused python detector * fix(credentials): credentials check out of whoami * [break]refactor(main) * [BREAK] Get list of checks parsing all input options * [break]refactor(main): execute checks functions * [break]refactor(main): move functions to libs * fix(validations): custom check validation and typos * refactor(validate_options): Include comments * fix(custom_checks): Minor fixes * refactor(closing_files): include libraries * refactor(loader): Include ignored checks * refactor(main): Fix shellcheck * refactor(loader): beautify * refactor(monochrome): without variables * refactor(modes): MODES array not needed * refactor(whoami): get error from AWSCLI * refactor(secrets-detector) * refactor(secrets-detector) * fix(html_scoring): html scoring was fixed. * fix(load_checks_from_file) * fix(color-code): Print if not mono * fix(not extra): Fixed if EXCLUDE_CHECK_ID is empty * fix(IFS): Restore default IFS once modes are parsed * fix(bucket): validate before whoami * fix(bucket): validate before whoami Co-authored-by: n4ch04 <nachor1992@gmail.com> Co-authored-by: sergargar <sergio@verica.io> Co-authored-by: Nacho Rivera <59198746+n4ch04@users.noreply.github.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
GROUP_CHECKS[14]='extra715,extra716,extra779,extra780,extra781,extra782,extra783,extra784,extra785,extra787,extra788,extra7101'
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-2
@@ -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'
|
||||
GROUP_CHECKS[24]='extra7114,extra7115,extra7116,extra7117,extra7118,extra7119,extra7120,extra7121,extra7122'
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
+2
-3
@@ -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'
|
||||
GROUP_TITLE[26]='Amazon Directory Service related security checks - [ds] *******'
|
||||
GROUP_CHECKS[26]='extra7181,extra7182,extra7183,extra7184'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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'
|
||||
|
||||
+55
-38
@@ -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
|
||||
}
|
||||
|
||||
+22
-9
@@ -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
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
|
||||
}
|
||||
|
||||
+30
-13
@@ -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="[0;39m"
|
||||
WARNING="[0;33m" # Warning (brown)
|
||||
NOTICE="[1;33m" # Notice (yellow)
|
||||
OK="[1;32m" # Ok (green)
|
||||
BAD="[1;31m" # Bad (red)
|
||||
CYAN="[0;36m"
|
||||
BLUE="[0;34m"
|
||||
BROWN="[0;33m"
|
||||
MAGENTA="[1;35m"
|
||||
RED="[1;31m"
|
||||
YELLOW="[1;33m"
|
||||
set_colors() {
|
||||
if [[ "${MODE}" =~ "mono" ]]
|
||||
then
|
||||
NORMAL=""
|
||||
WARNING=""
|
||||
NOTICE=""
|
||||
OK=""
|
||||
BAD=""
|
||||
CYAN=""
|
||||
BLUE=""
|
||||
BROWN=""
|
||||
MAGENTA=""
|
||||
RED=""
|
||||
YELLOW=""
|
||||
else
|
||||
NORMAL="[0;39m"
|
||||
WARNING="[0;33m" # Warning (brown)
|
||||
NOTICE="[1;33m" # Notice (yellow)
|
||||
OK="[1;32m" # Ok (green)
|
||||
BAD="[1;31m" # Bad (red)
|
||||
CYAN="[0;36m"
|
||||
BLUE="[0;34m"
|
||||
BROWN="[0;33m"
|
||||
MAGENTA="[1;35m"
|
||||
RED="[1;31m"
|
||||
YELLOW="[1;33m"
|
||||
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"
|
||||
|
||||
@@ -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
|
||||
|
||||
+16
-16
@@ -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
|
||||
}
|
||||
|
||||
@@ -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="[1;31m"
|
||||
OPTNORMAL="[0;39m"
|
||||
|
||||
# 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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
+137
@@ -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 <group_id>' 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
|
||||
}
|
||||
+123
-121
@@ -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
|
||||
}
|
||||
+43
-25
@@ -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 '<tr class="'$ROW_CLASS'">' >> ${OUTPUT_FILE_NAME}.$EXTENSION_HTML
|
||||
@@ -425,4 +443,4 @@ generateHtmlOutput(){
|
||||
echo ' <td>'$CHECK_RESOURCE_ID'</td>' >> ${OUTPUT_FILE_NAME}.$EXTENSION_HTML
|
||||
echo '</tr>' >> ${OUTPUT_FILE_NAME}.$EXTENSION_HTML
|
||||
echo '' >> ${OUTPUT_FILE_NAME}.$EXTENSION_HTML
|
||||
}
|
||||
}
|
||||
|
||||
+6
-16
@@ -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}"
|
||||
;;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+2
-2
@@ -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
|
||||
|
||||
+15
-16
@@ -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
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
+29
-65
@@ -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
|
||||
}
|
||||
|
||||
@@ -22,62 +22,70 @@
|
||||
# All there legs and lashes
|
||||
# I've just got to find my way...
|
||||
|
||||
OPTRED="[1;31m"
|
||||
OPTNORMAL="[0;39m"
|
||||
|
||||
# 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 <profile> -r <region> -h ]
|
||||
$(basename "${0}") [ -p <profile> -r <region> -h ]
|
||||
Options:
|
||||
-p <profile> Specify your AWS profile to use.
|
||||
(i.e.: default)
|
||||
-r <region> 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 <check_id> 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 <check_id> 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 <group_id> 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 <group_id> 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 <filterregion> 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 <maxitems> Specify the maximum number of items to return for long-running requests (default: 100).
|
||||
-M <mode> 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> Shodan API key used by check extra7102.
|
||||
-o Custom output directory, if not specified will use default prowler/output, requires -M <mode>.
|
||||
(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 <mgmnt acct ID> Specify AWS Organizations management account ID. Used to get account details, requires -R.
|
||||
(requires organizations:ListAccounts* and organizations:ListTagsForResource)
|
||||
-a <aws_cli_cmd> 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 <aws_cli_cmd> 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 <group_id>' 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
|
||||
|
||||
Reference in New Issue
Block a user