Script File To Validate Network Setup

You must create a script file to validate if the existing WebLogic Server subnet and the database subnets meet the prerequisites to provision the WebLogic instance in Oracle WebLogic Server for OKE. You can copy the following scripts in Cloud Shell to perform the validation. For example, copy the scripts and save the file as validateoke.sh.

# Script to validate existing public, private and database subnets meet the prerequisites
# for provisioning and proper functioning of Oracle WebLogic Server for OKE.
#
version="1.0.0"

# Set Flags
# -----------------------------------
# Flags which can be overridden by user input.
# Default values are below
# -----------------------------------
DB_PORT=1521
SSH_PORT=22
BASTION_SUBNET_OCID=""
ADMIN_SUBNET_OCID=""
WORKER_SUBNET_OCID=""
FSS_SUBNET_OCID=""
LB_SUBNET_OCID=""

DB_SUBNET_OCID=""
BASTION_HOST_IP_CIDR=""

debug=false
args=()

function ip_to_int() {
  local ip_addr="${1}"
  local ip_1 ip_2 ip_3 ip_4

  ip_1=$(echo "${ip_addr}" | cut -d'.' -f1)
  ip_2=$(echo "${ip_addr}" | cut -d'.' -f2)
  ip_3=$(echo "${ip_addr}" | cut -d'.' -f3)
  ip_4=$(echo "${ip_addr}" | cut -d'.' -f4)

  echo $(( ip_1 * 256**3 + ip_2 * 256**2 + ip_3 * 256 + ip_4 ))
}

####################################################
# Determine whether IP address is in the specified subnet.
#
# Args:
#   cidr_subnet: Subnet, in CIDR notation.
#   ip_addr: IP address to check.
#
# Returns:
#   0|1
####################################################
function in_cidr_range() {
  local cidr_subnet="${1}"
  local ip_addr="${2}"
  local subnet_ip cidr_mask netmask ip_addr_subnet subnet rval

  subnet_ip=$(echo "${cidr_subnet}" | cut -d'/' -f1)
  cidr_mask=$(echo "${cidr_subnet}" | cut -d'/' -f2)

  netmask=$(( 0xFFFFFFFF << $(( 32 - ${cidr_mask} )) ))

  # Apply netmask to both the subnet IP and the given IP address
  ip_addr_subnet=$(( netmask & $(ip_to_int ${ip_addr}) ))
  subnet=$(( netmask & $(ip_to_int ${subnet_ip}) ))

  # Subnet IPs will match if given IP address is in CIDR subnet
  [ "${ip_addr_subnet}" == "${subnet}" ] && rval=0 || rval=1

  return $rval
}

####################################################
# Validates if one of service or nat gateways exist in the specified private subnet.
#
# Returns:
#   0|1
####################################################
function validate_service_or_nat_gw_exist() {
  local subnet_ocid=$1
  local vcn_ocid=""
  local vcn_compartment_ocid=""
  is_private_subnet=$(oci network subnet get --subnet-id "${subnet_ocid}" | jq -r '.data["prohibit-public-ip-on-vnic"]')

  if [[ $is_private_subnet = true ]]
  then
    vcn_ocid=$(oci network subnet get --subnet-id "${subnet_ocid}" | jq -r '.data["vcn-id"]')
    vcn_compartment_ocid=$(oci network vcn get --vcn-id "${vcn_ocid}" | jq -r '.data["compartment-id"]')
    # Check if NAT gateway exists in the VCN
    res=$(oci network nat-gateway list --compartment-id ${vcn_compartment_ocid} --vcn-id ${vcn_ocid})
    nat_gw_found=$(if [[ -n $res ]]; then echo 0; else echo 1; fi)

    # Check if Service gateway exists in the VCN
    res=$(oci network service-gateway list --compartment-id ${vcn_compartment_ocid} --vcn-id ${vcn_ocid})
    svc_gw_found=$(if [[ -n $res ]]; then echo 0; else echo 1; fi)

    # One of NAT or Service Gateway must exist
    if [[ $nat_gw_found -ne 0 ]] && [[ $svc_gw_found -ne 0 ]]
    then
      echo 1
      return
    fi

    # Admin subnet should be using either NAT or service gateway or both in its routetable
    rt_ocid=$(oci network subnet get --subnet-id ${subnet_ocid} | jq -r '.data["route-table-id"]')
    rt_rules=$(oci network route-table get --rt-id ${rt_ocid} | jq -r '.data["route-rules"]')
    rt_rules_count=$(echo $rt_rules | jq '.|length')

    nat=""
    svc=""
    nat_gw_id=""
    svc_gw_id=""

    for ((i = 0 ; i < $rt_rules_count ; i++))
    do
      network_entity_ocid=$(echo $rt_rules | jq -r --arg i "$i" '.[$i|tonumber]["network-entity-id"]')
      nat_id=$(echo $network_entity_ocid | grep natgateway)
      if [[ -n $nat_id ]]; then nat_gw_id=$nat_id; fi

      svc_id=$(echo $network_entity_ocid | grep servicegateway)
      if [[ -n $svc_id ]]; then svc_gw_id=$svc_id; fi
    done

    if [[ (-z $nat_gw_id  && -z $svc_gw_id) ]]; then
      echo 2
      return
    fi

    # If WLS subnet route table has a rule to use service gateway then it should be using
    # all-<region-code>-services-in-oracle-services-network destination
    echo ""
    if [[ -n $svc_gw_id ]]
    then
      is_all_services_name=$(oci network service-gateway get --service-gateway-id $svc_gw_id | jq -r '.data.services[0]["service-name"]' | grep -i "all.*services in oracle services network")
      if [[ -z $is_all_services_name ]]
      then
        echo 3
        return
      fi
      for ((i = 0 ; i < $rt_rules_count ; i++))
      do
        network_entity_ocid=$(echo $rt_rules | jq -r --arg i "$i" '.[$i|tonumber]["network-entity-id"]')
        res=$(echo $network_entity_ocid | grep servicegateway)
        if [[ -n $res ]]
        then
          all_services_destination=$(echo $rt_rules | jq -r --arg i "$i" '.[$i|tonumber].destination'  | grep -i "all-.*-services-in-oracle-services-network")
          if [[ -z $all_services_destination ]]
          then
            echo 4
            return
          fi
        fi
      done
    fi
  fi
  echo 0
}

####################################################
# Validates if the internet gateway exists in the VCN of Admin subnet.
# Without Internet gateway in Admin Subnet VCN, SSH access from ORM will not work.
# When using terraform CLI from within private network, internet gateway is not required.
# Hence this check will give a warning and not an error.
#
# Returns:
#   0|1
####################################################
function validate_internet_gw_exist() {
  local subnet_ocid=$1
  local vcn_ocid=""
  local vcn_compartment_ocid=""

  vcn_ocid=$(oci network subnet get --subnet-id ${subnet_ocid} | jq -r '.data["vcn-id"]')
  vcn_compartment_ocid=$(oci network vcn get --vcn-id ${vcn_ocid} | jq -r '.data["compartment-id"]')
  # Check if Service gateway exists in the VCN
  res=$(oci network internet-gateway list --compartment-id ${vcn_compartment_ocid} --vcn-id ${vcn_ocid})
  if [[ -n $res ]]; then
    echo 0
  else
    echo 1
  fi
}

####################################################
# Checks if specified port is open to specified source CIDR in the specified seclist's ingress rules.
#
# Args:
#     seclist_ocid: Security list OCID for the security list to check ingress rules for.
#     port: destination port to check
#     source: Source CIDR (either block/range of IPs or single IP (with /32 suffix)
#
# Returns:
#   0|1
####################################################
function check_tcp_port_open_in_seclist() {
  local seclist_ocid=$1
  local port=$2
  local source=$3
  local port_is_open=false
  local tcp_protocol="6"

  ingress_rules=$(oci network security-list get --security-list-id $seclist_ocid | jq -r '.data["ingress-security-rules"]')
  ingress_rules_count=$(echo $ingress_rules | jq '.|length')

  for ((i = 0 ; i < $ingress_rules_count ; i++))
  do
    ingress_protocol=$(echo $ingress_rules | jq -r --arg i "$i" '.[$i|tonumber].protocol')
    ingress_source=$(echo $ingress_rules | jq -r --arg i "$i" '.[$i|tonumber].source')
    tcp_options=$(echo $ingress_rules | jq -r --arg i "$i" '.[$i|tonumber]["tcp-options"]')
    port_min=$(echo $ingress_rules | jq -r --arg i "$i" '.[$i|tonumber]["tcp-options"]["destination-port-range"].min')
    port_max=$(echo $ingress_rules | jq -r --arg i "$i" '.[$i|tonumber]["tcp-options"]["destination-port-range"].max')

    source_in_cidr_range=1
    if [[ $source = "0.0.0.0/0" ]]
    then
      if [[ $ingress_source = $source ]]
      then
        source_in_cidr_range=0
      else
        source_in_cidr_range=1
      fi
    else
      source_in_cidr_range=$(in_cidr_range $ingress_source $source ; echo $?)
    fi

    if [[ ($ingress_protocol = "all" || $ingress_protocol = $tcp_protocol ) && ( $tcp_options = "null" || ( $port -ge $port_min && $port -le $port_max ) ) && $source_in_cidr_range -eq 0 ]]
    then
       port_is_open=true
       echo 0
       return
    fi
  done
  echo 1
}

####################################################
# Validates if the specified TCP port is open for the WLS subnet CIDR.
#
# Args:
#     port:         Destination port
#     source_cidr:  Source CIDR
#
# Returns:
#   0|1
####################################################
function validate_subnet_port_access() {
  local port_found_open=1
  local subnet=$1
  local port=$2
  local source_cidr=$3
  local protocol=$4 # Default protocol is TCP, if it is UDP then need to pass this param
  sec_lists=$(oci network subnet get --subnet-id ${subnet} | jq -c '.data["security-list-ids"]')
  # Convert to bash array
  declare -A seclists_array

  while IFS="=" read -r key value
  do
      seclists_array[$key]="$value"
  done < <(jq -r 'to_entries|map("\(.key)=\(.value|tostring)")|.[]' <<< "$sec_lists")
  # Check the ingress rules for specified destination port is open for access by source CIDR
  for seclist_ocid in "${seclists_array[@]}"
  do
    if [[ $port_found_open -ne 0 ]]; then
      if [[ -z $protocol ]]; then # default is TCP
        port_found_open=$(check_tcp_port_open_in_seclist $seclist_ocid "${port}" "$source_cidr")
      else # protocol param is non empty then udp
        port_found_open=$(check_udp_port_open_in_seclist $seclist_ocid "${port}" "$source_cidr")
      fi
    fi
  done
  echo $port_found_open
}

####################################################
# Checks if specified UDP port is open to specified source CIDR in the specified seclist's ingress rules.
#
# Args:
#     seclist_ocid: Security list OCID for the security list to check ingress rules for.
#     port: destination port to check
#     source: Source CIDR (either block/range of IPs or single IP (with /32 suffix)
#
# Returns:
#   0|1
####################################################
function check_udp_port_open_in_seclist() {
  local seclist_ocid=$1
  local port=$2
  local source=$3
  local port_is_open=false
  local udp_protocol="17"

  ingress_rules=$(oci network security-list get --security-list-id $seclist_ocid | jq -r '.data["ingress-security-rules"]')
  ingress_rules_count=$(echo $ingress_rules | jq '.|length')

  for ((i = 0 ; i < $ingress_rules_count ; i++))
  do
    ingress_protocol=$(echo $ingress_rules | jq -r --arg i "$i" '.[$i|tonumber].protocol')
    ingress_source=$(echo $ingress_rules | jq -r --arg i "$i" '.[$i|tonumber].source')
    udp_options=$(echo $ingress_rules | jq -r --arg i "$i" '.[$i|tonumber]["udp-options"]')
    port_min=$(echo $ingress_rules | jq -r --arg i "$i" '.[$i|tonumber]["udp-options"]["destination-port-range"].min')
    port_max=$(echo $ingress_rules | jq -r --arg i "$i" '.[$i|tonumber]["udp-options"]["destination-port-range"].max')

    source_in_cidr_range=1
    if [[ $source = "0.0.0.0/0" ]]
    then
      if [[ $ingress_source = $source ]]
      then
        source_in_cidr_range=0
      else
        source_in_cidr_range=1
      fi
    else
      source_in_cidr_range=$(in_cidr_range $ingress_source $source ; echo $?)
    fi

    if [[ ($ingress_protocol = "all" || $ingress_protocol = $udp_protocol ) && ( $udp_options = "null" || ( $port -ge $port_min && $port -le $port_max ) ) && $source_in_cidr_range -eq 0 ]]
    then
       port_is_open=true
       echo 0
       return
    fi
  done
  echo 1
}


####################################################
# Validates if CIDR is a valid single host IP (must end with /32 suffix).
#
# Args:
#     ip_cidr: Single host IPv4 Address in CIDR format
#
# Returns:
#   0|1
####################################################
function is_valid_ip_cidr() {
  local ip_cidr=$1

  is_valid=$(echo ${ip_cidr} | grep -E '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(32))$')
  if [[ -n $is_valid ]]; then
    echo 0
  else
    echo 1
  fi
}

############## Begin Options and Usage ###################

# Print usage
usage() {
  echo -n "$0 [OPTIONS]...

 This script is used to validate existing subnets for OKE - Bastion, Admin, Worker, FSS, LB subnets (and optionally database subnets) are setup correctly.
 ${bold}Options:${reset}
  -b, --bastionsubnet Bastion Subnet OCID (Required)
  -a, --adminsubnet   Admin Subnet OCID (Required)
  -w  --workersubnet  Workers Subnet OCID (Required)
  -f  --fsssubnet     FSS Subnet OCID (Required)
  -l  --lbsubnet      LB Subnet OCID (Required)
  -d, --dbsubnet      DB Subnet OCID
  -i, --bastionipcidr Bastion Host IP CIDR (should be suffixed with /32)
      --debug         Runs script in BASH debug mode (set -x)
  -h, --help          Display this help and exit
      --version       Output version information and exit
  "
}

# Iterate over options breaking -ab into -a -b when needed and --foo=bar into
# --foo bar
optstring=h
unset options
while (($#)); do
  case $1 in
    # If option is of type -ab
    -[!-]?*)
      # Loop over each character starting with the second
      for ((i=1; i < ${#1}; i++)); do
        c=${1:i:1}

        # Add current char to options
        options+=("-$c")

        # If option takes a required argument, and it's not the last char make
        # the rest of the string its argument
        if [[ $optstring = *"$c:"* && ${1:i+1} ]]; then
          options+=("${1:i+1}")
          break
        fi
      done
      ;;

    # If option is of type --foo=bar
    --?*=*) options+=("${1%%=*}" "${1#*=}") ;;
    # add --endopts for --
    --) options+=(--endopts) ;;
    # Otherwise, nothing special
    *) options+=("$1") ;;
  esac
  shift
done
set -- "${options[@]}"
unset options

# Print help if no arguments were passed.
[[ $# -eq 0 ]] && set -- "--help"

# Read the options and set stuff
while [[ $1 = -?* ]]; do
  case $1 in
    -h|--help) usage >&2; exit 0 ;;
    --version) echo "$(basename $0) ${version}"; exit 0 ;;
    -b|--bastionsubnet) shift; BASTION_SUBNET_OCID=${1} ;;
    -a|--adminsubnet) shift; ADMIN_SUBNET_OCID=${1} ;;
    -w|--workersubnet) shift; WORKER_SUBNET_OCID=${1} ;;
    -f|--fsssubnet) shift; FSS_SUBNET_OCID=${1} ;;
    -l|--lbsubnet) shift; LB_SUBNET_OCID=${1} ;;
    -d|--dbsubnet) shift; DB_SUBNET_OCID=${1} ;;
    -i|--bastionipcidr) shift; BASTION_HOST_IP_CIDR=${1} ;;
    --debug) debug=true;;
    --endopts) shift; break ;;
    *) "invalid option: '$1'." ; usage >&2; exit 1 ;;
  esac
  shift
done

# Store the remaining part as arguments.
args+=("$@")

############## End Options and Usage ###################

# ############# ############# #############
# ##       MAIN SCRIPT BODY              ##
# ##                                     ##
# ##                                     ##
# ############# ############# #############

# Set IFS to preferred implementation
IFS=$'\n\t'

# Exit on error. Append '||true' when you run the script if you expect an error.
set -o errexit

# Run in debug mode, if set
if ${debug}; then set -x ; fi

# Bash will remember & return the highest exitcode in a chain of pipes.
# This way you can catch the error in case mysqldump fails in `mysqldump |gzip`, for example.
set -o pipefail

# Validate all required params are present
if [[ -z ${BASTION_SUBNET_OCID} || -z ${ADMIN_SUBNET_OCID} || -z ${WORKER_SUBNET_OCID} || -z ${FSS_SUBNET_OCID} || -z ${LB_SUBNET_OCID} ]]
then
  echo "One or more required params are not specified. Please provide either bastion and Admin subnet OCIDs"
  usage >&2
  exit
fi

vcn_ocid=$(oci network subnet get --subnet-id "${BASTION_SUBNET_OCID}" | jq -r '.data["vcn-id"]')
vcn_cidr=$(oci network vcn get --vcn-id "${vcn_ocid}" | jq -r '.data["cidr-block"]')

# Check if SSH port - 22 is open for access by Bastion Subnet
if [[ -n ${BASTION_SUBNET_OCID} || -n ${BASTION_HOST_IP_CIDR} ]]
then
  all_ips="0.0.0.0/0"
  res=$(validate_subnet_port_access "${BASTION_SUBNET_OCID}" "${SSH_PORT}" "${all_ips}")

  if [[ ${res} -ne 0 ]]
    then
      echo "ERROR: SSH port ${SSH_PORT} is not open for access by [$all_ips] in -- ${BASTION_SUBNET_OCID}"
  fi

  # Check if bastion host IP is valid CIDR
  bastion_cidr_block=""
  if [[ -n ${BASTION_HOST_IP_CIDR} ]]
  then
    is_valid_cidr=$(is_valid_ip_cidr "${BASTION_HOST_IP_CIDR}")
    if [[ $is_valid_cidr -ne 0 ]]
    then
      echo "Bastion host IP CIDR is not valid: [${BASTION_HOST_IP_CIDR}]"
      usage >&2
      exit
    fi
    bastion_cidr_block=${BASTION_HOST_IP_CIDR}
  else
    bastion_cidr_block=$(oci network subnet get --subnet-id "${BASTION_SUBNET_OCID}" | jq -r '.data["cidr-block"]')
  fi

  # Check if bastion CIDR has access to SSH port on ADMIN subnet
  res=$(validate_subnet_port_access "${ADMIN_SUBNET_OCID}" "${SSH_PORT}" "${bastion_cidr_block}")

  if [[ $res -ne 0 ]]
  then
    echo "WARNING: SSH port ${SSH_PORT} is not open for access by Bastion Subnet CIDR [$bastion_cidr_block] in private Admin Subnet [$ADMIN_SUBNET_OCID]"
  fi
fi

# Check if service or NAT gateway exists in ADMIN & WORKER subnet's VCN.
if [[ -n ${ADMIN_SUBNET_OCID} && -n ${WORKER_SUBNET_OCID} ]]
then
  subnet_names=('ADMIN_SUBNET' 'WORKER_SUBNET')
  i=0
  for subnet_ocid in ${ADMIN_SUBNET_OCID} ${WORKER_SUBNET_OCID}; do
    res=$(validate_service_or_nat_gw_exist "${subnet_ocid}")
    if [[ $res -eq 1 ]]
    then
      echo "ERROR: Missing Service or NAT gateway in the VCN of the private ${subnet_names[i]} subnet ocid [$subnet_ocid]"
    elif [[ $res -eq 2 ]]
    then
      echo "ERROR: Private ${subnet_names[i]} subnet [$subnet_ocid] does not use NAT or Service gateway"
    elif [[ $res -eq 3 ]]
    then
      echo "ERROR: Service Gateway in VCN of private ${subnet_names[i]} subnet [$subnet_ocid] does not allow access to all services in Oracle services network"
    elif [[ $res -eq 4 ]]
    then
      echo "ERROR: Route Rule of private ${subnet_names[i]} subnet [$subnet_ocid] does not use 'ALL Services in Oracle services network' destination"
    fi
  done
fi

# Check if internet gateway exists in BASTION & LB & FSS subnet's VCN.
subnet_names=('BASTION_SUBNET' 'LB_SUBNET' 'FSS_SUBNET_OCID')
i=0
for subnet_ocid in ${BASTION_SUBNET_OCID} ${LB_SUBNET_OCID} ${FSS_SUBNET_OCID}; do
  res=$(validate_internet_gw_exist "${subnet_ocid}")

  if [[ $res -ne 0 ]]
  then
    echo "WARNING: Missing internet gateway in the VCN of the ${subnet_names[i]} subnet [$subnet_ocid]"
  fi
  i=$((i+1))
done

# Check if LB Subnet ports are open  0.0.0.0/0 all, 443, 80
all_ips="0.0.0.0/0"
for port in 'all' '443' '80'; do
  res=$(validate_subnet_port_access "${LB_SUBNET_OCID}" "${port}" "${all_ips}")

  if [[ $res -ne 0 ]]
  then
    echo "WARNING: Port [$port] is not open for 0.0.0.0/0 in LB Subnet CIDR [${LB_SUBNET_OCID}]"
  fi
done

# Check if Worker Subnet all protocols are open for workers subnet
worker_subnet_cidr=$(oci network subnet get --subnet-id "${WORKER_SUBNET_OCID}" | jq -r '.data["cidr-block"]')
res=$(validate_subnet_port_access "${WORKER_SUBNET_OCID}" "all" ${worker_subnet_cidr})

if [[ $res -ne 0 ]]
then
  echo "ERROR: All Protocols are not open for WORKER's Subnet CIDR [${worker_subnet_cidr}]"
fi

# FSS subnet verification - Checking All TCP Ports are open in FSS SUBNET OCID for VCN CIDR
for port in '111' '2048' '2049' '2050'; do
  res=$(validate_subnet_port_access "${FSS_SUBNET_OCID}" "${port}" "${vcn_cidr}")
  if [[ $res -ne 0 ]]
  then
    echo "ERROR: TCP Port [${port}] is not open in FSS Subnet for VCN CIDR"
  fi
done

# FSS subnet verification - UDP - '111' '2048' in FSS SUBNET OCID for VCN CIDR"
for port in '111' '2048'; do
  res=$(validate_subnet_port_access "${FSS_SUBNET_OCID}" "${port}" "${vcn_cidr}" "UDP")
  if [[ $res -ne 0 ]]
  then
    echo "ERROR: UDP Port [${port}] is not open in FSS Subnet for VCN CIDR"
  fi
done

# Check if DB port is open for access by Worker's subnet CIDR in DB subnet (only if DB subnet is provided)
if [[ -n ${DB_SUBNET_OCID} ]]
then
  res=$(validate_subnet_port_access ${DB_SUBNET_OCID} ${DB_PORT} ${vcn_cidr})
  res1=$(validate_subnet_port_access ${DB_SUBNET_OCID} ${DB_PORT} ${worker_subnet_cidr})

  if [[ (${res} -ne 0) || (${res1} -ne 0) ]]
  then
    echo "ERROR: DB port ${DB_PORT} is not open for access by VCN CIDR [$vcn_cidr] or Worker Subnet CIDR [$worker_subnet_cidr] in DB Subnet [$DB_SUBNET_OCID]"
  fi
fi