AWSのセキュリティグループをShellスクリプトで作る

 前回はVPCとサブネットを作成したんで、今回はセキュリティグループを作成してみた。これは使用シーン多いので結構、重宝するかもしれない。ありきたりだけどなかなかほしいと思うのがなかったので作成。例のごとくレガシーなCSVファイル読込という古い形式です。

◆シリーズ

VPCとサブネットの作成

セキュリティグループの作成

EC2の作成


◆Shellスクリプト本体

#!/bin/bash

# 変数の設定
RULES_FILE=$1  # ルールを記述したCSVファイル

# 引数の検証
if [ $# -ne 1 ]; then
    echo "Usage: $0 <rule_file.csv>" >&2
    exit 1
fi

# ファイルの存在確認
if [[ ! -f "$RULES_FILE" ]]; then
  echo "ルールファイルが見つかりません: $RULES_FILE" >&2
  exit 1
fi

# VPC名とリージョンからVPC IDを取得する関数
get_vpc_id() {
  local REGION=$1
  local VPC_NAME=$2
  local VPC_ID=$(aws ec2 describe-vpcs \
    --region ${REGION} \
    --filters "Name=tag:Name,Values=${VPC_NAME}" \
    --query "Vpcs[0].VpcId" \
    --output text
  )
  if [[ $VPC_ID == "None" ]]; then
    echo "エラー: VPC名 '${VPC_NAME}' はリージョン '${REGION}' に存在しません。" >&2
    exit 1
  fi
  echo $VPC_ID
}

# プレフィックスリスト名からIDを取得する関数
get_prefix_list_id() {
  local REGION=$1
  local PREFIX_LIST_NAME=$2
  local PREFIX_LIST_ID=$(aws ec2 describe-managed-prefix-lists \
    --region ${REGION} \
    --filters "Name=prefix-list-name,Values=${PREFIX_LIST_NAME}" \
    --query "PrefixLists[0].PrefixListId" \
    --output text
  )
  if [[ $PREFIX_LIST_ID == "None" ]]; then
    echo ""
  else
    echo $PREFIX_LIST_ID
  fi
}

# セキュリティグループ名からIDを取得する関数
get_security_group_id() {
  local REGION=$1
  local SG_NAME=$2
  local SG_ID=$(aws ec2 describe-security-groups \
    --region ${REGION} \
    --filters "Name=group-name,Values=${SG_NAME}" \
    --query "SecurityGroups[0].GroupId" \
    --output text)
  if [[ $SG_ID == "None" ]]; then
    echo ""
  else
    echo $SG_ID
  fi
}

# セキュリティグループに同一ルールが既に存在するか確認する関数
rule_exists() {
  local REGION=$1
  local GROUP_ID=$2
  local PROTOCOL=$3
  local FROM_PORT=$4
  local TO_PORT=$5
  local TARGET=$6

  # 既存のルールを取得
  EXISTING_RULES=$(aws ec2 describe-security-group-rules \
    --region ${REGION} \
    --filters "Name=group-id,Values=${GROUP_ID}" \
    --query "SecurityGroupRules[?IpProtocol=='${PROTOCOL}' && FromPort==\`${FROM_PORT}\` && ToPort==\`${TO_PORT}\`]" \
    --output json
  )

  # ターゲットがCIDRの場合
  if [[ ${TARGET} =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+$ ]]; then
    echo "${EXISTING_RULES}" | jq -e --arg TARGET "${TARGET}" '.[] | select(.CidrIpv4 == $TARGET)' > /dev/null
    return $?
  # ターゲットがセキュリティグループIDの場合
  elif [[ ${TARGET} =~ ^sg- ]]; then
    echo "${EXISTING_RULES}" | jq -e --arg TARGET "${TARGET}" '.[] | select(.ReferencedGroupInfo.GroupId == $TARGET)' > /dev/null
    return $?
  # ターゲットがプレフィックスリストIDの場合
  elif [[ ${TARGET} =~ ^pl- ]]; then
    echo "${EXISTING_RULES}" | jq -e --arg TARGET "${TARGET}" '.[] | select(.PrefixListId == $TARGET)' > /dev/null
    return $?
  fi

  return 1
}

# 作業対象のセキュリティグループの名前とIDをマッピング
# 存在しない場合は新規で作成
# 対象は引数にしたファイル内のリージョン、VPC ID、名前、説明が一意のものを抽出
declare -A sg_map
while IFS=, read -r REGION VPC_NAME NAME DESCRIPTION
do
  VPCID=$(get_vpc_id ${REGION} ${VPC_NAME})
  if [ -z ${sg_map[${REGION}${VPCID}${NAME}]} ]; then
    sg_id=$(aws ec2 describe-security-groups \
      --region ${REGION} \
      --filters "Name=vpc-id,Values=${VPCID}" "Name=group-name,Values=${NAME}" \
      --query "SecurityGroups[0].GroupId" \
      --output text
    )
    if [[ $sg_id == "None" ]]; then
      echo "新しいセキュリティグループを作成します: ${NAME} (${REGION})"
      sg_map[${REGION}${VPCID}${NAME}]=$(aws ec2 create-security-group \
        --region ${REGION} \
        --group-name ${NAME} \
        --description "${DESCRIPTION}" \
        --vpc-id ${VPCID} \
        --query 'GroupId' \
        --output text
      )
    else
      echo "セキュリティグループが既に存在します: ${NAME} (${REGION})"
      sg_map[${REGION}${VPCID}${NAME}]=$sg_id
    fi
  fi
done << EOS
$(awk 'BEGIN{FS=","; OFS=","} NR>1 {print $1,$2,$3,$4}' ${RULES_FILE} | sort | uniq)
EOS

# 引数にしたファイルを読み込みセキュリティグループのルールを修正
# ファイルの1行目はヘッダーとして無視する
while IFS=, read -r REGION VPC_NAME NAME DESCRIPTION ACTION DIRECTION PROTOCOL FROM_PORT TO_PORT TARGET RULE_DESCRIPTION
do
  VPCID=$(get_vpc_id ${REGION} ${VPC_NAME})
  if [ "${ACTION}" == "add" ]; then
    ACT="authorize"
    if [ -z "$RULE_DESCRIPTION" ]; then
      RLDESC=""
    else
      RLDESC=",Description=${RULE_DESCRIPTION}"
    fi
  elif [ "${ACTION}" == "remove" ]; then
    ACT="revoke"
    RLDESC=""
  fi
  
  SCMD="${ACT}-security-group-${DIRECTION}"

  # ターゲットの種類を判定
  if [[ ${TARGET} =~ ^sg- ]]; then
    TARGET_TYPE="GroupId"
  elif [[ ${TARGET} =~ ^pl- ]]; then
    TARGET_TYPE="PrefixListId"
  else
    # セキュリティグループ名の場合
    SG_ID=$(get_security_group_id ${REGION} ${TARGET})
    if [[ -n $SG_ID ]]; then
      TARGET=$SG_ID
      TARGET_TYPE="GroupId"
    else
      # プレフィックスリスト名の場合
      PL_ID=$(get_prefix_list_id ${REGION} ${TARGET})
      if [[ -n $PL_ID ]]; then
        TARGET=$PL_ID
        TARGET_TYPE="PrefixListId"
      else
        # CIDRブロックの場合
        TARGET_TYPE="CidrIp"
      fi
    fi
  fi

  if [[ $TARGET_TYPE == "GroupId" ]]; then
    RULE_TARGET="UserIdGroupPairs=[{GroupId=${TARGET}${RLDESC}}]"
  elif [[ $TARGET_TYPE == "PrefixListId" ]]; then
    RULE_TARGET="PrefixListIds=[{PrefixListId=${TARGET}${RLDESC}}]"
  else
    RULE_TARGET="IpRanges=[{CidrIp=${TARGET}${RLDESC}}]"
  fi
  
  PACKET="IpProtocol=${PROTOCOL}"
  
  if [ "${PROTOCOL}" != "-1" ]; then
    PACKET="${PACKET},FromPort=${FROM_PORT},ToPort=${TO_PORT}"
  fi
  
  PERMISSIONS="${PACKET},${RULE_TARGET}"

  # addの場合、同一ルールが既に存在するか確認
  if [ "${ACTION}" == "add" ]; then
    if rule_exists ${REGION} ${sg_map[${REGION}${VPCID}${NAME}]} ${PROTOCOL} ${FROM_PORT} ${TO_PORT} ${TARGET}; then
      echo "同一ルールが既に存在します: ${DIRECTION}-${PROTOCOL}-${FROM_PORT}-${TO_PORT}-${TARGET}"
      continue
    fi
  fi

  echo "${ACTION} ルール: セキュリティグループ ${NAME}: ${DIRECTION}-${PROTOCOL}-${FROM_PORT}-${TO_PORT}-${TARGET}"
  aws ec2 ${SCMD} --region ${REGION} --group-id ${sg_map[${REGION}${VPCID}${NAME}]} --ip-permissions "${PERMISSIONS}" 1> /dev/null
  if [ $? -eq 0 ]; then
    echo "${ACTION} ルールの適用に成功しました"
  else
    echo "${ACTION} ルールの適用に失敗しました"
  fi

done << EOS
$(awk 'BEGIN{FS=","; OFS=","} NR>1 {print $0}' ${RULES_FILE})
EOS


で、例のごとく読込用のCSVファイルのサンプル、addのところremoveとすると既存のセキュリティグループからルールを削除できる。

cat <<_EOF_ > securitygroups.csv
region,VPC_name,group_name,description,action,direction,protocol,from_port,to_port,source,rule_description
ap-northeast-1,handson-cli-vpc,my-sg-1,My first security group1,add,ingress,tcp,22,22,0.0.0.0/0,SSH access
ap-northeast-1,handson-cli-vpc,my-sg-1,My first security group1,add,egress,tcp,80,80,0.0.0.0/0,HTTP outbound
ap-northeast-1,handson-cli-vpc,my-sg-1,My first security group1,add,ingress,-1,,,my-sg-3,Allow all
ap-northeast-1,handson-cli-vpc,my-sg-2,My first security group2,add,ingress,tcp,22,22,0.0.0.0/0,SSH access
ap-northeast-1,handson-cli-vpc,my-sg-2,My second security group2,add,ingress,tcp,80,80,10.0.0.0/16,HTTP outbound
ap-northeast-1,handson-cli-vpc,my-sg-2,My second security group2,add,ingress,tcp,443,443,my-sg-1,HTTP inbound
ap-northeast-1,handson-cli-vpc,my-sg-3,My third security group3,add,ingress,tcp,22,22,test_plefix_list,SSH access
ap-northeast-1,handson-cli-vpc,my-sg-3,My third security group3,add,egress,-1,,,0.0.0.0/0,
us-east-1,test1vpc,test-sg,Existing security group,add,ingress,tcp,3389,3389,10.0.2.0/24,exist
us-east-1,test1vpc,test-sg,Existing security group,add,ingress,tcp,22,22,0.0.0.0/0,
us-east-1,test1vpc,test-sg,Existing security group,add,ingress,tcp,443,443,0.0.0.0/0,add
_EOF_

後はEC2とALBとRDSくらいかな。。。。

気が向いたらやるかもだけど、ありきたりなのであまりやる気が起きない。

コメント

このブログの人気の投稿

GASでGoogleDriveのサブフォルダとファイル一覧を出力する

マクロ経済学(IS-LM分析)

証券外務員1種勉強(計算式暗記用メモ)