mirror of
https://github.com/ghndrx/prowler.git
synced 2026-02-10 06:45:08 +00:00
feat(): opensearch service and checks (#1487)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
0
providers/aws/services/opensearch/__init__.py
Normal file
0
providers/aws/services/opensearch/__init__.py
Normal file
4
providers/aws/services/opensearch/opensearch_client.py
Normal file
4
providers/aws/services/opensearch/opensearch_client.py
Normal 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)
|
||||
141
providers/aws/services/opensearch/opensearch_service.py
Normal file
141
providers/aws/services/opensearch/opensearch_service.py
Normal 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
|
||||
@@ -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": []
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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": []
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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": []
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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": []
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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": []
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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": []
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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": []
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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": []
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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": []
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
181
providers/aws/services/opensearch/opensearch_service_test.py
Normal file
181
providers/aws/services/opensearch/opensearch_service_test.py
Normal 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"
|
||||
Reference in New Issue
Block a user