Repository: prowler-cloud/prowler
Pull request: fix(check32): filterName base64encoded to avoid space problems in filter names

### Context 

Prowler fails in check32 when metric filter names include spaces, interpreting each single word of the name as a different metric filter name. This generates false positives from non existing metric filter names -  prowler-cloud/prowler#745


### Description

To avoid this metric filter name is base64 encoded in check3x file, decoding it when its needed ( aws api calls and display messages)


### License

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.


Problem statement:
Fix check32: fails when metric name contain spaces in it
Metric is **Console sign-in without MFA**
and prowler is reading ['Console', 'sign-in', 'without', 'MFA']

```[
  {
    "Account Number": "",
    "Control": "[check32] Ensure a log metric filter and alarm exist for Management Console sign-in without MFA (Scored)",
    "Message": "CloudWatch group aws-cloudtrail-logs-a12sa26a8 found with metric filter **Console** but no alarms associated",
    "Severity": "Medium",
    "Status": "FAIL",
    "Scored": "Scored",
    "Level": "Level 1",
    "Control ID": 3.2,
    "Region": "us-east-2",
    "Compliance": "ens-op.exp.8.aws.trail.4"
  },
  {
    "Account Number": "",
    "Control": "[check32] Ensure a log metric filter and alarm exist for Management Console sign-in without MFA (Scored)",
    "Message": "CloudWatch group aws-cloudtrail-logs-a12sa26a8 found with metric filter **Login** but no alarms associated",
    "Severity": "Medium",
    "Status": "FAIL",
    "Scored": "Scored",
    "Level": "Level 1",
    "Control ID": 3.2,
    "Region": "us-east-2",
    "Compliance": "ens-op.exp.8.aws.trail.4"
  },
  {
    "Account Number": "",
    "Control": "[check32] Ensure a log metric filter and alarm exist for Management Console sign-in without MFA (Scored)",
    "Message": "CloudWatch group aws-cloudtrail-logs-a12sa26a8 found with metric filter **without** but no alarms associated",
    "Severity": "Medium",
    "Status": "FAIL",
    "Scored": "Scored",
    "Level": "Level 1",
    "Control ID": 3.2,
    "Region": "us-east-2",
    "Compliance": "ens-op.exp.8.aws.trail.4"
  },
  {
    "Account Number": "",
    "Control": "[check32] Ensure a log metric filter and alarm exist for Management Console sign-in without MFA (Scored)",
    "Message": "CloudWatch group aws-cloudtrail-logs-a12sa26a8 found with metric filter **MFA** but no alarms associated",
    "Severity": "Medium",
    "Status": "FAIL",
    "Scored": "Scored",
    "Level": "Level 1",
    "Control ID": 3.2,
    "Region": "us-east-2",
    "Compliance": "ens-op.exp.8.aws.trail.4"
  }
]```

Hints from issue discussion:
Hi, thanks for the comment. I don´t see an issue in your output, to know more about what is the filter for that check look at this line https://github.com/toniblyx/prowler/blob/138ece153e96fe70a0d758f5b6b77a37443e9177/checks/check32#L48

I have 2 metric filter one with name "CloudTrail Changes" and another one with name "cloud_trail" as visible in screenshot

![image](https://user-images.githubusercontent.com/28739174/107636129-35e29980-6c92-11eb-81b3-a42dd0dd75cb.png)

but when I run prowler Report for "check35" 

![image](https://user-images.githubusercontent.com/28739174/107636455-abe70080-6c92-11eb-9d83-00865b0fc3de.png)

It return 2 FAIL and 1 PASS 

Prowler taking  "CloudTrail Changes"  not as a single metric filter but 2 different metric filter ie. "CloudTrail" and "Changes", seperating them with whitespaces.

its happening with all the checks associated with metric filter

@ayushme001 I see it, the metric name has a space in it and when iterating the group names that is being treated as two groups.

@toniblyx maybe this could be fixed by base64 encoding the metric name and decoding it before the result is used/printed?  Otherwise this would need a larger rewrite of check3x. I can take a shot at it if the approach is ok with you.

Looks like same issue as https://github.com/toniblyx/prowler/issues/700
@zfLQ2qx2 I think that encoding workaround can make the trick, there are other checks doing similar way.

Commit message: fix(check32): filterName base64encoded to avoid space problems in filter names

Patch to review:
diff --git a/include/check3x b/include/check3x
index 29de748e4c..2137c0a322 100644
--- a/include/check3x
+++ b/include/check3x
@@ -39,11 +39,12 @@ check3x(){
       if [ "$CLOUDWATCH_LOGGROUP_ACCOUNT" == "$CURRENT_ACCOUNT_ID" ];then
         # Filter control and whitespace from .metricFilters[*].filterPattern for easier matching later
         METRICFILTER_CACHE=$($AWSCLI logs describe-metric-filters --log-group-name "$CLOUDWATCH_LOGGROUP_NAME" $PROFILE_OPT --region "$CLOUDWATCH_LOGGROUP_REGION"|jq '.metricFilters|=map(.filterPattern|=gsub("[[:space:]]+"; " "))')
-        METRICFILTER_SET=$(echo $METRICFILTER_CACHE | jq -r --arg re "$grep_filter" '.metricFilters[]|select(.filterPattern|test($re))|.filterName')
+        METRICFILTER_SET=$(echo $METRICFILTER_CACHE | jq -r --arg re "$grep_filter" '.metricFilters[]|select(.filterPattern|test($re))|.filterName|@base64')
       fi
       if [[ $METRICFILTER_SET ]];then
         for metric in $METRICFILTER_SET; do
-          metric_name=$(echo $METRICFILTER_CACHE | jq -r --arg name $metric '.metricFilters[]|select(.filterName==$name)|.metricTransformations[0].metricName')
+          metric_decode=$(echo $metric | base64 -d)
+          metric_name=$(echo $METRICFILTER_CACHE | jq -r --arg name "${metric_decode}" '.metricFilters[]|select(.filterName==$name)|.metricTransformations[0].metricName')
           HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region "$CLOUDWATCH_LOGGROUP_REGION" --query 'MetricAlarms[?MetricName==`'"$metric_name"'`]' --output text)
           if [[ $HAS_ALARM_ASSOCIATED ]];then
             CHECK_OK="$CHECK_OK $CLOUDWATCH_LOGGROUP_NAME:$metric"
@@ -61,7 +62,7 @@ check3x(){
 
     if [[ $CHECK_OK ]]; then
       for group in $CHECK_OK; do
-        metric=${group#*:}
+        metric=$(echo ${group#*:} | base64 -d)
         group=${group%:*}
         textPass "$REGION: CloudWatch group $group found with metric filter $metric and alarms set" "$REGION" "$group"
       done
@@ -69,7 +70,7 @@ check3x(){
     if [[ $CHECK_WARN ]]; then
       for group in $CHECK_WARN; do
         case $group in
-           *:*) metric=${group#*:}
+           *:*) metric=$(echo ${group#*:} | base64 -d)
                 group=${group%:*}
                 if [[ $pass_count == 0 ]]; then
                   textFail "$REGION: CloudWatch group $group found with metric filter $metric but no alarms associated" "$REGION" "$group"


Code review comments:
diff_hunk: @@ -61,15 +62,15 @@ check3x(){
 
     if [[ $CHECK_OK ]]; then
       for group in $CHECK_OK; do
-        metric=${group#*:}
+        metric=$(echo ${group#*:} | base64 -d)
path: include/check3x
text: @user1:
```suggestion
        metric=$(base64 -d <<< "${group#*:}")
```

diff_hunk: @@ -39,11 +39,12 @@ check3x(){
       if [ "$CLOUDWATCH_LOGGROUP_ACCOUNT" == "$CURRENT_ACCOUNT_ID" ];then
         # Filter control and whitespace from .metricFilters[*].filterPattern for easier matching later
         METRICFILTER_CACHE=$($AWSCLI logs describe-metric-filters --log-group-name "$CLOUDWATCH_LOGGROUP_NAME" $PROFILE_OPT --region "$CLOUDWATCH_LOGGROUP_REGION"|jq '.metricFilters|=map(.filterPattern|=gsub("[[:space:]]+"; " "))')
-        METRICFILTER_SET=$(echo $METRICFILTER_CACHE | jq -r --arg re "$grep_filter" '.metricFilters[]|select(.filterPattern|test($re))|.filterName')
+        METRICFILTER_SET=$(echo $METRICFILTER_CACHE | jq -r --arg re "$grep_filter" '.metricFilters[]|select(.filterPattern|test($re))|.filterName|@base64')
       fi
       if [[ $METRICFILTER_SET ]];then
         for metric in $METRICFILTER_SET; do
-          metric_name=$(echo $METRICFILTER_CACHE | jq -r --arg name $metric '.metricFilters[]|select(.filterName==$name)|.metricTransformations[0].metricName')
+          metric_decode=$(echo $metric | base64 -d)
path: include/check3x
text: @user1:
```suggestion
          metric_decode=$(base64 -d <<< "${metric}")
```

diff_hunk: @@ -39,11 +39,12 @@ check3x(){
       if [ "$CLOUDWATCH_LOGGROUP_ACCOUNT" == "$CURRENT_ACCOUNT_ID" ];then
         # Filter control and whitespace from .metricFilters[*].filterPattern for easier matching later
         METRICFILTER_CACHE=$($AWSCLI logs describe-metric-filters --log-group-name "$CLOUDWATCH_LOGGROUP_NAME" $PROFILE_OPT --region "$CLOUDWATCH_LOGGROUP_REGION"|jq '.metricFilters|=map(.filterPattern|=gsub("[[:space:]]+"; " "))')
-        METRICFILTER_SET=$(echo $METRICFILTER_CACHE | jq -r --arg re "$grep_filter" '.metricFilters[]|select(.filterPattern|test($re))|.filterName')
+        METRICFILTER_SET=$(echo $METRICFILTER_CACHE | jq -r --arg re "$grep_filter" '.metricFilters[]|select(.filterPattern|test($re))|.filterName|@base64')
path: include/check3x
text: @user2:
```suggestion
        METRICFILTER_SET=$(echo "${METRICFILTER_CACHE}" | jq -r --arg re "${grep_filter}" '.metricFilters[]|select(.filterPattern|test("${re}"))|.filterName|@user1')
```

diff_hunk: @@ -61,15 +62,15 @@ check3x(){
 
     if [[ $CHECK_OK ]]; then
       for group in $CHECK_OK; do
-        metric=${group#*:}
+        metric=$(echo ${group#*:} | base64 -d)
         group=${group%:*}
         textPass "$REGION: CloudWatch group $group found with metric filter $metric and alarms set" "$REGION" "$group"
       done
     fi
     if [[ $CHECK_WARN ]]; then
       for group in $CHECK_WARN; do
         case $group in
-           *:*) metric=${group#*:}
+           *:*) metric=$(echo ${group#*:} | base64 -d)
path: include/check3x
text: @user1:
```suggestion
           *:*) metric=$(base64 -d <<< "${group#*:}")
```

diff_hunk: @@ -39,11 +39,12 @@ check3x(){
       if [ "$CLOUDWATCH_LOGGROUP_ACCOUNT" == "$CURRENT_ACCOUNT_ID" ];then
         # Filter control and whitespace from .metricFilters[*].filterPattern for easier matching later
         METRICFILTER_CACHE=$($AWSCLI logs describe-metric-filters --log-group-name "$CLOUDWATCH_LOGGROUP_NAME" $PROFILE_OPT --region "$CLOUDWATCH_LOGGROUP_REGION"|jq '.metricFilters|=map(.filterPattern|=gsub("[[:space:]]+"; " "))')
-        METRICFILTER_SET=$(echo $METRICFILTER_CACHE | jq -r --arg re "$grep_filter" '.metricFilters[]|select(.filterPattern|test($re))|.filterName')
+        METRICFILTER_SET=$(echo $METRICFILTER_CACHE | jq -r --arg re "$grep_filter" '.metricFilters[]|select(.filterPattern|test($re))|.filterName|@base64')
       fi
       if [[ $METRICFILTER_SET ]];then
         for metric in $METRICFILTER_SET; do
-          metric_name=$(echo $METRICFILTER_CACHE | jq -r --arg name $metric '.metricFilters[]|select(.filterName==$name)|.metricTransformations[0].metricName')
+          metric_decode=$(echo $metric | base64 -d)
+          metric_name=$(echo $METRICFILTER_CACHE | jq -r --arg name "${metric_decode}" '.metricFilters[]|select(.filterName==$name)|.metricTransformations[0].metricName')
path: include/check3x
text: @user1:
```suggestion
          metric_name=$(echo "${METRICFILTER_CACHE}" | jq -r --arg name "${metric_decode}" '.metricFilters[]|select(.filterName=="${name}")|.metricTransformations[0].metricName')
```