feat(): opensearch service and checks (#1487)

Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
This commit is contained in:
Nacho Rivera
2022-11-17 11:48:18 +01:00
committed by GitHub
parent 24ca19d502
commit 5a9c064943
51 changed files with 1896 additions and 726 deletions

View File

@@ -1,51 +0,0 @@
#!/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.
CHECK_ID_extra7101="7.101"
CHECK_TITLE_extra7101="[extra7101] Check if Amazon Elasticsearch Service (ES) domains have audit logging enabled"
CHECK_SCORED_extra7101="NOT_SCORED"
CHECK_CIS_LEVEL_extra7101="EXTRA"
CHECK_SEVERITY_extra7101="Low"
CHECK_ASFF_RESOURCE_TYPE_extra7101="AwsElasticsearchDomain"
CHECK_ALTERNATE_check7101="extra7101"
CHECK_SERVICENAME_extra7101="es"
CHECK_RISK_extra7101='If logs are not enabled; monitoring of service use and threat analysis is not possible.'
CHECK_REMEDIATION_extra7101='Make sure you are logging information about Amazon Elasticsearch Service operations.'
CHECK_DOC_extra7101='https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/audit-logs.html'
CHECK_CAF_EPIC_extra7101='Logging and Monitoring'
extra7101(){
for regx in ${REGIONS}; do
LIST_OF_DOMAINS=$("${AWSCLI}" es list-domain-names ${PROFILE_OPT} --region "${regx}" --query 'DomainNames[].DomainName' --output text 2>&1)
if [[ $(echo "${LIST_OF_DOMAINS}" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
textInfo "${regx}: Access Denied trying to list domain names" "${regx}"
continue
fi
if [[ "${LIST_OF_DOMAINS}" ]]; then
for domain in ${LIST_OF_DOMAINS}; do
AUDIT_LOGS_ENABLED=$("${AWSCLI}" es describe-elasticsearch-domain-config --domain-name "${domain}" ${PROFILE_OPT} --region "${regx}" --query 'DomainConfig.LogPublishingOptions.Options.AUDIT_LOGS.Enabled' --output text 2>&1)
if [[ $(echo "${AUDIT_LOGS_ENABLED}" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
textInfo "${regx}: Access Denied trying to get ES domain config for ${domain}" "${regx}"
continue
fi
if [[ $(tr '[:upper:]' '[:lower:]' <<< "${AUDIT_LOGS_ENABLED}") == "true" ]]; then
textPass "${regx}: Amazon ES domain ${domain} AUDIT_LOGS enabled" "${regx}" "${domain}"
else
textFail "${regx}: Amazon ES domain ${domain} AUDIT_LOGS disabled!" "${regx}" "${domain}"
fi
done
else
textInfo "${regx}: No Amazon ES domain found" "${regx}"
fi
done
}

View File

@@ -1,58 +0,0 @@
#!/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.
CHECK_ID_extra715="7.15"
CHECK_TITLE_extra715="[extra715] Check if Amazon Elasticsearch Service (ES) domains have logging enabled"
CHECK_SCORED_extra715="NOT_SCORED"
CHECK_CIS_LEVEL_extra715="EXTRA"
CHECK_SEVERITY_extra715="Medium"
CHECK_ASFF_RESOURCE_TYPE_extra715="AwsElasticsearchDomain"
CHECK_ALTERNATE_check715="extra715"
CHECK_SERVICENAME_extra715="es"
CHECK_RISK_extra715='Amazon ES exposes four Elasticsearch logs through Amazon CloudWatch Logs: error logs; search slow logs; index slow logs; and audit logs. '
CHECK_REMEDIATION_extra715='Enable Elasticsearch log. Create use cases for them. Using audit logs check for access denied events.'
CHECK_DOC_extra715='https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-createdomain-configure-slow-logs.html'
CHECK_CAF_EPIC_extra715='Logging and Monitoring'
extra715(){
for regx in ${REGIONS}; do
LIST_OF_DOMAINS=$("${AWSCLI}" es list-domain-names ${PROFILE_OPT} --region "${regx}" --query 'DomainNames[].DomainName' --output text 2>&1)
if [[ $(echo "${LIST_OF_DOMAINS}" | grep -E 'AccessDenied|UnauthorizedOperation') ]]; then
textInfo "${regx}: Access Denied trying to list domain names" "${regx}"
continue
fi
if [[ "${LIST_OF_DOMAINS}" ]]; then
for domain in ${LIST_OF_DOMAINS}; do
SLOWLOG_ENABLED=$("${AWSCLI}" es describe-elasticsearch-domain-config --domain-name "${domain}" ${PROFILE_OPT} --region "${regx}" --query 'DomainConfig.LogPublishingOptions.Options.[SEARCH_SLOW_LOGS.Enabled, INDEX_SLOW_LOGS.Enabled]' --output text 2>&1)
if [[ $(echo "${SLOWLOG_ENABLED}" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
textInfo "${regx}: Access Denied trying to get ES domain config for ${domain}" "${regx}"
continue
fi
read -r SEARCH_SLOWLOG_ENABLED INDEX_SLOWLOG_ENABLED <<< "${SLOWLOG_ENABLED}" && {
if [[ $(tr '[:upper:]' '[:lower:]' <<< "${SEARCH_SLOWLOG_ENABLED}") == "true" ]]; then
textPass "${regx}: Amazon ES domain ${domain} SEARCH_SLOW_LOGS enabled" "${regx}" "${domain}"
else
textFail "${regx}: Amazon ES domain ${domain} SEARCH_SLOW_LOGS disabled!" "${regx}" "${domain}"
fi
if [[ $(tr '[:upper:]' '[:lower:]' <<< "${INDEX_SLOWLOG_ENABLED}") == "true" ]]; then
textPass "${regx}: Amazon ES domain ${domain} INDEX_SLOW_LOGS enabled" "${regx}" "${domain}"
else
textFail "${regx}: Amazon ES domain ${domain} INDEX_SLOW_LOGS disabled!" "${regx}" "${domain}"
fi
}
done
else
textInfo "${regx}: No Amazon ES domain found" "${regx}"
fi
done
}

View File

@@ -1,107 +0,0 @@
#!/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.
CHECK_ID_extra716="7.16"
CHECK_TITLE_extra716="[extra716] Check if Amazon Elasticsearch Service (ES) domains are set as Public or if it has open policy access"
CHECK_SCORED_extra716="NOT_SCORED"
CHECK_CIS_LEVEL_extra716="EXTRA"
CHECK_SEVERITY_extra716="Critical"
CHECK_ASFF_RESOURCE_TYPE_extra716="AwsElasticsearchDomain"
CHECK_ALTERNATE_check716="extra716"
CHECK_SERVICENAME_extra716="es"
CHECK_RISK_extra716='Publicly accessible services could expose sensitive data to bad actors.'
CHECK_REMEDIATION_extra716='Use VPC endpoints for internal services.'
CHECK_DOC_extra716='https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-vpc.html'
CHECK_CAF_EPIC_extra716='Infrastructure Security'
extra716(){
for regx in $REGIONS; do
LIST_OF_DOMAINS=$($AWSCLI es list-domain-names $PROFILE_OPT --region $regx --query 'DomainNames[].DomainName' --output text 2>&1)
if [[ $(echo "$LIST_OF_DOMAINS" | grep -E 'AccessDenied|UnauthorizedOperation') ]]; then
textInfo "$regx: Access Denied trying to list domain names" "$regx"
continue
fi
if [[ $LIST_OF_DOMAINS ]]; then
TEMP_POLICY_FILE=$(mktemp -t prowler-${ACCOUNT_NUM}-es-domain.policy.XXXXXXXXXX)
for domain in $LIST_OF_DOMAINS;do
# get endpoint or vpc endpoints
ES_DOMAIN_INFO=$($AWSCLI es describe-elasticsearch-domain --domain-name $domain $PROFILE_OPT --region $regx --query 'DomainStatus.[Endpoints.vpc, VPCOptions.VPCId]' --output text 2>&1)
if [[ $(echo "$ES_DOMAIN_INFO" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
textInfo "$regx: Access Denied trying to get domain $domain" "$regx"
continue
fi
read ES_DOMAIN_ENDPOINT_VPC ES_DOMAIN_VPC <<< "$ES_DOMAIN_INFO" &&
# If the endpoint starts with "vpc-" it is in a VPC then it is fine.
if [[ "${ES_DOMAIN_ENDPOINT_VPC:0:3}" == "vpc" ]]; then
textInfo "$regx: Amazon ES domain $domain is in VPC $ES_DOMAIN_VPC run extra779 to make sure it is not exposed using custom proxy" "$regx" "$domain"
else
$AWSCLI es describe-elasticsearch-domain-config --domain-name $domain $PROFILE_OPT --region $regx --query 'DomainConfig.AccessPolicies.Options' --output text > $TEMP_POLICY_FILE 2>&1
if [[ $(grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError' $TEMP_POLICY_FILE) ]]; then
textInfo "$regx: Access Denied trying to get domain config for $domain" "$regx"
continue
fi
# check if the policy has a principal set up
CHECK_ES_POLICY_PRINCIPAL=$(cat $TEMP_POLICY_FILE | jq -r '. | .Statement[] | select(.Effect == "Allow" and (((.Principal|type == "object") and .Principal.AWS != "*") or ((.Principal|type == "string") and .Principal != "*")) and select(has("Condition") | not))')
if [[ $CHECK_ES_POLICY_PRINCIPAL ]]; then
textPass "$regx: Amazon ES domain $domain does have a Principal set up" "$regx" "$domain"
fi
CHECK_ES_DOMAIN_POLICY_OPEN=$(cat $TEMP_POLICY_FILE | jq -r '. | .Statement[] | select(.Effect == "Allow" and (((.Principal|type == "object") and .Principal.AWS == "*") or ((.Principal|type == "string") and .Principal == "*")) and select(has("Condition") | not))')
CHECK_ES_DOMAIN_POLICY_HAS_CONDITION=$(cat $TEMP_POLICY_FILE | jq -r '. | .Statement[] | select(.Effect == "Allow" and (((.Principal|type == "object") and .Principal.AWS == "*") or ((.Principal|type == "string") and .Principal == "*")) and select(has("Condition")))' )
if [[ $CHECK_ES_DOMAIN_POLICY_HAS_CONDITION ]]; then
# get content of IpAddress."aws:SourceIp" and get a clean list
LIST_CONDITION_IPS=$(cat $TEMP_POLICY_FILE | jq '.Statement[0] .Condition.IpAddress."aws:SourceIp"'| awk -F'"' '{print $2}' | tr -d '",^$' | sed '/^$/d')
unset CONDITION_HAS_PUBLIC_IP_ARRAY
for condition_ip in "${LIST_CONDITION_IPS}";do
CONDITION_HAS_PRIVATE_IP=$(echo "${condition_ip}" | grep -E '^(192\.168|10\.|172\.1[6789]\.|172\.2[0-9]\.|172\.3[01]\.)')
if [[ $CONDITION_HAS_PRIVATE_IP ]];then
CONDITION_HAS_PRIVATE_IP_ARRAY+=($condition_ip)
fi
CONDITION_HAS_PUBLIC_IP=$(echo "${condition_ip}" | grep -vE '^(192\.168|10\.|172\.1[6789]\.|172\.2[0-9]\.|172\.3[01]\.|0\.0\.0\.0|\*)')
if [[ $CONDITION_HAS_PUBLIC_IP ]];then
CONDITION_HAS_PUBLIC_IP_ARRAY+=($condition_ip)
fi
CONDITION_HAS_ZERO_NET=$(echo "${condition_ip}" | grep -E '^(0\.0\.0\.0)')
CONDITION_HAS_STAR=$(echo "${condition_ip}" | grep -E '^\*')
done
CHECK_ES_DOMAIN_POLICY_CONDITION_PRIVATE_IP=${CONDITION_HAS_PRIVATE_IP_ARRAY[@]}
CHECK_ES_DOMAIN_POLICY_CONDITION_PUBLIC_IP=${CONDITION_HAS_PUBLIC_IP_ARRAY[@]}
CHECK_ES_DOMAIN_POLICY_CONDITION_ZERO=$CONDITION_HAS_ZERO_NET
CHECK_ES_DOMAIN_POLICY_CONDITION_STAR=$CONDITION_HAS_STAR
fi
if [[ $CHECK_ES_DOMAIN_POLICY_OPEN || $CHECK_ES_DOMAIN_POLICY_CONDITION_ZERO || $CHECK_ES_DOMAIN_POLICY_CONDITION_STAR || ${CHECK_ES_DOMAIN_POLICY_CONDITION_PUBLIC_IP[@]} ]];then
if [[ $CHECK_ES_DOMAIN_POLICY_OPEN ]];then
textFail "$regx: Amazon ES domain $domain policy allows access (Principal: \"*\") - use extra788 to test AUTH" "$regx" "$domain"
fi
if [[ $CHECK_ES_DOMAIN_POLICY_HAS_CONDITION && $CHECK_ES_DOMAIN_POLICY_CONDITION_ZERO ]];then
textFail "$regx: Amazon ES domain $domain policy allows access (Principal: \"*\" and network 0.0.0.0) - use extra788 to test AUTH" "$regx" "$domain"
fi
if [[ $CHECK_ES_DOMAIN_POLICY_HAS_CONDITION && $CHECK_ES_DOMAIN_POLICY_CONDITION_STAR ]];then
textFail "$regx: Amazon ES domain $domain policy allows access (Principal: \"*\" and network \"*\") - use extra788 to test AUTH" "$regx" "$domain"
fi
if [[ $CHECK_ES_DOMAIN_POLICY_HAS_CONDITION && ${CHECK_ES_DOMAIN_POLICY_CONDITION_PUBLIC_IP[@]} ]];then
textInfo "$regx: Amazon ES domain $domain policy allows access (Principal: \"*\" and Public IP or Network $(echo ${CONDITION_HAS_PUBLIC_IP_ARRAY[@]})) - use extra788 to test AUTH" "$regx" "$domain"
fi
else
if [[ $CHECK_ES_DOMAIN_POLICY_HAS_CONDITION && ${CHECK_ES_DOMAIN_POLICY_CONDITION_PRIVATE_IP[@]} ]];then
textInfo "$regx: Amazon ES domain $domain policy allows access from a Private IP or CIDR RFC1918 $(echo ${CONDITION_HAS_PRIVATE_IP_ARRAY[@]})" "$regx" "$domain"
else
textPass "$regx: Amazon ES domain $domain does not allow anonymous access" "$regx" "$domain"
fi
fi
fi
done
[[ -f "${TEMP_POLICY_FILE}" ]] && rm -f $TEMP_POLICY_FILE
else
textInfo "$regx: No Amazon ES domain found" "$regx"
fi
done
}

View File

@@ -1,50 +0,0 @@
#!/usr/bin/env bash
# Prowler - the handy cloud security tool (copyright 2020) by Toni de la Fuente
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
CHECK_ID_extra780="7.80"
CHECK_TITLE_extra780="[extra780] Check if Amazon Elasticsearch Service (ES) domains has Amazon Cognito authentication for Kibana enabled"
CHECK_SCORED_extra780="NOT_SCORED"
CHECK_CIS_LEVEL_extra780="EXTRA"
CHECK_SEVERITY_extra780="High"
CHECK_ASFF_RESOURCE_TYPE_extra780="AwsElasticsearchDomain"
CHECK_ALTERNATE_check780="extra780"
CHECK_SERVICENAME_extra780="es"
CHECK_RISK_extra780='Amazon Elasticsearch Service supports Amazon Cognito for Kibana authentication. '
CHECK_REMEDIATION_extra780='If you do not configure Amazon Cognito authentication; you can still protect Kibana using an IP-based access policy and a proxy server; HTTP basic authentication; or SAML.'
CHECK_DOC_extra780='https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-ac.html'
CHECK_CAF_EPIC_extra780='IAM'
extra780(){
for regx in ${REGIONS}; do
LIST_OF_DOMAINS=$("${AWSCLI}" es list-domain-names ${PROFILE_OPT} --region "${regx}" --query 'DomainNames[].DomainName' --output text 2>&1)
if [[ $(echo "${LIST_OF_DOMAINS}" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
textInfo "${regx}: Access Denied trying to list domain names" "${regx}"
continue
fi
if [[ "${LIST_OF_DOMAINS}" ]]; then
for domain in ${LIST_OF_DOMAINS}; do
CHECK_IF_COGNITO_ENABLED=$("${AWSCLI}" es describe-elasticsearch-domain --domain-name "${domain}" ${PROFILE_OPT} --region "${regx}" --query 'DomainStatus.CognitoOptions.Enabled' --output text 2>&1)
if [[ $(echo "${CHECK_IF_COGNITO_ENABLED}" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
textInfo "${regx}: Access Denied trying to get ES domain ${domain}" "${regx}"
continue
fi
if [[ $(tr '[:upper:]' '[:lower:]' <<< "${CHECK_IF_COGNITO_ENABLED}") == "true" ]]; then
textPass "${regx}: Amazon ES domain ${domain} has Amazon Cognito authentication for Kibana enabled" "${regx}" "${domain}"
else
textFail "${regx}: Amazon ES domain ${domain} does not have Amazon Cognito authentication for Kibana enabled" "${regx}" "${domain}"
fi
done
else
textInfo "${regx}: No Amazon ES domain found" "${regx}"
fi
done
}

View File

@@ -1,51 +0,0 @@
#!/usr/bin/env bash
# Prowler - the handy cloud security tool (copyright 2020) by Toni de la Fuente
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
CHECK_ID_extra781="7.81"
CHECK_TITLE_extra781="[extra781] Check if Amazon Elasticsearch Service (ES) domains has encryption at-rest enabled"
CHECK_SCORED_extra781="NOT_SCORED"
CHECK_CIS_LEVEL_extra781="EXTRA"
CHECK_SEVERITY_extra781="Medium"
CHECK_ASFF_RESOURCE_TYPE_extra781="AwsElasticsearchDomain"
CHECK_ALTERNATE_check781="extra781"
CHECK_ASFF_COMPLIANCE_TYPE_extra781="ens-mp.info.3.aws.au.1"
CHECK_SERVICENAME_extra781="es"
CHECK_RISK_extra781='If not enable unauthorized access to your data could risk increases.'
CHECK_REMEDIATION_extra781='Enable encryption at rest using AWS KMS to store and manage your encryption keys and the Advanced Encryption Standard algorithm with 256-bit keys (AES-256) to perform the encryption.'
CHECK_DOC_extra781='https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/encryption-at-rest.html'
CHECK_CAF_EPIC_extra781='Data Protection'
extra781(){
for regx in $REGIONS; do
LIST_OF_DOMAINS=$($AWSCLI es list-domain-names $PROFILE_OPT --region $regx --query 'DomainNames[].DomainName' --output text 2>&1)
if [[ $(echo "$LIST_OF_DOMAINS" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
textInfo "$regx: Access Denied trying to list domain names" "$regx"
continue
fi
if [[ $LIST_OF_DOMAINS ]]; then
for domain in $LIST_OF_DOMAINS;do
CHECK_IF_ENCREST_ENABLED=$($AWSCLI es describe-elasticsearch-domain --domain-name $domain $PROFILE_OPT --region $regx --query 'DomainStatus.EncryptionAtRestOptions.Enabled' --output text 2>&1)
if [[ $(echo "$CHECK_IF_ENCREST_ENABLED" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
textInfo "$regx: Access Denied trying to get ES domain $domain" "$regx"
continue
fi
if [[ $(echo "$CHECK_IF_ENCREST_ENABLED" | grep -i true) ]];then
textPass "$regx: Amazon ES domain $domain has encryption at-rest enabled" "$regx" "$domain"
else
textFail "$regx: Amazon ES domain $domain does not have encryption at-rest enabled" "$regx" "$domain"
fi
done
else
textInfo "$regx: No Amazon ES domain found" "$regx"
fi
done
}

View File

@@ -1,50 +0,0 @@
#!/usr/bin/env bash
# Prowler - the handy cloud security tool (copyright 2020) by Toni de la Fuente
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
CHECK_ID_extra782="7.82"
CHECK_TITLE_extra782="[extra782] Check if Amazon Elasticsearch Service (ES) domains has node-to-node encryption enabled"
CHECK_SCORED_extra782="NOT_SCORED"
CHECK_CIS_LEVEL_extra782="EXTRA"
CHECK_SEVERITY_extra782="Medium"
CHECK_ASFF_RESOURCE_TYPE_extra782="AwsElasticsearchDomain"
CHECK_ALTERNATE_check782="extra782"
CHECK_SERVICENAME_extra782="es"
CHECK_RISK_extra782='Node-to-node encryption provides an additional layer of security on top of the default features of Amazon ES. This architecture prevents potential attackers from intercepting traffic between Elasticsearch nodes and keeps the cluster secure.'
CHECK_REMEDIATION_extra782='Node-to-node encryption on new domains requires Elasticsearch 6.0 or later. Enabling the feature on existing domains requires Elasticsearch 6.7 or later. Choose the existing domain in the AWS console; Actions; and Modify encryption.'
CHECK_DOC_extra782='https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/ntn.html'
CHECK_CAF_EPIC_extra782='Data Protection'
extra782(){
for regx in $REGIONS; do
LIST_OF_DOMAINS=$($AWSCLI es list-domain-names $PROFILE_OPT --region $regx --query 'DomainNames[].DomainName' --output text 2>&1)
if [[ $(echo "$LIST_OF_DOMAINS" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
textInfo "$regx: Access Denied trying to list domain names" "$regx"
continue
fi
if [[ $LIST_OF_DOMAINS ]]; then
for domain in $LIST_OF_DOMAINS;do
CHECK_IF_NODETOENCR_ENABLED=$($AWSCLI es describe-elasticsearch-domain --domain-name $domain $PROFILE_OPT --region $regx --query 'DomainStatus.NodeToNodeEncryptionOptions.Enabled' --output text 2>&1)
if [[ $(echo "$CHECK_IF_NODETOENCR_ENABLED" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
textInfo "$regx: Access Denied trying to get ES domain $domain" "$regx"
continue
fi
if [[ $(echo "$CHECK_IF_NODETOENCR_ENABLED" | grep -i true) ]];then
textPass "$regx: Amazon ES domain $domain has node-to-node encryption enabled" "$regx" "$domain"
else
textFail "$regx: Amazon ES domain $domain does not have node-to-node encryption enabled" "$regx" "$domain"
fi
done
else
textInfo "$regx: No Amazon ES domain found" "$regx"
fi
done
}

View File

@@ -1,50 +0,0 @@
#!/usr/bin/env bash
# Prowler - the handy cloud security tool (copyright 2020) by Toni de la Fuente
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
CHECK_ID_extra783="7.83"
CHECK_TITLE_extra783="[extra783] Check if Amazon Elasticsearch Service (ES) domains has enforce HTTPS enabled"
CHECK_SCORED_extra783="NOT_SCORED"
CHECK_CIS_LEVEL_extra783="EXTRA"
CHECK_SEVERITY_extra783="Medium"
CHECK_ASFF_RESOURCE_TYPE_extra783="AwsElasticsearchDomain"
CHECK_ALTERNATE_check783="extra783"
CHECK_SERVICENAME_extra783="es"
CHECK_RISK_extra783='If not enable unauthorized access to your data could risk increases.'
CHECK_REMEDIATION_extra783='When creating ES Domains; enable "Require HTTPS fo all traffic to the domain".'
CHECK_DOC_extra783='https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-createupdatedomains.html'
CHECK_CAF_EPIC_extra783='Data Protection'
extra783(){
for regx in $REGIONS; do
LIST_OF_DOMAINS=$($AWSCLI es list-domain-names $PROFILE_OPT --region $regx --query 'DomainNames[].DomainName' --output text 2>&1)
if [[ $(echo "$LIST_OF_DOMAINS" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
textInfo "$regx: Access Denied trying to list domain names" "$regx"
continue
fi
if [[ $LIST_OF_DOMAINS ]]; then
for domain in $LIST_OF_DOMAINS;do
CHECK_IF_ENFORCEHTTPS_ENABLED=$($AWSCLI es describe-elasticsearch-domain --domain-name $domain $PROFILE_OPT --region $regx --query 'DomainStatus.DomainEndpointOptions.EnforceHTTPS' --output text 2>&1)
if [[ $(echo "$CHECK_IF_ENFORCEHTTPS_ENABLED" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
textInfo "$regx: Access Denied trying to get ES domain $domain" "$regx"
continue
fi
if [[ $(echo "$CHECK_IF_ENFORCEHTTPS_ENABLED" | grep -i true) ]];then
textPass "$regx: Amazon ES domain $domain has enforce HTTPS enabled" "$regx" "$domain"
else
textFail "$regx: Amazon ES domain $domain does not have enforce HTTPS enabled" "$regx" "$domain"
fi
done
else
textInfo "$regx: No Amazon ES domain found" "$regx"
fi
done
}

View File

@@ -1,50 +0,0 @@
#!/usr/bin/env bash
# Prowler - the handy cloud security tool (copyright 2020) by Toni de la Fuente
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
CHECK_ID_extra784="7.84"
CHECK_TITLE_extra784="[extra784] Check if Amazon Elasticsearch Service (ES) domains internal user database enabled"
CHECK_SCORED_extra784="NOT_SCORED"
CHECK_CIS_LEVEL_extra784="EXTRA"
CHECK_SEVERITY_extra784="Medium"
CHECK_ASFF_RESOURCE_TYPE_extra784="AwsElasticsearchDomain"
CHECK_ALTERNATE_check784="extra784"
CHECK_SERVICENAME_extra784="es"
CHECK_RISK_extra784='Internal User Database is convenient for demos; for production environment use Federated authentication.'
CHECK_REMEDIATION_extra784='Remove users from internal user database and uso Cognito instead.'
CHECK_DOC_extra784='https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/fgac.html'
CHECK_CAF_EPIC_extra784='IAM'
extra784(){
for regx in $REGIONS; do
LIST_OF_DOMAINS=$($AWSCLI es list-domain-names $PROFILE_OPT --region $regx --query 'DomainNames[].DomainName' --output text 2>&1)
if [[ $(echo "$LIST_OF_DOMAINS" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
textInfo "$regx: Access Denied trying to list domain names" "$regx"
continue
fi
if [[ $LIST_OF_DOMAINS ]]; then
for domain in $LIST_OF_DOMAINS;do
CHECK_IF_INTERNALDB_ENABLED=$($AWSCLI es describe-elasticsearch-domain --domain-name $domain $PROFILE_OPT --region $regx --query 'DomainStatus.AdvancedSecurityOptions.InternalUserDatabaseEnabled' --output text 2>&1)
if [[ $(echo "$CHECK_IF_INTERNALDB_ENABLED" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
textInfo "$regx: Access Denied trying to get ES domain $domain" "$regx"
continue
fi
if [[ $(echo "$CHECK_IF_INTERNALDB_ENABLED" | grep -i true) ]];then
textFail "$regx: Amazon ES domain $domain has internal user database enabled" "$regx" "$domain"
else
textPass "$regx: Amazon ES domain $domain does not have internal user database enabled" "$regx" "$domain"
fi
done
else
textInfo "$regx: No Amazon ES domain found" "$regx"
fi
done
}

View File

@@ -1,56 +0,0 @@
#!/usr/bin/env bash
# Prowler - the handy cloud security tool (copyright 2020) by Toni de la Fuente
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
CHECK_ID_extra785="7.85"
CHECK_TITLE_extra785="[extra785] Check if Amazon Elasticsearch Service (ES) domains have updates available"
CHECK_SCORED_extra785="NOT_SCORED"
CHECK_CIS_LEVEL_extra785="EXTRA"
CHECK_SEVERITY_extra785="Low"
CHECK_ASFF_RESOURCE_TYPE_extra785="AwsElasticsearchDomain"
CHECK_ALTERNATE_check785="extra785"
CHECK_SERVICENAME_extra785="es"
CHECK_RISK_extra785='Amazon ES regularly releases system software updates that add features or otherwise improve your domains.'
CHECK_REMEDIATION_extra785='The Notifications panel in the console is the easiest way to see if an update is available or check the status of an update. You can also receive these notifications through Amazon EventBridge. If you take no action on required updates; Amazon ES still updates your domain service software automatically after a certain timeframe (typically two weeks). In this situation; Amazon ES sends notifications when it starts the update and when the update is complete.'
CHECK_DOC_extra785='https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-service-software.html'
CHECK_CAF_EPIC_extra785='Infrastructure Security'
# NOTE!
# API does not properly shows if an update is available while it is a new version available
# that can be done using the Console but not the API, not sure if it is a bug
# I have to investigate further
extra785(){
for regx in ${REGIONS}; do
LIST_OF_DOMAINS=$("${AWSCLI}" es list-domain-names ${PROFILE_OPT} --region "${regx}" --query 'DomainNames[].DomainName' --output text 2>&1)
if [[ $(echo "${LIST_OF_DOMAINS}" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
textInfo "${regx}: Access Denied trying to list domain names" "${regx}"
continue
fi
if [[ "${LIST_OF_DOMAINS}" ]]; then
for domain in ${LIST_OF_DOMAINS}; do
CHECK_IF_UPDATE_AVAILABLE_AND_VERSION=$("${AWSCLI}" es describe-elasticsearch-domain --domain-name "${domain}" ${PROFILE_OPT} --region "${regx}" --query 'DomainStatus.[ServiceSoftwareOptions.UpdateAvailable,ElasticsearchVersion]' --output text 2>&1)
if [[ $(echo "${CHECK_IF_UPDATE_AVAILABLE_AND_VERSION}" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
textInfo "${regx}: Access Denied trying to get ES domain ${domain}" "${regx}"
continue
fi
read -r update_status es_version <<< "${CHECK_IF_UPDATE_AVAILABLE_AND_VERSION}" &&
if [[ $(tr '[:upper:]' '[:lower:]' <<< "${update_status}") != "false" ]]; then
textInfo "${regx}: Amazon ES domain ${domain} v${es_version} has updates available" "${regx}" "${domain}"
else
textPass "${regx}: Amazon ES domain ${domain} v${es_version} does not have have updates available" "${regx}" "${domain}"
fi
done
else
textInfo "${regx}: No Amazon ES domain found" "${regx}"
fi
done
}

View File

@@ -1,92 +0,0 @@
#!/usr/bin/env bash
# Prowler - the handy cloud security tool (copyright 2020) by Toni de la Fuente
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
CHECK_ID_extra787="7.87"
CHECK_TITLE_extra787="[extra787] Check connection and authentication for Internet exposed Elasticsearch/Kibana ports"
CHECK_SCORED_extra787="NOT_SCORED"
CHECK_CIS_LEVEL_extra787="EXTRA"
CHECK_SEVERITY_extra787="Critical"
CHECK_ASFF_RESOURCE_TYPE_extra787="AwsEc2Instance"
CHECK_ALTERNATE_check787="extra787"
CHECK_SERVICENAME_extra787="es"
CHECK_RISK_extra787='Internet exposed services increases the risk of unauthorised.'
CHECK_REMEDIATION_extra787='Placing an Amazon ES domain within a VPC enables secure communication between Amazon ES and other services within the VPC without the need for an internet gateway; NAT device; or VPN connection. All traffic remains securely within the AWS Cloud.'
CHECK_DOC_extra787='https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-vpc.html'
CHECK_CAF_EPIC_extra787='Infrastructure Security'
extra787(){
# Prowler will try to access each ElasticSearch server to port:
# 9200 API, 9300 Communcation and 5601 Kibana to figure out if authentication is enabled.
# That is from the host where Prowler is running and will try to read indices or get kibana status
ES_API_PORT="9200"
ES_DATA_PORT="9300"
ES_KIBANA_PORT="5601"
for regx in $REGIONS; do
# create a list of SG open to the world with port $ES_API_PORT or $ES_DATA_PORT or $ES_KIBANA_PORT
SG_LIST=$($AWSCLI ec2 describe-security-groups $PROFILE_OPT --region $regx --output text \
--query "SecurityGroups[?length(IpPermissions[?((FromPort==null && ToPort==null) || (FromPort<=\`$ES_API_PORT\` && ToPort>=\`$ES_API_PORT\`) || (FromPort<=\`$ES_DATA_PORT\` && ToPort>=\`$ES_DATA_PORT\`) || (FromPort<=\`$ES_KIBANA_PORT\` && ToPort>=\`$ES_KIBANA_PORT\`)) && (contains(IpRanges[].CidrIp, \`0.0.0.0/0\`) || contains(Ipv6Ranges[].CidrIpv6, \`::/0\`))]) > \`0\`].{GroupId:GroupId}" 2>&1)
if [[ $(echo "$SG_LIST" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
textInfo "$regx: Access Denied trying to describe security groups" "$regx"
continue
fi
# in case of open security groups goes through each one
if [[ $SG_LIST ]];then
for sg in $SG_LIST;do
# temp file store the list of instances IDs and public IP address if found
TEMP_EXTRA787_FILE=$(mktemp -t prowler-${ACCOUNT_NUM}-es-domain.EXTRA787.XXXXXXXXXX)
# finds instances with that open security group attached and get its public ip address (if it has one)
$AWSCLI $PROFILE_OPT --region $regx ec2 describe-instances --filters Name=instance.group-id,Values=$sg --query 'Reservations[*].Instances[*].[InstanceId,PublicIpAddress]' --output text > $TEMP_EXTRA787_FILE
# in case of exposed instances it does access checks
if [[ -s "$TEMP_EXTRA787_FILE" ]];then
while read instance eip ; do
if [[ "$eip" != "None" ]];then
# check for Elasticsearch on port $ES_API_PORT, rest API HTTP.
CHECH_HTTP_ES_API=$(curl -m 2 -s -w "%{http_code}" -o /dev/null -X GET "http://$eip:$ES_API_PORT/_cat/indices")
httpStatus $CHECH_HTTP_ES_API
if [[ $CHECH_HTTP_ES_API -eq "200" ]];then
textFail "$regx: Found instance $instance with public IP $eip on Security Group: $sg with Elasticsearch port $ES_API_PORT response $SERVER_RESPONSE" "$regx" "$instance"
else
textInfo "$regx: Found instance $instance with public IP $eip on Security Group: $sg with Elasticsearch port $ES_API_PORT response $SERVER_RESPONSE" "$regx" "$instance"
fi
# check for port $ES_DATA_PORT TCP, this is the communication port, not:
# test_tcp_connectivity is in include/os_detector
# syntax is 'test_tcp_connectivity $HOST $PORT $TIMEOUT' (in seconds)
CHECH_HTTP_ES_DATA=$(test_tcp_connectivity $eip $ES_DATA_PORT 2)
# Using HTTP error codes here as well to reuse httpStatus function
# codes for better handling, so 200 is open and 000 is not responding
httpStatus $CHECH_HTTP_ES_DATA
if [[ $CHECH_HTTP_ES_DATA -eq "200" ]];then
textFail "$regx: Found instance $instance with public IP $eip on Security Group: $sg with Elasticsearch port $ES_DATA_PORT response $SERVER_RESPONSE" "$regx" "$instance"
else
textInfo "$regx: Found instance $instance with public IP $eip on Security Group: $sg with Elasticsearch port $ES_DATA_PORT response $SERVER_RESPONSE" "$regx" "$instance"
fi
# check for Kibana on port $ES_KIBANA_PORT
CHECH_HTTP_ES_KIBANA=$(curl -m 2 -s -w "%{http_code}" -o /dev/null -X GET "http://$eip:$ES_KIBANA_PORT/api/status")
httpStatus $CHECH_HTTP_ES_KIBANA
if [[ $CHECH_AUTH_5601 -eq "200" ]];then
textFail "$regx: Found instance $instance with public IP $eip on Security Group: $sg with Kibana on port $ES_KIBANA_PORT response $SERVER_RESPONSE" "$regx" "$instance"
else
textInfo "$regx: Found instance $instance with public IP $eip on Security Group: $sg with Kibana on port $ES_KIBANA_PORT response $SERVER_RESPONSE" "$regx" "$instance"
fi
else
textInfo "$regx: Found instance $instance with private IP on Security Group: $sg" "$regx"
fi
done < <(cat $TEMP_EXTRA787_FILE)
fi
rm -rf $TEMP_EXTRA787_FILE
done
else
textPass "$regx: No Security Groups found open to 0.0.0.0/0 for Elasticsearch/Kibana ports" "$regx"
fi
done
}

View File

@@ -1,111 +0,0 @@
#!/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.
CHECK_ID_extra788="7.88"
CHECK_TITLE_extra788="[extra788] Check connection and authentication for Internet exposed Amazon Elasticsearch Service (ES) domains"
CHECK_SCORED_extra788="NOT_SCORED"
CHECK_CIS_LEVEL_extra788="EXTRA"
CHECK_SEVERITY_extra788="Critical"
CHECK_ASFF_RESOURCE_TYPE_extra788="AwsElasticsearchDomain"
CHECK_ALTERNATE_check788="extra788"
CHECK_SERVICENAME_extra788="es"
CHECK_RISK_extra788='Internet exposed services increases the risk of unauthorised.'
CHECK_REMEDIATION_extra788='Placing an Amazon ES domain within a VPC enables secure communication between Amazon ES and other services within the VPC without the need for an internet gateway; NAT device; or VPN connection. All traffic remains securely within the AWS Cloud.'
CHECK_DOC_extra788='https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-vpc.html'
CHECK_CAF_EPIC_extra788='Infrastructure Security'
extra788(){
# Prowler will try to access each ElasticSearch server to the public URI endpoint.
# That is from the host where Prowler is running and will try to read indices or get kibana status
# "Check if Elasticsearch Service domains allow open access "
for regx in $REGIONS; do
LIST_OF_DOMAINS=$($AWSCLI es list-domain-names $PROFILE_OPT --region $regx --query 'DomainNames[].DomainName' --output text 2>&1)
if [[ $(echo "$LIST_OF_DOMAINS" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
textInfo "$regx: Access Denied trying to list domain names" "$regx"
continue
fi
if [[ $LIST_OF_DOMAINS ]]; then
TEMP_POLICY_FILE=$(mktemp -t prowler-${ACCOUNT_NUM}-es-domain.policy.XXXXXXXXXX)
for domain in $LIST_OF_DOMAINS;do
# get endpoint or vpc endpoints
ES_DOMAIN_INFO=$($AWSCLI es describe-elasticsearch-domain --domain-name $domain $PROFILE_OPT --region $regx --query 'DomainStatus.[Endpoint, Endpoints.vpc, VPCOptions.VPCId]' --output text 2>&1)
if [[ $(echo "$ES_DOMAIN_INFO" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
textInfo "$regx: Access Denied trying to get domain $domain" "$regx"
continue
fi
read ES_DOMAIN_ENDPOINT ES_DOMAIN_ENDPOINT_VPC ES_DOMAIN_VPC <<< "$ES_DOMAIN_INFO" &&
# If the endpoint starts with "vpc-" it is in a VPC then it is fine.
if [[ "${ES_DOMAIN_ENDPOINT_VPC:0:3}" == "vpc" ]]; then
textInfo "$regx: Amazon ES domain $domain is in VPC $ES_DOMAIN_VPC run extra779 to make sure it is not exposed using custom proxy" "$regx"
else
$AWSCLI es describe-elasticsearch-domain-config --domain-name $domain $PROFILE_OPT --region $regx --query 'DomainConfig.AccessPolicies.Options' --output text > $TEMP_POLICY_FILE 2>&1
if [[ $(grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError' $TEMP_POLICY_FILE) ]]; then
textInfo "$regx: Access Denied trying to get domain config for $domain" "$regx"
continue
fi
CHECK_ES_DOMAIN_POLICY_OPEN=$(cat $TEMP_POLICY_FILE | jq -r '. | .Statement[] | select(.Effect == "Allow" and (((.Principal|type == "object") and .Principal.AWS == "*") or ((.Principal|type == "string") and .Principal == "*")) and select(has("Condition") | not))')
CHECK_ES_DOMAIN_POLICY_HAS_CONDITION=$(cat $TEMP_POLICY_FILE | jq -r '. | .Statement[] | select(.Effect == "Allow" and (((.Principal|type == "object") and .Principal.AWS == "*") or ((.Principal|type == "string") and .Principal == "*")) and select(has("Condition")))' )
if [[ $CHECK_ES_DOMAIN_POLICY_HAS_CONDITION ]]; then
# get content of IpAddress."aws:SourceIp" and get a clean list
LIST_CONDITION_IPS=$(cat $TEMP_POLICY_FILE | jq '.Statement[0] .Condition.IpAddress."aws:SourceIp"'| awk -F'"' '{print $2}' | tr -d '",^$' | sed '/^$/d')
unset CONDITION_HAS_PUBLIC_IP_ARRAY
for condition_ip in "${LIST_CONDITION_IPS}";do
CONDITION_HAS_PRIVATE_IP=$(echo "${condition_ip}" | grep -E '^(192\.168|10\.|172\.1[6789]\.|172\.2[0-9]\.|172\.3[01]\.)')
if [[ $CONDITION_HAS_PRIVATE_IP ]];then
CONDITION_HAS_PRIVATE_IP_ARRAY+=($condition_ip)
fi
CONDITION_HAS_PUBLIC_IP=$(echo "${condition_ip}" | grep -vE '^(192\.168|10\.|172\.1[6789]\.|172\.2[0-9]\.|172\.3[01]\.|0\.0\.0\.0|\*)')
if [[ $CONDITION_HAS_PUBLIC_IP ]];then
CONDITION_HAS_PUBLIC_IP_ARRAY+=($condition_ip)
fi
CONDITION_HAS_ZERO_NET=$(echo "${condition_ip}" | grep -E '^(0\.0\.0\.0)')
CONDITION_HAS_STAR=$(echo "${condition_ip}" | grep -E '^\*')
done
CHECK_ES_DOMAIN_POLICY_CONDITION_PRIVATE_IP=${CONDITION_HAS_PRIVATE_IP_ARRAY[@]}
CHECK_ES_DOMAIN_POLICY_CONDITION_PUBLIC_IP=${CONDITION_HAS_PUBLIC_IP_ARRAY[@]}
CHECK_ES_DOMAIN_POLICY_CONDITION_ZERO=$CONDITION_HAS_ZERO_NET
CHECK_ES_DOMAIN_POLICY_CONDITION_STAR=$CONDITION_HAS_STAR
fi
if [[ $CHECK_ES_DOMAIN_POLICY_OPEN || $CHECK_ES_DOMAIN_POLICY_CONDITION_ZERO || $CHECK_ES_DOMAIN_POLICY_CONDITION_STAR || ${CHECK_ES_DOMAIN_POLICY_CONDITION_PUBLIC_IP[@]} ]];then
#Prowler will check to read indices or kibaba status if no conditions, condition IP is *, 0.0.0.0/0, 0.0.0.0/8 or any public IP.
# check for REST API on port 443
CHECH_ES_HTTPS=$(curl -m 2 -s -w "%{http_code}" -o /dev/null -X GET "https://$ES_DOMAIN_ENDPOINT/_cat/indices")
httpStatus $CHECH_ES_HTTPS
if [[ $CHECH_ES_HTTPS -eq "200" ]];then
textFail "$regx: Amazon ES domain $domain policy allows Anonymous access and ES service endpoint $ES_DOMAIN_ENDPOINT responded $SERVER_RESPONSE" "$regx"
else
textInfo "$regx: Amazon ES domain $domain policy allows Anonymous access but ES service endpoint $ES_DOMAIN_ENDPOINT responded $SERVER_RESPONSE" "$regx"
fi
# check for Kibana on port 443
CHECH_KIBANA_HTTPS=$(curl -m 2 -s -w "%{http_code}" -o /dev/null -X GET "https://$ES_DOMAIN_ENDPOINT/_plugin/kibana")
httpStatus $CHECH_KIBANA_HTTPS
if [[ $CHECH_KIBANA_HTTPS -eq "200" || $CHECH_KIBANA_HTTPS -eq "301" || $CHECH_KIBANA_HTTPS -eq "302" ]];then
textFail "$regx: Amazon ES domain $domain policy allows Anonymous access and Kibana service endpoint $ES_DOMAIN_ENDPOINT responded $SERVER_RESPONSE" "$regx" "$domain"
else
textInfo "$regx: Amazon ES domain $domain policy allows Anonymous access but Kibana service endpoint $ES_DOMAIN_ENDPOINT responded $SERVER_RESPONSE" "$regx" "$domain"
fi
else
if [[ $CHECK_ES_DOMAIN_POLICY_HAS_CONDITION && ${CHECK_ES_DOMAIN_POLICY_CONDITION_PRIVATE_IP[@]} ]];then
textInfo "$regx: Amazon ES domain $domain policy allows access from a Private IP or CIDR RFC1918 $(echo ${CONDITION_HAS_PRIVATE_IP_ARRAY[@]})" "$regx"
else
textPass "$regx: Amazon ES domain $domain does not allow Anonymous cross account access" "$regx" "$domain"
fi
fi
fi
done
[[ -f "${TEMP_POLICY_FILE}" ]] && rm -f $TEMP_POLICY_FILE
else
textInfo "$regx: No Amazon ES domain found" "$regx"
fi
done
}

View File

@@ -0,0 +1,4 @@
from providers.aws.lib.audit_info.audit_info import current_audit_info
from providers.aws.services.opensearch.opensearch_service import OpenSearchService
opensearch_client = OpenSearchService(current_audit_info)

View File

@@ -0,0 +1,141 @@
import threading
from json import loads
from pydantic import BaseModel
from lib.logger import logger
from providers.aws.aws_provider import generate_regional_clients
################################ OpenSearch
class OpenSearchService:
def __init__(self, audit_info):
self.service = "opensearch"
self.session = audit_info.audit_session
self.regional_clients = generate_regional_clients(self.service, audit_info)
self.opensearch_domains = []
self.__threading_call__(self.__list_domain_names__)
self.__describe_domain_config__(self.regional_clients)
self.__describe_domain__(self.regional_clients)
def __get_session__(self):
return self.session
def __threading_call__(self, call):
threads = []
for regional_client in self.regional_clients.values():
threads.append(threading.Thread(target=call, args=(regional_client,)))
for t in threads:
t.start()
for t in threads:
t.join()
def __list_domain_names__(self, regional_client):
logger.info("OpenSearch - listing domain names...")
try:
domains = regional_client.list_domain_names()
for domain in domains["DomainNames"]:
self.opensearch_domains.append(
OpenSearchDomain(
name=domain["DomainName"], region=regional_client.region
)
)
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def __describe_domain_config__(self, regional_clients):
logger.info("OpenSearch - describing domain configurations...")
try:
for domain in self.opensearch_domains:
regional_client = regional_clients[domain.region]
describe_domain = regional_client.describe_domain_config(
DomainName=domain.name
)
for logging_key in [
"SEARCH_SLOW_LOGS",
"INDEX_SLOW_LOGS",
"AUDIT_LOGS",
]:
if (
logging_key
in describe_domain["DomainConfig"]["LogPublishingOptions"][
"Options"
]
):
domain.logging.append(
PublishingLoggingOption(
name=logging_key,
enabled=describe_domain["DomainConfig"][
"LogPublishingOptions"
]["Options"][logging_key]["Enabled"],
)
)
domain.access_policy = loads(
describe_domain["DomainConfig"]["AccessPolicies"]["Options"]
)
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def __describe_domain__(self, regional_clients):
logger.info("OpenSearch - describing domain configurations...")
try:
for domain in self.opensearch_domains:
regional_client = regional_clients[domain.region]
describe_domain = regional_client.describe_domain(
DomainName=domain.name
)
domain.arn = describe_domain["DomainStatus"]["ARN"]
if "vpc" in describe_domain["DomainStatus"]["Endpoints"]:
domain.endpoint_vpc = describe_domain["DomainStatus"]["Endpoints"][
"vpc"
]
domain.vpc_id = describe_domain["DomainStatus"]["VPCOptions"]["VPCId"]
domain.cognito_options = describe_domain["DomainStatus"][
"CognitoOptions"
]["Enabled"]
domain.encryption_at_rest = describe_domain["DomainStatus"][
"EncryptionAtRestOptions"
]["Enabled"]
domain.node_to_node_encryption = describe_domain["DomainStatus"][
"NodeToNodeEncryptionOptions"
]["Enabled"]
domain.enforce_https = describe_domain["DomainStatus"][
"DomainEndpointOptions"
]["EnforceHTTPS"]
domain.internal_user_database = describe_domain["DomainStatus"][
"AdvancedSecurityOptions"
]["InternalUserDatabaseEnabled"]
domain.update_available = describe_domain["DomainStatus"][
"ServiceSoftwareOptions"
]["UpdateAvailable"]
domain.version = describe_domain["DomainStatus"]["EngineVersion"]
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
class PublishingLoggingOption(BaseModel):
name: str
enabled: bool
class OpenSearchDomain(BaseModel):
name: str
region: str
arn: str = None
logging: list[PublishingLoggingOption] = []
endpoint_vpc: str = None
vpc_id: str = None
access_policy: dict = None
cognito_options: bool = None
encryption_at_rest: bool = None
node_to_node_encryption: bool = None
enforce_https: bool = None
internal_user_database: bool = None
update_available: bool = None
version: str = None

View File

@@ -0,0 +1,35 @@
{
"Provider": "aws",
"CheckID": "opensearch_service_domains_audit_logging_enabled",
"CheckTitle": "Check if Amazon Elasticsearch/Opensearch Service domains have audit logging enabled",
"CheckType": ["Identify", "Logging"],
"ServiceName": "opensearch",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "low",
"ResourceType": "AwsOpenSearchDomain",
"Description": "Check if Amazon Elasticsearch/Opensearch Service domains have audit logging enabled",
"Risk": "If logs are not enabled; monitoring of service use and threat analysis is not possible.",
"RelatedUrl": "",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Make sure you are logging information about Amazon Elasticsearch Service operations.",
"Url": "https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/audit-logs.html"
}
},
"Categories": [],
"Tags": {
"Tag1Key": "value",
"Tag2Key": "value"
},
"DependsOn": [],
"RelatedTo": [],
"Notes": "",
"Compliance": []
}

View File

@@ -0,0 +1,26 @@
from lib.check.models import Check, Check_Report
from providers.aws.services.opensearch.opensearch_client import opensearch_client
class opensearch_service_domains_audit_logging_enabled(Check):
def execute(self):
findings = []
for domain in opensearch_client.opensearch_domains:
report = Check_Report(self.metadata)
report.region = domain.region
report.resource_id = domain.name
report.resource_arn = domain.arn
report.status = "FAIL"
report.status_extended = (
f"Opensearch domain {domain.name} AUDIT_LOGS disabled"
)
for logging_item in domain.logging:
if logging_item.name == "AUDIT_LOGS" and logging_item.enabled:
report.status = "PASS"
report.status_extended = (
f"Opensearch domain {domain.name} AUDIT_LOGS enabled"
)
findings.append(report)
return findings

View File

@@ -0,0 +1,81 @@
from re import search
from unittest import mock
from providers.aws.services.opensearch.opensearch_service import (
OpenSearchDomain,
PublishingLoggingOption,
)
AWS_REGION = "eu-west-1"
AWS_ACCOUNT_NUMBER = "123456789012"
domain_name = "test-domain"
domain_arn = f"arn:aws:es:us-west-2:{AWS_ACCOUNT_NUMBER}:domain/{domain_name}"
class Test_opensearch_service_domains_audit_logging_enabled:
def test_no_domains(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_audit_logging_enabled.opensearch_service_domains_audit_logging_enabled import (
opensearch_service_domains_audit_logging_enabled,
)
check = opensearch_service_domains_audit_logging_enabled()
result = check.execute()
assert len(result) == 0
def test_no_logging_enabled(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
opensearch_client.opensearch_domains.append(
OpenSearchDomain(name=domain_name, region=AWS_REGION, arn=domain_arn)
)
opensearch_client.opensearch_domains[0].logging = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_audit_logging_enabled.opensearch_service_domains_audit_logging_enabled import (
opensearch_service_domains_audit_logging_enabled,
)
check = opensearch_service_domains_audit_logging_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert search("AUDIT_LOGS disabled", result[0].status_extended)
assert result[0].resource_id == domain_name
assert result[0].resource_arn == domain_arn
def test_logging_AUDIT_LOGS_enabled(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
opensearch_client.opensearch_domains.append(
OpenSearchDomain(name=domain_name, region=AWS_REGION, arn=domain_arn)
)
opensearch_client.opensearch_domains[0].logging = []
opensearch_client.opensearch_domains[0].logging.append(
PublishingLoggingOption(name="AUDIT_LOGS", enabled=True)
)
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_audit_logging_enabled.opensearch_service_domains_audit_logging_enabled import (
opensearch_service_domains_audit_logging_enabled,
)
check = opensearch_service_domains_audit_logging_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert search("AUDIT_LOGS enabled", result[0].status_extended)
assert result[0].resource_id == domain_name
assert result[0].resource_arn == domain_arn

View File

@@ -0,0 +1,35 @@
{
"Provider": "aws",
"CheckID": "opensearch_service_domains_cloudwatch_logging_enabled",
"CheckTitle": "Check if Amazon Elasticsearch/Opensearch Service domains have logging enabled",
"CheckType": ["Identify", "Logging"],
"ServiceName": "opensearch",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsOpenSearchDomain",
"Description": "Check if Amazon Elasticsearch/Opensearch Service domains have logging enabled",
"Risk": "Amazon ES exposes four Elasticsearch/Opensearch logs through Amazon CloudWatch Logs: error logs; search slow logs; index slow logs; and audit logs.",
"RelatedUrl": "",
"Remediation": {
"Code": {
"CLI": "https://docs.bridgecrew.io/docs/elasticsearch_7#cli-command",
"NativeIaC": "https://docs.bridgecrew.io/docs/elasticsearch_7#cloudformation",
"Other": "https://docs.bridgecrew.io/docs/elasticsearch_7#fix---runtime",
"Terraform": "https://docs.bridgecrew.io/docs/elasticsearch_7#fix---buildtime"
},
"Recommendation": {
"Text": "Enable Elasticsearch/Opensearch log. Create use cases for them. Using audit logs check for access denied events.",
"Url": "https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-createdomain-configure-slow-logs.html"
}
},
"Categories": [],
"Tags": {
"Tag1Key": "value",
"Tag2Key": "value"
},
"DependsOn": [],
"RelatedTo": [],
"Notes": "",
"Compliance": []
}

View File

@@ -0,0 +1,35 @@
from lib.check.models import Check, Check_Report
from providers.aws.services.opensearch.opensearch_client import opensearch_client
class opensearch_service_domains_cloudwatch_logging_enabled(Check):
def execute(self):
findings = []
for domain in opensearch_client.opensearch_domains:
report = Check_Report(self.metadata)
report.region = domain.region
report.resource_id = domain.name
report.resource_arn = domain.arn
report.status = "FAIL"
report.status_extended = f"Opensearch domain {domain.name} SEARCH_SLOW_LOGS and INDEX_SLOW_LOGS disabled"
has_SEARCH_SLOW_LOGS = False
has_INDEX_SLOW_LOGS = False
for logging_item in domain.logging:
if logging_item.name == "SEARCH_SLOW_LOGS" and logging_item.enabled:
has_SEARCH_SLOW_LOGS = True
if logging_item.name == "INDEX_SLOW_LOGS" and logging_item.enabled:
has_INDEX_SLOW_LOGS = True
if has_SEARCH_SLOW_LOGS and has_INDEX_SLOW_LOGS:
report.status = "PASS"
report.status_extended = f"Opensearch domain {domain.name} SEARCH_SLOW_LOGS and INDEX_SLOW_LOGS enabled"
elif not has_SEARCH_SLOW_LOGS and has_INDEX_SLOW_LOGS:
report.status = "FAIL"
report.status_extended = f"Opensearch domain {domain.name} INDEX_SLOW_LOGS enabled but SEARCH_SLOW_LOGS disabled"
elif not has_INDEX_SLOW_LOGS and has_SEARCH_SLOW_LOGS:
report.status = "FAIL"
report.status_extended = f"Opensearch domain {domain.name} SEARCH_SLOW_LOGS enabled but INDEX_SLOW_LOGS disabled"
findings.append(report)
return findings

View File

@@ -0,0 +1,149 @@
from re import search
from unittest import mock
from providers.aws.services.opensearch.opensearch_service import (
OpenSearchDomain,
PublishingLoggingOption,
)
AWS_REGION = "eu-west-1"
AWS_ACCOUNT_NUMBER = "123456789012"
domain_name = "test-domain"
domain_arn = f"arn:aws:es:us-west-2:{AWS_ACCOUNT_NUMBER}:domain/{domain_name}"
class Test_opensearch_service_domains_cloudwatch_logging_enabled:
def test_no_domains(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_cloudwatch_logging_enabled.opensearch_service_domains_cloudwatch_logging_enabled import (
opensearch_service_domains_cloudwatch_logging_enabled,
)
check = opensearch_service_domains_cloudwatch_logging_enabled()
result = check.execute()
assert len(result) == 0
def test_no_logging_enabled(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
opensearch_client.opensearch_domains.append(
OpenSearchDomain(name=domain_name, region=AWS_REGION, arn=domain_arn)
)
opensearch_client.opensearch_domains[0].logging = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_cloudwatch_logging_enabled.opensearch_service_domains_cloudwatch_logging_enabled import (
opensearch_service_domains_cloudwatch_logging_enabled,
)
check = opensearch_service_domains_cloudwatch_logging_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert search(
"SEARCH_SLOW_LOGS and INDEX_SLOW_LOGS disabled",
result[0].status_extended,
)
assert result[0].resource_id == domain_name
assert result[0].resource_arn == domain_arn
def test_logging_SEARCH_SLOW_LOGS_enabled(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
opensearch_client.opensearch_domains.append(
OpenSearchDomain(name=domain_name, region=AWS_REGION, arn=domain_arn)
)
opensearch_client.opensearch_domains[0].logging = []
opensearch_client.opensearch_domains[0].logging.append(
PublishingLoggingOption(name="SEARCH_SLOW_LOGS", enabled=True)
)
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_cloudwatch_logging_enabled.opensearch_service_domains_cloudwatch_logging_enabled import (
opensearch_service_domains_cloudwatch_logging_enabled,
)
check = opensearch_service_domains_cloudwatch_logging_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert search(
"SEARCH_SLOW_LOGS enabled but INDEX_SLOW_LOGS disabled",
result[0].status_extended,
)
assert result[0].resource_id == domain_name
assert result[0].resource_arn == domain_arn
def test_logging_INDEX_SLOW_LOGS_enabled(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
opensearch_client.opensearch_domains.append(
OpenSearchDomain(name=domain_name, region=AWS_REGION, arn=domain_arn)
)
opensearch_client.opensearch_domains[0].logging = []
opensearch_client.opensearch_domains[0].logging.append(
PublishingLoggingOption(name="INDEX_SLOW_LOGS", enabled=True)
)
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_cloudwatch_logging_enabled.opensearch_service_domains_cloudwatch_logging_enabled import (
opensearch_service_domains_cloudwatch_logging_enabled,
)
check = opensearch_service_domains_cloudwatch_logging_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert search(
"INDEX_SLOW_LOGS enabled but SEARCH_SLOW_LOGS disabled",
result[0].status_extended,
)
assert result[0].resource_id == domain_name
assert result[0].resource_arn == domain_arn
def test_logging_INDEX_SLOW_LOGS_and_SEARCH_SLOW_LOGS_enabled(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
opensearch_client.opensearch_domains.append(
OpenSearchDomain(name=domain_name, region=AWS_REGION, arn=domain_arn)
)
opensearch_client.opensearch_domains[0].logging = []
logging_options = [
PublishingLoggingOption(name="INDEX_SLOW_LOGS", enabled=True),
PublishingLoggingOption(name="SEARCH_SLOW_LOGS", enabled=True),
]
opensearch_client.opensearch_domains[0].logging.extend(logging_options)
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_cloudwatch_logging_enabled.opensearch_service_domains_cloudwatch_logging_enabled import (
opensearch_service_domains_cloudwatch_logging_enabled,
)
check = opensearch_service_domains_cloudwatch_logging_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert search(
"SEARCH_SLOW_LOGS and INDEX_SLOW_LOGS enabled",
result[0].status_extended,
)
assert result[0].resource_id == domain_name
assert result[0].resource_arn == domain_arn

View File

@@ -0,0 +1,35 @@
{
"Provider": "aws",
"CheckID": "opensearch_service_domains_encryption_at_rest_enabled",
"CheckTitle": "Check if Amazon Elasticsearch/Opensearch Service domains have encryption at-rest enabled",
"CheckType": ["Protect", "Data protection", "Encryption of data at rest"],
"ServiceName": "opensearch",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsOpenSearchDomain",
"Description": "Check if Amazon Elasticsearch/Opensearch Service domains have encryption at-rest enabled",
"Risk": "If not enable unauthorized access to your data could risk increases.",
"RelatedUrl": "",
"Remediation": {
"Code": {
"CLI": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/Elasticsearch/encryption-at-rest.html",
"NativeIaC": "https://docs.bridgecrew.io/docs/elasticsearch_3-enable-encryptionatrest#fix---builtime",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/Elasticsearch/encryption-at-rest.html",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable encryption at rest using AWS KMS to store and manage your encryption keys and the Advanced Encryption Standard algorithm with 256-bit keys (AES-256) to perform the encryption.",
"Url": "https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/encryption-at-rest.html"
}
},
"Categories": [],
"Tags": {
"Tag1Key": "value",
"Tag2Key": "value"
},
"DependsOn": [],
"RelatedTo": [],
"Notes": "",
"Compliance": []
}

View File

@@ -0,0 +1,23 @@
from lib.check.models import Check, Check_Report
from providers.aws.services.opensearch.opensearch_client import opensearch_client
class opensearch_service_domains_encryption_at_rest_enabled(Check):
def execute(self):
findings = []
for domain in opensearch_client.opensearch_domains:
report = Check_Report(self.metadata)
report.region = domain.region
report.resource_id = domain.name
report.resource_arn = domain.arn
report.status = "PASS"
report.status_extended = (
f"Opensearch domain {domain.name} has encryption at-rest enabled"
)
if not domain.encryption_at_rest:
report.status = "FAIL"
report.status_extended = f"Opensearch domain {domain.name} does not have encryption at-rest enabled"
findings.append(report)
return findings

View File

@@ -0,0 +1,87 @@
from re import search
from unittest import mock
from providers.aws.services.opensearch.opensearch_service import OpenSearchDomain
AWS_REGION = "eu-west-1"
AWS_ACCOUNT_NUMBER = "123456789012"
domain_name = "test-domain"
domain_arn = f"arn:aws:es:us-west-2:{AWS_ACCOUNT_NUMBER}:domain/{domain_name}"
class Test_opensearch_service_domains_encryption_at_rest_enabled:
def test_no_domains(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_encryption_at_rest_enabled.opensearch_service_domains_encryption_at_rest_enabled import (
opensearch_service_domains_encryption_at_rest_enabled,
)
check = opensearch_service_domains_encryption_at_rest_enabled()
result = check.execute()
assert len(result) == 0
def test_no_encryption_at_rest_enabled(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
opensearch_client.opensearch_domains.append(
OpenSearchDomain(
name=domain_name,
region=AWS_REGION,
arn=domain_arn,
encryption_at_rest=False,
)
)
opensearch_client.opensearch_domains[0].logging = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_encryption_at_rest_enabled.opensearch_service_domains_encryption_at_rest_enabled import (
opensearch_service_domains_encryption_at_rest_enabled,
)
check = opensearch_service_domains_encryption_at_rest_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert search(
"does not have encryption at-rest enabled", result[0].status_extended
)
assert result[0].resource_id == domain_name
assert result[0].resource_arn == domain_arn
def test_encryption_at_rest_enabled(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
opensearch_client.opensearch_domains.append(
OpenSearchDomain(
name=domain_name,
region=AWS_REGION,
arn=domain_arn,
encryption_at_rest=True,
)
)
opensearch_client.opensearch_domains[0].logging = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_encryption_at_rest_enabled.opensearch_service_domains_encryption_at_rest_enabled import (
opensearch_service_domains_encryption_at_rest_enabled,
)
check = opensearch_service_domains_encryption_at_rest_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert search("has encryption at-rest enabled", result[0].status_extended)
assert result[0].resource_id == domain_name
assert result[0].resource_arn == domain_arn

View File

@@ -0,0 +1,35 @@
{
"Provider": "aws",
"CheckID": "opensearch_service_domains_https_communications_enforced",
"CheckTitle": "Check if Amazon Elasticsearch/Opensearch Service domains have enforce HTTPS enabled",
"CheckType": ["Protect", "Data protection", "Encryption of data in transit"],
"ServiceName": "opensearch",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsOpenSearchDomain",
"Description": "Check if Amazon Elasticsearch/Opensearch Service domains have enforce HTTPS enabled",
"Risk": "If not enable unauthorized access to your data could risk increases.",
"RelatedUrl": "",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "https://docs.bridgecrew.io/docs/elasticsearch_6#fix---builtime",
"Other": "https://docs.bridgecrew.io/docs/elasticsearch_6#aws-console",
"Terraform": ""
},
"Recommendation": {
"Text": "When creating ES Domains; enable 'Require HTTPS fo all traffic to the domain'",
"Url": "https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-createupdatedomains.html"
}
},
"Categories": [],
"Tags": {
"Tag1Key": "value",
"Tag2Key": "value"
},
"DependsOn": [],
"RelatedTo": [],
"Notes": "",
"Compliance": []
}

View File

@@ -0,0 +1,23 @@
from lib.check.models import Check, Check_Report
from providers.aws.services.opensearch.opensearch_client import opensearch_client
class opensearch_service_domains_https_communications_enforced(Check):
def execute(self):
findings = []
for domain in opensearch_client.opensearch_domains:
report = Check_Report(self.metadata)
report.region = domain.region
report.resource_id = domain.name
report.resource_arn = domain.arn
report.status = "PASS"
report.status_extended = (
f"Opensearch domain {domain.name} has enforce HTTPS enabled"
)
if not domain.enforce_https:
report.status = "FAIL"
report.status_extended = f"Opensearch domain {domain.name} does not have enforce HTTPS enabled"
findings.append(report)
return findings

View File

@@ -0,0 +1,81 @@
from re import search
from unittest import mock
from providers.aws.services.opensearch.opensearch_service import OpenSearchDomain
AWS_REGION = "eu-west-1"
AWS_ACCOUNT_NUMBER = "123456789012"
domain_name = "test-domain"
domain_arn = f"arn:aws:es:us-west-2:{AWS_ACCOUNT_NUMBER}:domain/{domain_name}"
class Test_opensearch_service_domains_https_communications_enforced:
def test_no_domains(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_https_communications_enforced.opensearch_service_domains_https_communications_enforced import (
opensearch_service_domains_https_communications_enforced,
)
check = opensearch_service_domains_https_communications_enforced()
result = check.execute()
assert len(result) == 0
def test_no_https_enabled(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
opensearch_client.opensearch_domains.append(
OpenSearchDomain(
name=domain_name, region=AWS_REGION, arn=domain_arn, enforce_https=False
)
)
opensearch_client.opensearch_domains[0].logging = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_https_communications_enforced.opensearch_service_domains_https_communications_enforced import (
opensearch_service_domains_https_communications_enforced,
)
check = opensearch_service_domains_https_communications_enforced()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert search(
"does not have enforce HTTPS enabled", result[0].status_extended
)
assert result[0].resource_id == domain_name
assert result[0].resource_arn == domain_arn
def test_https_enabled(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
opensearch_client.opensearch_domains.append(
OpenSearchDomain(
name=domain_name, region=AWS_REGION, arn=domain_arn, enforce_https=True
)
)
opensearch_client.opensearch_domains[0].logging = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_https_communications_enforced.opensearch_service_domains_https_communications_enforced import (
opensearch_service_domains_https_communications_enforced,
)
check = opensearch_service_domains_https_communications_enforced()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert search("has enforce HTTPS enabled", result[0].status_extended)
assert result[0].resource_id == domain_name
assert result[0].resource_arn == domain_arn

View File

@@ -0,0 +1,35 @@
{
"Provider": "aws",
"CheckID": "opensearch_service_domains_internal_user_database_enabled",
"CheckTitle": "Check if Amazon Elasticsearch/Opensearch Service domains have internal user database enabled",
"CheckType": ["Protect", "Data protection"],
"ServiceName": "opensearch",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsOpenSearchDomain",
"Description": "Check if Amazon Elasticsearch/Opensearch Service domains have internal user database enabled",
"Risk": "Internal User Database is convenient for demos; for production environment use Federated authentication.",
"RelatedUrl": "",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Remove users from internal user database and uso Cognito instead.",
"Url": "https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/fgac.html"
}
},
"Categories": [],
"Tags": {
"Tag1Key": "value",
"Tag2Key": "value"
},
"DependsOn": [],
"RelatedTo": [],
"Notes": "",
"Compliance": []
}

View File

@@ -0,0 +1,21 @@
from lib.check.models import Check, Check_Report
from providers.aws.services.opensearch.opensearch_client import opensearch_client
class opensearch_service_domains_internal_user_database_enabled(Check):
def execute(self):
findings = []
for domain in opensearch_client.opensearch_domains:
report = Check_Report(self.metadata)
report.region = domain.region
report.resource_id = domain.name
report.resource_arn = domain.arn
report.status = "PASS"
report.status_extended = f"Opensearch domain {domain.name} does not have internal user database enabled"
if domain.internal_user_database:
report.status = "FAIL"
report.status_extended = f"Opensearch domain {domain.name} has internal user database enabled"
findings.append(report)
return findings

View File

@@ -0,0 +1,90 @@
from re import search
from unittest import mock
from providers.aws.services.opensearch.opensearch_service import OpenSearchDomain
AWS_REGION = "eu-west-1"
AWS_ACCOUNT_NUMBER = "123456789012"
domain_name = "test-domain"
domain_arn = f"arn:aws:es:us-west-2:{AWS_ACCOUNT_NUMBER}:domain/{domain_name}"
class Test_opensearch_service_domains_internal_user_database_enabled:
def test_no_domains(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_internal_user_database_enabled.opensearch_service_domains_internal_user_database_enabled import (
opensearch_service_domains_internal_user_database_enabled,
)
check = opensearch_service_domains_internal_user_database_enabled()
result = check.execute()
assert len(result) == 0
def test_internal_database_disabled(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
opensearch_client.opensearch_domains.append(
OpenSearchDomain(
name=domain_name,
region=AWS_REGION,
arn=domain_arn,
internal_user_database=False,
)
)
opensearch_client.opensearch_domains[0].logging = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_internal_user_database_enabled.opensearch_service_domains_internal_user_database_enabled import (
opensearch_service_domains_internal_user_database_enabled,
)
check = opensearch_service_domains_internal_user_database_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert search(
"does not have internal user database enabled",
result[0].status_extended,
)
assert result[0].resource_id == domain_name
assert result[0].resource_arn == domain_arn
def test_internal_database_enabled(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
opensearch_client.opensearch_domains.append(
OpenSearchDomain(
name=domain_name,
region=AWS_REGION,
arn=domain_arn,
internal_user_database=True,
)
)
opensearch_client.opensearch_domains[0].logging = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_internal_user_database_enabled.opensearch_service_domains_internal_user_database_enabled import (
opensearch_service_domains_internal_user_database_enabled,
)
check = opensearch_service_domains_internal_user_database_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert search(
"has internal user database enabled", result[0].status_extended
)
assert result[0].resource_id == domain_name
assert result[0].resource_arn == domain_arn

View File

@@ -0,0 +1,35 @@
{
"Provider": "aws",
"CheckID": "opensearch_service_domains_node_to_node_encryption_enabled",
"CheckTitle": "Check if Amazon Elasticsearch/Opensearch Service domains have node-to-node encryption enabled",
"CheckType": ["Protect", "Data protection", "Encryption of data in transit"],
"ServiceName": "opensearch",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsOpenSearchDomain",
"Description": "Check if Amazon Elasticsearch/Opensearch Service domains have node-to-node encryption enabled",
"Risk": "Node-to-node encryption provides an additional layer of security on top of the default features of Amazon ES. This architecture prevents potential attackers from intercepting traffic between Elasticsearch nodes and keeps the cluster secure.",
"RelatedUrl": "",
"Remediation": {
"Code": {
"CLI": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/Elasticsearch/node-to-node-encryption.html",
"NativeIaC": "https://docs.bridgecrew.io/docs/elasticsearch_5#fix---builtime",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/Elasticsearch/node-to-node-encryption.html",
"Terraform": ""
},
"Recommendation": {
"Text": "Node-to-node encryption on new domains requires Elasticsearch 6.0 or later. Enabling the feature on existing domains requires Elasticsearch 6.7 or later. Choose the existing domain in the AWS console; Actions; and Modify encryption.",
"Url": "https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/ntn.html"
}
},
"Categories": [],
"Tags": {
"Tag1Key": "value",
"Tag2Key": "value"
},
"DependsOn": [],
"RelatedTo": [],
"Notes": "",
"Compliance": []
}

View File

@@ -0,0 +1,23 @@
from lib.check.models import Check, Check_Report
from providers.aws.services.opensearch.opensearch_client import opensearch_client
class opensearch_service_domains_node_to_node_encryption_enabled(Check):
def execute(self):
findings = []
for domain in opensearch_client.opensearch_domains:
report = Check_Report(self.metadata)
report.region = domain.region
report.resource_id = domain.name
report.resource_arn = domain.arn
report.status = "PASS"
report.status_extended = (
f"Opensearch domain {domain.name} has node-to-node encryption enabled"
)
if not domain.node_to_node_encryption:
report.status = "FAIL"
report.status_extended = f"Opensearch domain {domain.name} does not have node-to-node encryption enabled"
findings.append(report)
return findings

View File

@@ -0,0 +1,90 @@
from re import search
from unittest import mock
from providers.aws.services.opensearch.opensearch_service import OpenSearchDomain
AWS_REGION = "eu-west-1"
AWS_ACCOUNT_NUMBER = "123456789012"
domain_name = "test-domain"
domain_arn = f"arn:aws:es:us-west-2:{AWS_ACCOUNT_NUMBER}:domain/{domain_name}"
class Test_opensearch_service_domains_node_to_node_encryption_enabled:
def test_no_domains(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_node_to_node_encryption_enabled.opensearch_service_domains_node_to_node_encryption_enabled import (
opensearch_service_domains_node_to_node_encryption_enabled,
)
check = opensearch_service_domains_node_to_node_encryption_enabled()
result = check.execute()
assert len(result) == 0
def test_no_encryption_enabled(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
opensearch_client.opensearch_domains.append(
OpenSearchDomain(
name=domain_name,
region=AWS_REGION,
arn=domain_arn,
node_to_node_encryption=False,
)
)
opensearch_client.opensearch_domains[0].logging = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_node_to_node_encryption_enabled.opensearch_service_domains_node_to_node_encryption_enabled import (
opensearch_service_domains_node_to_node_encryption_enabled,
)
check = opensearch_service_domains_node_to_node_encryption_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert search(
"does not have node-to-node encryption enabled",
result[0].status_extended,
)
assert result[0].resource_id == domain_name
assert result[0].resource_arn == domain_arn
def test_encryption_enabled(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
opensearch_client.opensearch_domains.append(
OpenSearchDomain(
name=domain_name,
region=AWS_REGION,
arn=domain_arn,
node_to_node_encryption=True,
)
)
opensearch_client.opensearch_domains[0].logging = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_node_to_node_encryption_enabled.opensearch_service_domains_node_to_node_encryption_enabled import (
opensearch_service_domains_node_to_node_encryption_enabled,
)
check = opensearch_service_domains_node_to_node_encryption_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert search(
"has node-to-node encryption enabled", result[0].status_extended
)
assert result[0].resource_id == domain_name
assert result[0].resource_arn == domain_arn

View File

@@ -0,0 +1,35 @@
{
"Provider": "aws",
"CheckID": "opensearch_service_domains_not_publicly_accessible",
"CheckTitle": "Check if Amazon Opensearch/Elasticsearch domains are set as Public or if it has open policy access",
"CheckType": ["Protect", "Secure Access Management"],
"ServiceName": "opensearch",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "critical",
"ResourceType": "AwsOpenSearchDomain",
"Description": "Check if Amazon Opensearch/Elasticsearch domains are set as Public or if it has open policy access",
"Risk": "Publicly accessible services could expose sensitive data to bad actors.",
"RelatedUrl": "",
"Remediation": {
"Code": {
"CLI": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/Elasticsearch/elasticsearch-domain-exposed.html",
"NativeIaC": "",
"Other": "https://docs.bridgecrew.io/docs/public_3#fix---runtime",
"Terraform": ""
},
"Recommendation": {
"Text": "Use VPC endpoints for internal services.",
"Url": "https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-vpc.html"
}
},
"Categories": [],
"Tags": {
"Tag1Key": "value",
"Tag2Key": "value"
},
"DependsOn": [],
"RelatedTo": [],
"Notes": "",
"Compliance": []
}

View File

@@ -0,0 +1,52 @@
from lib.check.models import Check, Check_Report
from providers.aws.services.opensearch.opensearch_client import opensearch_client
class opensearch_service_domains_not_publicly_accessible(Check):
def execute(self):
findings = []
for domain in opensearch_client.opensearch_domains:
report = Check_Report(self.metadata)
report.region = domain.region
report.resource_id = domain.name
report.resource_arn = domain.arn
report.status = "PASS"
report.status_extended = (
f"Opensearch domain {domain.name} does not allow anonymous access"
)
if domain.access_policy:
for statement in domain.access_policy["Statement"]:
# look for open policies
if (
statement["Effect"] == "Allow"
and (
"AWS" in statement["Principal"]
and "*" in statement["Principal"]["AWS"]
)
or (statement["Principal"] == "*")
):
if "Condition" not in statement:
report.status = "FAIL"
report.status_extended = f"Opensearch domain {domain.name} policy allows access (Principal: '*')"
break
else:
if (
"IpAddress" in statement["Condition"]
and "aws:SourceIp"
in statement["Condition"]["IpAddress"]
):
for ip in statement["Condition"]["IpAddress"][
"aws:SourceIp"
]:
if ip == "*":
report.status = "FAIL"
report.status_extended = f"Opensearch domain {domain.name} policy allows access (Principal: '*') and network *"
break
elif ip == "0.0.0.0/0":
report.status = "FAIL"
report.status_extended = f"Opensearch domain {domain.name} policy allows access (Principal: '*') and network 0.0.0.0/0"
break
findings.append(report)
return findings

View File

@@ -0,0 +1,248 @@
from unittest import mock
from providers.aws.services.opensearch.opensearch_service import OpenSearchDomain
AWS_REGION = "eu-west-1"
AWS_ACCOUNT_NUMBER = "123456789012"
domain_name = "test-domain"
domain_arn = f"arn:aws:es:us-west-2:{AWS_ACCOUNT_NUMBER}:domain/{domain_name}"
policy_data_restricted = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"AWS": [f"{AWS_ACCOUNT_NUMBER}"]},
"Action": ["es:*"],
"Resource": f"arn:aws:es:us-west-2:{AWS_ACCOUNT_NUMBER}:domain/{domain_name}/*",
}
],
}
policy_data_not_restricted = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"AWS": ["*"]},
"Action": ["es:*"],
"Resource": f"arn:aws:es:us-west-2:{AWS_ACCOUNT_NUMBER}:domain/{domain_name}/*",
}
],
}
policy_data_not_restricted_principal = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": ["es:*"],
"Resource": f"arn:aws:es:us-west-2:{AWS_ACCOUNT_NUMBER}:domain/{domain_name}/*",
}
],
}
policy_data_source_ip_full = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"AWS": "*"},
"Action": ["es:ESHttp*"],
"Condition": {"IpAddress": {"aws:SourceIp": ["*"]}},
"Resource": f"arn:aws:es:us-west-2:{AWS_ACCOUNT_NUMBER}:domain/{domain_name}/*",
}
],
}
policy_data_source_whole_internet = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"AWS": "*"},
"Action": ["es:ESHttp*"],
"Condition": {"IpAddress": {"aws:SourceIp": ["0.0.0.0/0"]}},
"Resource": f"arn:aws:es:us-west-2:{AWS_ACCOUNT_NUMBER}:domain/{domain_name}/*",
}
],
}
class Test_opensearch_service_domains_not_publicly_accessible:
def test_no_domains(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_not_publicly_accessible.opensearch_service_domains_not_publicly_accessible import (
opensearch_service_domains_not_publicly_accessible,
)
check = opensearch_service_domains_not_publicly_accessible()
result = check.execute()
assert len(result) == 0
def test_policy_data_restricted(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
opensearch_client.opensearch_domains.append(
OpenSearchDomain(
name=domain_name,
region=AWS_REGION,
arn=domain_arn,
access_policy=policy_data_restricted,
)
)
opensearch_client.opensearch_domains[0].logging = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_not_publicly_accessible.opensearch_service_domains_not_publicly_accessible import (
opensearch_service_domains_not_publicly_accessible,
)
check = opensearch_service_domains_not_publicly_accessible()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Opensearch domain {domain_name} does not allow anonymous access"
)
assert result[0].resource_id == domain_name
assert result[0].resource_arn == domain_arn
def test_policy_data_not_restricted_with_principal_AWS(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
opensearch_client.opensearch_domains.append(
OpenSearchDomain(
name=domain_name,
region=AWS_REGION,
arn=domain_arn,
access_policy=policy_data_not_restricted,
)
)
opensearch_client.opensearch_domains[0].logging = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_not_publicly_accessible.opensearch_service_domains_not_publicly_accessible import (
opensearch_service_domains_not_publicly_accessible,
)
check = opensearch_service_domains_not_publicly_accessible()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Opensearch domain {domain_name} policy allows access (Principal: '*')"
)
assert result[0].resource_id == domain_name
assert result[0].resource_arn == domain_arn
def test_policy_data_not_restricted_with_principal_no_AWS(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
opensearch_client.opensearch_domains.append(
OpenSearchDomain(
name=domain_name,
region=AWS_REGION,
arn=domain_arn,
access_policy=policy_data_not_restricted_principal,
)
)
opensearch_client.opensearch_domains[0].logging = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_not_publicly_accessible.opensearch_service_domains_not_publicly_accessible import (
opensearch_service_domains_not_publicly_accessible,
)
check = opensearch_service_domains_not_publicly_accessible()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Opensearch domain {domain_name} policy allows access (Principal: '*')"
)
assert result[0].resource_id == domain_name
assert result[0].resource_arn == domain_arn
def test_policy_data_not_restricted_ip_full(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
opensearch_client.opensearch_domains.append(
OpenSearchDomain(
name=domain_name,
region=AWS_REGION,
arn=domain_arn,
access_policy=policy_data_source_ip_full,
)
)
opensearch_client.opensearch_domains[0].logging = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_not_publicly_accessible.opensearch_service_domains_not_publicly_accessible import (
opensearch_service_domains_not_publicly_accessible,
)
check = opensearch_service_domains_not_publicly_accessible()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Opensearch domain {domain_name} policy allows access (Principal: '*') and network *"
)
assert result[0].resource_id == domain_name
assert result[0].resource_arn == domain_arn
def test_policy_data_not_restricted_whole_internet(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
opensearch_client.opensearch_domains.append(
OpenSearchDomain(
name=domain_name,
region=AWS_REGION,
arn=domain_arn,
access_policy=policy_data_source_whole_internet,
)
)
opensearch_client.opensearch_domains[0].logging = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_not_publicly_accessible.opensearch_service_domains_not_publicly_accessible import (
opensearch_service_domains_not_publicly_accessible,
)
check = opensearch_service_domains_not_publicly_accessible()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Opensearch domain {domain_name} policy allows access (Principal: '*') and network 0.0.0.0/0"
)
assert result[0].resource_id == domain_name
assert result[0].resource_arn == domain_arn

View File

@@ -0,0 +1,35 @@
{
"Provider": "aws",
"CheckID": "opensearch_service_domains_updated_to_the_latest_service_software_version",
"CheckTitle": "Check if Amazon Elasticsearch/Opensearch Service domains have updates available",
"CheckType": ["Detect", "Vulnerability, patch, and version management"],
"ServiceName": "opensearch",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "low",
"ResourceType": "AwsOpenSearchDomain",
"Description": "Check if Amazon Elasticsearch/Opensearch Service domains have updates available",
"Risk": "Amazon ES regularly releases system software updates that add features or otherwise improve your domains.",
"RelatedUrl": "",
"Remediation": {
"Code": {
"CLI": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/Elasticsearch/version.html",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/Elasticsearch/version.html",
"Terraform": ""
},
"Recommendation": {
"Text": "The Notifications panel in the console is the easiest way to see if an update is available or check the status of an update. You can also receive these notifications through Amazon EventBridge. If you take no action on required updates; Amazon ES still updates your domain service software automatically after a certain timeframe (typically two weeks). In this situation; Amazon ES sends notifications when it starts the update and when the update is complete.",
"Url": "https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-service-software.html"
}
},
"Categories": [],
"Tags": {
"Tag1Key": "value",
"Tag2Key": "value"
},
"DependsOn": [],
"RelatedTo": [],
"Notes": "",
"Compliance": []
}

View File

@@ -0,0 +1,21 @@
from lib.check.models import Check, Check_Report
from providers.aws.services.opensearch.opensearch_client import opensearch_client
class opensearch_service_domains_updated_to_the_latest_service_software_version(Check):
def execute(self):
findings = []
for domain in opensearch_client.opensearch_domains:
report = Check_Report(self.metadata)
report.region = domain.region
report.resource_id = domain.name
report.resource_arn = domain.arn
report.status = "PASS"
report.status_extended = f"Opensearch domain {domain.name} with version {domain.version} does not have internal updates available"
if domain.update_available:
report.status = "FAIL"
report.status_extended = f"Opensearch domain {domain.name} with version {domain.version} has internal updates available"
findings.append(report)
return findings

View File

@@ -0,0 +1,93 @@
from re import search
from unittest import mock
from providers.aws.services.opensearch.opensearch_service import OpenSearchDomain
AWS_REGION = "eu-west-1"
AWS_ACCOUNT_NUMBER = "123456789012"
domain_name = "test-domain"
domain_arn = f"arn:aws:es:us-west-2:{AWS_ACCOUNT_NUMBER}:domain/{domain_name}"
class Test_opensearch_service_domains_updated_to_the_latest_service_software_version:
def test_no_domains(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_updated_to_the_latest_service_software_version.opensearch_service_domains_updated_to_the_latest_service_software_version import (
opensearch_service_domains_updated_to_the_latest_service_software_version,
)
check = (
opensearch_service_domains_updated_to_the_latest_service_software_version()
)
result = check.execute()
assert len(result) == 0
def test_internal_update_available(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
opensearch_client.opensearch_domains.append(
OpenSearchDomain(
name=domain_name,
region=AWS_REGION,
arn=domain_arn,
update_available=False,
)
)
opensearch_client.opensearch_domains[0].logging = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_updated_to_the_latest_service_software_version.opensearch_service_domains_updated_to_the_latest_service_software_version import (
opensearch_service_domains_updated_to_the_latest_service_software_version,
)
check = (
opensearch_service_domains_updated_to_the_latest_service_software_version()
)
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert search(
"does not have internal updates available", result[0].status_extended
)
assert result[0].resource_id == domain_name
assert result[0].resource_arn == domain_arn
def test_internal_database_enabled(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
opensearch_client.opensearch_domains.append(
OpenSearchDomain(
name=domain_name,
region=AWS_REGION,
arn=domain_arn,
update_available=True,
)
)
opensearch_client.opensearch_domains[0].logging = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_updated_to_the_latest_service_software_version.opensearch_service_domains_updated_to_the_latest_service_software_version import (
opensearch_service_domains_updated_to_the_latest_service_software_version,
)
check = (
opensearch_service_domains_updated_to_the_latest_service_software_version()
)
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert search("has internal updates available", result[0].status_extended)
assert result[0].resource_id == domain_name
assert result[0].resource_arn == domain_arn

View File

@@ -0,0 +1,35 @@
{
"Provider": "aws",
"CheckID": "opensearch_service_domains_use_cognito_authentication_for_kibana",
"CheckTitle": "Check if Amazon Elasticsearch/Opensearch Service domains has Amazon Cognito authentication for Kibana enabled",
"CheckType": ["Identify", "Logging"],
"ServiceName": "opensearch",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "high",
"ResourceType": "AwsOpenSearchDomain",
"Description": "Check if Amazon Elasticsearch/Opensearch Service domains has Amazon Cognito authentication for Kibana enabled",
"Risk": "Amazon Elasticsearch Service supports Amazon Cognito for Kibana authentication.",
"RelatedUrl": "",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "If you do not configure Amazon Cognito authentication; you can still protect Kibana using an IP-based access policy and a proxy server; HTTP basic authentication; or SAML.",
"Url": "https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-ac.html"
}
},
"Categories": [],
"Tags": {
"Tag1Key": "value",
"Tag2Key": "value"
},
"DependsOn": [],
"RelatedTo": [],
"Notes": "",
"Compliance": []
}

View File

@@ -0,0 +1,21 @@
from lib.check.models import Check, Check_Report
from providers.aws.services.opensearch.opensearch_client import opensearch_client
class opensearch_service_domains_use_cognito_authentication_for_kibana(Check):
def execute(self):
findings = []
for domain in opensearch_client.opensearch_domains:
report = Check_Report(self.metadata)
report.region = domain.region
report.resource_id = domain.name
report.resource_arn = domain.arn
report.status = "PASS"
report.status_extended = f"Opensearch domain {domain.name} has Amazon Cognito authentication for Kibana enabled"
if not domain.cognito_options:
report.status = "FAIL"
report.status_extended = f"Opensearch domain {domain.name} does not have Amazon Cognito authentication for Kibana enabled"
findings.append(report)
return findings

View File

@@ -0,0 +1,91 @@
from re import search
from unittest import mock
from providers.aws.services.opensearch.opensearch_service import OpenSearchDomain
AWS_REGION = "eu-west-1"
AWS_ACCOUNT_NUMBER = "123456789012"
domain_name = "test-domain"
domain_arn = f"arn:aws:es:us-west-2:{AWS_ACCOUNT_NUMBER}:domain/{domain_name}"
class Test_opensearch_service_domains_use_cognito_authentication_for_kibana:
def test_no_domains(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_use_cognito_authentication_for_kibana.opensearch_service_domains_use_cognito_authentication_for_kibana import (
opensearch_service_domains_use_cognito_authentication_for_kibana,
)
check = opensearch_service_domains_use_cognito_authentication_for_kibana()
result = check.execute()
assert len(result) == 0
def test_no_cognito_enabled(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
opensearch_client.opensearch_domains.append(
OpenSearchDomain(
name=domain_name,
region=AWS_REGION,
arn=domain_arn,
cognito_options=False,
)
)
opensearch_client.opensearch_domains[0].logging = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_use_cognito_authentication_for_kibana.opensearch_service_domains_use_cognito_authentication_for_kibana import (
opensearch_service_domains_use_cognito_authentication_for_kibana,
)
check = opensearch_service_domains_use_cognito_authentication_for_kibana()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert search(
"does not have Amazon Cognito authentication for Kibana enabled",
result[0].status_extended,
)
assert result[0].resource_id == domain_name
assert result[0].resource_arn == domain_arn
def test_cognito_enabled(self):
opensearch_client = mock.MagicMock
opensearch_client.opensearch_domains = []
opensearch_client.opensearch_domains.append(
OpenSearchDomain(
name=domain_name,
region=AWS_REGION,
arn=domain_arn,
cognito_options=True,
)
)
opensearch_client.opensearch_domains[0].logging = []
with mock.patch(
"providers.aws.services.opensearch.opensearch_service.OpenSearchService",
opensearch_client,
):
from providers.aws.services.opensearch.opensearch_service_domains_use_cognito_authentication_for_kibana.opensearch_service_domains_use_cognito_authentication_for_kibana import (
opensearch_service_domains_use_cognito_authentication_for_kibana,
)
check = opensearch_service_domains_use_cognito_authentication_for_kibana()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert search(
"has Amazon Cognito authentication for Kibana enabled",
result[0].status_extended,
)
assert result[0].resource_id == domain_name
assert result[0].resource_arn == domain_arn

View File

@@ -0,0 +1,181 @@
from json import dumps
from unittest.mock import patch
import botocore
from boto3 import session
from providers.aws.lib.audit_info.models import AWS_Audit_Info
from providers.aws.services.opensearch.opensearch_service import OpenSearchService
AWS_ACCOUNT_NUMBER = 123456789012
AWS_REGION = "eu-west-1"
test_domain_name = "test"
domain_arn = f"arn:aws:es:us-west-2:{AWS_ACCOUNT_NUMBER}:domain/{test_domain_name}"
policy_data = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"AWS": ["*"]},
"Action": ["es:*"],
"Resource": f"arn:aws:es:us-west-2:{AWS_ACCOUNT_NUMBER}:domain/{test_domain_name}/*",
}
],
}
policy_json = dumps(policy_data)
make_api_call = botocore.client.BaseClient._make_api_call
def mock_make_api_call(self, operation_name, kwarg):
if operation_name == "ListDomainNames":
return {
"DomainNames": [
{
"DomainName": test_domain_name,
},
]
}
if operation_name == "DescribeDomainConfig":
return {
"DomainConfig": {
"AccessPolicies": {
"Options": policy_json,
},
"LogPublishingOptions": {
"Options": {
"SEARCH_SLOW_LOGS": {"Enabled": True},
"INDEX_SLOW_LOGS": {"Enabled": True},
"AUDIT_LOGS": {"Enabled": True},
},
},
}
}
if operation_name == "DescribeDomain":
return {
"DomainStatus": {
"ARN": domain_arn,
"Endpoints": {
"vpc": "vpc-endpoint-h2dsd34efgyghrtguk5gt6j2foh4.us-east-1.es.amazonaws.com"
},
"EngineVersion": "opensearch-version1",
"VPCOptions": {
"VPCId": "test-vpc-id",
},
"CognitoOptions": {"Enabled": True},
"EncryptionAtRestOptions": {"Enabled": True},
"NodeToNodeEncryptionOptions": {"Enabled": True},
"AdvancedOptions": {"string": "string"},
"LogPublishingOptions": {
"string": {
"CloudWatchLogsLogGroupArn": "string",
"Enabled": True | False,
}
},
"ServiceSoftwareOptions": {"UpdateAvailable": True},
"DomainEndpointOptions": {"EnforceHTTPS": True},
"AdvancedSecurityOptions": {"InternalUserDatabaseEnabled": True},
}
}
return make_api_call(self, operation_name, kwarg)
def mock_generate_regional_clients(service, audit_info):
regional_client = audit_info.audit_session.client(service, region_name=AWS_REGION)
regional_client.region = AWS_REGION
return {AWS_REGION: regional_client}
@patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call)
@patch(
"providers.aws.services.opensearch.opensearch_service.generate_regional_clients",
new=mock_generate_regional_clients,
)
class Test_OpenSearchService_Service:
# Mocked Audit Info
def set_mocked_audit_info(self):
audit_info = AWS_Audit_Info(
original_session=None,
audit_session=session.Session(
profile_name=None,
botocore_session=None,
),
audited_account=AWS_ACCOUNT_NUMBER,
audited_user_id=None,
audited_partition="aws",
audited_identity_arn=None,
profile=None,
profile_region=None,
credentials=None,
assumed_role_info=None,
audited_regions=None,
organizations_metadata=None,
)
return audit_info
# Test OpenSearchService Service
def test_service(self):
audit_info = self.set_mocked_audit_info()
opensearch = OpenSearchService(audit_info)
assert opensearch.service == "opensearch"
# Test OpenSearchService_ client
def test_client(self):
audit_info = self.set_mocked_audit_info()
opensearch = OpenSearchService(audit_info)
for reg_client in opensearch.regional_clients.values():
assert reg_client.__class__.__name__ == "OpenSearchService"
# Test OpenSearchService session
def test__get_session__(self):
audit_info = self.set_mocked_audit_info()
opensearch = OpenSearchService(audit_info)
assert opensearch.session.__class__.__name__ == "Session"
# Test OpenSearchService list domains names
def test__list_domain_names__(self):
audit_info = self.set_mocked_audit_info()
opensearch = OpenSearchService(audit_info)
assert len(opensearch.opensearch_domains) == 1
assert opensearch.opensearch_domains[0].name == test_domain_name
assert opensearch.opensearch_domains[0].region == AWS_REGION
# Test OpenSearchService describ domain config
def test__describe_domain_config__(self):
audit_info = self.set_mocked_audit_info()
opensearch = OpenSearchService(audit_info)
assert len(opensearch.opensearch_domains) == 1
assert opensearch.opensearch_domains[0].name == test_domain_name
assert opensearch.opensearch_domains[0].region == AWS_REGION
assert opensearch.opensearch_domains[0].access_policy
assert opensearch.opensearch_domains[0].logging[0].name == "SEARCH_SLOW_LOGS"
assert opensearch.opensearch_domains[0].logging[0].enabled
assert opensearch.opensearch_domains[0].logging[1].name == "INDEX_SLOW_LOGS"
assert opensearch.opensearch_domains[0].logging[1].enabled
assert opensearch.opensearch_domains[0].logging[2].name == "AUDIT_LOGS"
assert opensearch.opensearch_domains[0].logging[2].enabled
# Test OpenSearchService describ domain
def test__describe_domain__(self):
audit_info = self.set_mocked_audit_info()
opensearch = OpenSearchService(audit_info)
assert len(opensearch.opensearch_domains) == 1
assert opensearch.opensearch_domains[0].name == test_domain_name
assert opensearch.opensearch_domains[0].region == AWS_REGION
assert opensearch.opensearch_domains[0].arn == domain_arn
assert opensearch.opensearch_domains[0].access_policy
assert (
opensearch.opensearch_domains[0].endpoint_vpc
== "vpc-endpoint-h2dsd34efgyghrtguk5gt6j2foh4.us-east-1.es.amazonaws.com"
)
assert opensearch.opensearch_domains[0].vpc_id == "test-vpc-id"
assert opensearch.opensearch_domains[0].cognito_options
assert opensearch.opensearch_domains[0].encryption_at_rest
assert opensearch.opensearch_domains[0].node_to_node_encryption
assert opensearch.opensearch_domains[0].enforce_https
assert opensearch.opensearch_domains[0].internal_user_database
assert opensearch.opensearch_domains[0].update_available
assert opensearch.opensearch_domains[0].version == "opensearch-version1"