#!/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_extra73="7.3,7.03"
CHECK_TITLE_extra73="[extra73] Ensure there are no S3 buckets open to the Everyone or Any AWS user (Not Scored) (Not part of CIS benchmark)"
CHECK_SCORED_extra73="NOT_SCORED"
CHECK_TYPE_extra73="EXTRA"
CHECK_ALTERNATE_extra703="extra73"
CHECK_ALTERNATE_check73="extra73"
CHECK_ALTERNATE_check703="extra73"

# Verified with AWS support that if get-bucket-acl doesn't return a grant
# for All and get-bucket-policy-status returns IsPublic false or bad request
# (no policy) then the bucket can be considered not public - though
# individual objects may still be. If in addition put-public-access-block is
# used to set IgnorePublicAcls and RestrictPublicBuckets to true then that
# causes Amazon S3 to ignore all public ACLs on a bucket and any objects that
# it contains.
#
# This check does not address legacy ACLs or policies that would give
# public access if not blocked at account or bucket level, instead it tries
# to reward the use of more broadly restrictive controls with quicker and less
# computational intensive checks.
#
# If we are assembling an inventory then maybe that is not what we want but
# for day to day usage that is probably desirable.

extra73(){
  textInfo "Looking for open S3 Buckets (ACLs and Policies) in all regions...  "

  #
  # If public ACLs disabled at account level then look no further
  #
  ACCOUNT_PUBLIC_ACCESS_BLOCK=$($AWSCLI s3control get-public-access-block $PROFILE_OPT --region $REGION --account-id $ACCOUNT_NUM --output json 2>&1)
  if [[ $(echo "$ACCOUNT_PUBLIC_ACCESS_BLOCK" | grep AccessDenied) ]]; then
    textFail "Access Denied Trying to Get Public Access Block for $bucket"
    return
  fi
  if [[ $(echo "$ACCOUNT_PUBLIC_ACCESS_BLOCK" | grep NoSuchPublicAccessBlockConfiguration) ]]; then
    ACCOUNTIGNOREPUBLICACLS=""
    ACCOUNTRESTRICTPUBLICBUCKETS=""
  else
    ACCOUNTIGNOREPUBLICACLS=$(echo "$ACCOUNT_PUBLIC_ACCESS_BLOCK" | jq -r '.PublicAccessBlockConfiguration.IgnorePublicAcls')
    ACCOUNTRESTRICTPUBLICBUCKETS=$(echo "$ACCOUNT_PUBLIC_ACCESS_BLOCK" | jq -r '.PublicAccessBlockConfiguration.RestrictPublicBuckets')
  fi
  if [[ $ACCOUNTIGNOREPUBLICACLS == "true" && $ACCOUNTRESTRICTPUBLICBUCKETS == "true" ]]; then
    textPass "All S3 public access blocked at account level"
    return
  fi

  #
  # Otherwise start to iterate bucket
  #
  ALL_BUCKETS_LIST=$($AWSCLI s3api list-buckets --query 'Buckets[*].{Name:Name}' $PROFILE_OPT --output text 2>&1)
  if [[ $(echo "$ALL_BUCKETS_LIST" | grep AccessDenied) ]]; then
    textFail "Access Denied Trying to List Buckets"
    return
  fi
  if [[ "$ALL_BUCKETS_LIST" == "" ]]; then
    textInfo "No buckets found"
    return
  fi

  for bucket in $ALL_BUCKETS_LIST; do

    #
    # LOCATION - requests referencing buckets created after March 20, 2019
    # must be made to S3 endpoints in the same region as the bucket was
    # created.
    #
    BUCKET_LOCATION=$($AWSCLI s3api get-bucket-location --bucket $bucket $PROFILE_OPT --output text 2>&1)
    if [[ $(echo "$BUCKET_LOCATION" | grep AccessDenied) ]]; then
      textFail "Access Denied Trying to Get Bucket Location for $bucket"
      continue
    fi
    if [[ "None" == $BUCKET_LOCATION ]]; then
      BUCKET_LOCATION="us-east-1"
    fi
    if [[ "EU" == $BUCKET_LOCATION ]]; then
      BUCKET_LOCATION="eu-west-1"
    fi

    #
    # If public ACLs disabled at bucket level then look no further
    #
    BUCKET_PUBLIC_ACCESS_BLOCK=$($AWSCLI s3api get-public-access-block $PROFILE_OPT --region $BUCKET_LOCATION --bucket $bucket --output json 2>&1)
    if [[ $(echo "$BUCKET_PUBLIC_ACCESS_BLOCK" | grep AccessDenied) ]]; then
      textFail "Access Denied Trying to Get Public Access Block for $bucket"
      continue
    fi
    if [[ $(echo "$BUCKET_PUBLIC_ACCESS_BLOCK" | grep NoSuchPublicAccessBlockConfiguration) ]]; then
      BUCKETIGNOREPUBLICACLS=""
      BUCKETRESTRICTPUBLICBUCKETS=""
    else
      BUCKETIGNOREPUBLICACLS=$(echo "$BUCKET_PUBLIC_ACCESS_BLOCK" | jq -r '.PublicAccessBlockConfiguration.IgnorePublicAcls')
      BUCKETRESTRICTPUBLICBUCKETS=$(echo "$BUCKET_PUBLIC_ACCESS_BLOCK" | jq -r '.PublicAccessBlockConfiguration.RestrictPublicBuckets')
    fi
    if [[ $BUCKETIGNOREPUBLICACLS == "true" && $BUCKETRESTRICTPUBLICBUCKETS == "true" ]]; then
      textPass "$BUCKET_LOCATION: $bucket bucket is not Public" "$BUCKET_LOCATION"
      continue
    fi

    #
    # Check for public ACL grants
    #
    BUCKET_ACL=$($AWSCLI s3api get-bucket-acl $PROFILE_OPT --region $BUCKET_LOCATION --bucket $bucket --output json 2>&1)
    if [[ $(echo "$BUCKET_ACL" | grep AccessDenied) ]]; then
      textFail "Access Denied Trying to Get Bucket Acl for $bucket"
      continue
    fi

    ALLUSERS_ACL=$(echo "$BUCKET_ACL" | jq '.Grants[]|select(.Grantee.URI != null)|select(.Grantee.URI | endswith("/AllUsers"))')
    if [[ $ALLUSERS_ACL != "" ]]; then
      textFail "$BUCKET_LOCATION: $bucket bucket is Public!" "$BUCKET_LOCATION"
      continue
    fi

    AUTHENTICATEDUSERS_ACL=$(echo "$BUCKET_ACL" | jq '.Grants[]|select(.Grantee.URI != null)|select(.Grantee.URI | endswith("/AuthenticatedUsers"))')
    if [[ $AUTHENTICATEDUSERS_ACL != "" ]]; then
      textFail "$BUCKET_LOCATION: $bucket bucket is Public!" "$BUCKET_LOCATION"
      continue
    fi

    #
    # Check for public access in policy
    #
    BUCKET_POLICY_STATUS=$($AWSCLI s3api get-bucket-policy-status $PROFILE_OPT --region $BUCKET_LOCATION --bucket $bucket --query PolicyStatus.IsPublic --output text 2>&1)
    if [[ $(echo "$BUCKET_POLICY_STATUS" | grep AccessDenied) ]]; then
      textFail "Access Denied Trying to Get Bucket Policy Status for $bucket"
      continue
    fi
    if [[ $(echo "$BUCKET_POLICY_STATUS" | grep NoSuchBucketPolicy) ]]; then
      BUCKET_POLICY_STATUS="False"
    fi

    if [[ $BUCKET_POLICY_STATUS != "" && $BUCKET_POLICY_STATUS != "False" ]]; then
      textFail "$BUCKET_LOCATION: $bucket bucket is Public!" "$BUCKET_LOCATION"
      continue
    fi

    textPass "$BUCKET_LOCATION: $bucket bucket is not Public" "$BUCKET_LOCATION"

  done
}
