OCI Security listおよびNetwork security groupのアクセスリストをCSVに抽出

## 概要
パブリッククラウドはネットワークの機能としてアクセスリストがありファイアウォールのような通信の保護が可能になっています。
OCIにおいてはSecurity listおよびNetwork security group(以下NSG)があり、以下の役割があります。
- Security list : サブネット単位でアクセス管理、ステートフル/ステートレス選択可能、Allowのみ
- NSG : ホスト(VNIC)単位でアクセス管理、ステートフル/ステートレス選択可能、Allowのみ

Security listとNSGは併用することが可能です。
以下を例にするとComputeはそれぞれ以下のアクセスリストが適用されます。
- Compute1 : Security list
- Compute2 : Security list + NSG
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730992/1c93359a-dafd-e6a0-cad2-423bc495014a.png">

Security listやNSGは以下のようにサブネットやVNICに関連付けが可能です。
- 1つのSecurity listを複数のSubnetに関連付け
- 1つのSubnetに複数のSecurityを関連付け
- 1つのNSGを複数のVNICに関連付け
- 1つのVNICを複数のNSGに関連付け

このようにSecurity listやNSGを組み合わせることによって効率的な設定が可能になりますが、大規模ネットワークになった場合の一元管理が課題となります。
また、PCI DSS要件内に規定されたファイアウォールルールセットの6カ月毎レビューのようにセキュリティ基準に対応したファイアウォール管理をする必要があります。

この記事ではSecurity listとNetwork security groupのアクセスリストをFirewallのような表示内容のCSVに抽出するためのShellを作成しました。

## 準備
プログラムを実行するための前提条件は以下のとおりです。
- OCI CLIが実行可能なLinux環境またはOCI Cloud Shell
- ネットワーク情報を参照できる適切なPolicy設定がある
　例:allow 【ユーザーが所属するグループ】 to use virtual-network-family in compartment 【対象コンパートメント名】

## Shell設計
- 仕様
    - 引数としてコンパートメントOCIDとVCN OCIDを使用
    - SubnetやVNICに関連付けられたSecurity listおよびNSGから情報抽出
    - 抽出する情報は以下のとおり
        - KIND:SL(Security list)/NSG
        - NAME:Security list名/NSG名
        - STATELESS:ステートフルはFALSE, ステートレスはTRUE
        - DIRECTION:EGRESS(外部向け通信)/INGRESS(内部向け通信)
        - SOURCE IP:送信元IP
        - DEST IP:宛先IP
        - PROTOCOL:All(全て)/1(ICMP)/6(TCP)/17(UDP)
        - TCP SRC MIN:送信元TCP PORT MIN
        - TCP SRC MAX:送信元TCP PORT MAX
        - TCP DST MIN:宛先TCP PORT MIN
        - TCP DST MAX:宛先TCP PORT MAX
        - UDP SRC MIN:送信元UDP PORT MIN
        - UDP SRC MAX:送信元UDP PORT MAX
        - UDP DST MIN:宛先UDP PORT MIN
        - UDP DST MAX:宛先UDP PORT MAX
        - ICMP TYPE:ICMP TYPE
        - ICMP CODE:ICMP CODE
        - OCID:Security listまたはNSGのOCID
 - Shellの流れ
    - Security listから抽出
         - VCN内subnet抽出
         - subnetに関連付けられたSecurity list抽出
         - Security listのruleを抽出
    - NSGから抽出
        - VCN内NSG抽出
        - NSGに関連付けられたVNIC list抽出
        - VNICからIP address抽出
        - NSGのruleを抽出
    - 列タイトル,Security list ruleおよびNSG ruleをマージして日付を含めたcsvファイルを出力

## Shell内容
```sample.sh
#! /bin/bash

COMP_OCID=$1
VCN_OCID=$2

#
# Security List
#
echo "Try:Security List to csv"

echo "Try:oci network subnet list"
oci network subnet list -c $COMP_OCID --vcn-id $VCN_OCID > /tmp/subnet_list.json
cat /tmp/subnet_list.json | jq -r '(.data[]|."cidr-block")' > /tmp/subnet_list.txt
echo "Done:oci network subnet list"

: > /tmp/security-list-temp2.csv
FILE_NAME=/tmp/subnet_list.txt
while read LINE
do

echo $LINE

cat /tmp/subnet_list.json | jq --arg arg1 $LINE -r '(.data[]|select(."cidr-block" == $arg1) | ."security-list-ids") | @csv'> /tmp/security_list_ids.txt

sed -i -e 's/"//g' /tmp/security_list_ids.txt
cat /tmp/security_list_ids.txt | tr "," "\n" > /tmp/security_list_ids2.txt

FILE_NAME2=/tmp/security_list_ids2.txt

    while read LINE2
    do
    echo "Try:oci network security-list get"
    oci network security-list get --security-list-id $LINE2 > /tmp/security-list.json
    echo "Done:oci network security-list get"

    SECURITY_LIST_SUBNET="\""$LINE"\""

    SECURITY_LIST_DISPNAME=`cat /tmp/security-list.json | jq -r '."data"|[."display-name"] |@csv'`
    SECURITY_LIST_DISPNAME2=${SECURITY_LIST_DISPNAME//" "/"_"}

    LINE22="\""$LINE2"\""

    cat /tmp/security-list.json | jq -r '."data"|."egress-security-rules"[]| [."is-stateless", ."destination", ."protocol", if ."protocol" == "6" and ."tcp-options"."source-port-range" == null then "ALL","ALL" else ."tcp-options"."source-port-range"."min",."tcp-options"."source-port-range"."max" end,if ."protocol" == "6" and ."tcp-options"."destination-port-range" == null then "ALL","ALL" else ."tcp-options"."destination-port-range"."min",."tcp-options"."destination-port-range"."max" end,if ."protocol" == "17" and ."udp-options"."source-port-range" == null then "ALL","ALL" else ."udp-options"."source-port-range"."min",."udp-options"."source-port-range"."max"end,if ."protocol" == "17" and ."udp-options"."destination-port-range" == null then "ALL","ALL" else ."udp-options"."destination-port-range"."min",."udp-options"."destination-port-range"."max" end,if ."protocol" == "1" and ."icmp-options" == null then "ALL","ALL" else ."icmp-options"."type",."icmp-options"."code" end]|@csv' > /tmp/security-list-temp.csv

    cat /tmp/security-list-temp.csv | awk -v awkVar1=$SECURITY_LIST_DISPNAME2 -v awkVar2=$SECURITY_LIST_SUBNET -v awkVar3=$LINE22 -F"," '{print "\"SL\"" "," awkVar1 "," $1 "," "\"EGRESS\"" "," awkVar2 "," $2 "," $3 "," $4 "," $5 "," $6 "," $7 "," $8 "," $9 "," $10 "," $11 "," $12 "," $13 "," awkVar3}'>> /tmp/security-list-temp2.csv

    cat /tmp/security-list.json | jq -r '."data"|."ingress-security-rules"[]| [."is-stateless", ."source", ."protocol", if ."protocol" == "6" and ."tcp-options"."source-port-range" == null then "ALL","ALL" else ."tcp-options"."source-port-range"."min",."tcp-options"."source-port-range"."max" end,if ."protocol" == "6" and ."tcp-options"."destination-port-range" == null then "ALL","ALL" else ."tcp-options"."destination-port-range"."min",."tcp-options"."destination-port-range"."max" end,if ."protocol" == "17" and ."udp-options"."source-port-range" == null then "ALL","ALL" else ."udp-options"."source-port-range"."min",."udp-options"."source-port-range"."max"end,if ."protocol" == "17" and ."udp-options"."destination-port-range" == null then "ALL","ALL" else ."udp-options"."destination-port-range"."min",."udp-options"."destination-port-range"."max" end,if ."protocol" == "1" and ."icmp-options" == null then "ALL","ALL" else ."icmp-options"."type",."icmp-options"."code" end]|@csv' > /tmp/security-list-temp.csv

    cat /tmp/security-list-temp.csv | awk -v awkVar1=$SECURITY_LIST_DISPNAME2 -v awkVar2=$SECURITY_LIST_SUBNET -v awkVar3=$LINE22 -F"," '{print "\"SL\"" "," awkVar1 "," $1 "," "\"INGRESS\"" ","  $2 "," awkVar2 "," $3 "," $4 "," $5 "," $6 "," $7 "," $8 "," $9 "," $10 "," $11 "," $12 "," $13 "," awkVar3}'>> /tmp/security-list-temp2.csv

    echo "Done:nsg-rule-list per SL OCID"

    done < ${FILE_NAME2}
    echo "Done:nsg-rule-list per Subnet
    "
done < ${FILE_NAME}
echo "Done:Security List to csv"

#
# NSG
#
echo "Try:NSG to csv"

oci network nsg list -c $COMP_OCID --vcn-id $VCN_OCID > /tmp/nsg_list.json
cat /tmp/nsg_list.json | jq -r '."data"[]|."id"' > /tmp/nsg_list.txt

: > /tmp/nsg-rule-list-temp2.csv
FILE_NAME=/tmp/nsg_list.txt
while read LINE
do

echo $LINE
oci network nsg vnics list --nsg-id $LINE | jq -r '."data"[]|."vnic-id"' > /tmp/vnic-id.txt

FILE_NAME2=/tmp/vnic-id.txt

    while read LINE2
    do
    echo "Try:oci network vnic get"
    VNIC_IP=`oci network vnic get --vnic-id $LINE2 | jq -r '."data"|."private-ip"'`
    VNIC_IP2="\""$VNIC_IP"\""
    echo "Done:oci network vnic get"

    echo "Try:oci network nsg get"
    NSGNAME=`oci network nsg get --nsg-id $LINE | jq -r '."data"|[."display-name"] |@csv'`
    NSGNAME2=${NSGNAME//" "/"_"}

    LINE12="\""$LINE"\""
    echo "Done:oci network nsg get"

    oci network nsg rules list --all --nsg-id $LINE > /tmp/nsg_rule_list.json
    echo "Done:oci network nsg rules list"

    cat /tmp/nsg_rule_list.json | jq -r '."data"[]|[."is-stateless", ."direction",if ."destination" then ."destination" else ."source" end, ."protocol", if ."protocol" == "6" and ."tcp-options"."source-port-range" == null then "ALL","ALL" else ."tcp-options"."source-port-range"."min",."tcp-options"."source-port-range"."max" end,if ."protocol" == "6" and ."tcp-options"."destination-port-range" == null then "ALL","ALL" else ."tcp-options"."destination-port-range"."min",."tcp-options"."destination-port-range"."max" end,if ."protocol" == "17" and ."udp-options"."source-port-range" == null then "ALL","ALL" else ."udp-options"."source-port-range"."min",."udp-options"."source-port-range"."max"end,if ."protocol" == "17" and ."udp-options"."destination-port-range" == null then "ALL","ALL" else ."udp-options"."destination-port-range"."min",."udp-options"."destination-port-range"."max" end,if ."protocol" == "1" and ."icmp-options" == null then "ALL","ALL" else ."icmp-options"."type",."icmp-options"."code" end]|@csv' > /tmp/nsg-rule-list-temp.csv

    cat /tmp/nsg-rule-list-temp.csv | grep EGRESS > /tmp/nsg-rule-list-temp-egress.csv
    cat /tmp/nsg-rule-list-temp-egress.csv | awk -v awkVar1=$NSGNAME2 -v awkVar2=$VNIC_IP2 -v awkVar3=$LINE12 -F"," '{print "\"NSG\"" "," awkVar1 "," $1 "," $2 ","  awkVar2 "," $3 "," $4 "," $5 "," $6 "," $7 "," $8 "," $9 "," $10 "," $11 "," $12 "," $13 "," $14 "," awkVar3}'>> /tmp/nsg-rule-list-temp2.csv

    cat /tmp/nsg-rule-list-temp.csv | grep INGRESS > /tmp/nsg-rule-list-temp-ingress.csv
    cat /tmp/nsg-rule-list-temp-ingress.csv | awk -v awkVar1=$NSGNAME2 -v awkVar2=$VNIC_IP2 -v awkVar3=$LINE12 -F"," '{print "\"NSG\"" "," awkVar1 "," $1 "," $2 ","  $3 "," awkVar2 "," $4 "," $5 "," $6 "," $7 "," $8 "," $9 "," $10 "," $11 "," $12 "," $13 "," $14 "," awkVar3}'>> /tmp/nsg-rule-list-temp2.csv
    echo "Done:nsg-rule-list per VNIC OCID"

    done < ${FILE_NAME2}

    echo "Done:nsg-rule-list per NSG OCID"
done < ${FILE_NAME}

echo "Done:NSG to csv"

#
# MERGE
#
: > /tmp/seclist-csv-temp.csv
echo "KIND","NAME","STATELESS","DIRECTION","SOURCE IP","DEST IP","PROTOCOL","TCP SRC MIN","TCP SRC MAX","TCP DST MIN","TCP DST MAX","UDP SRC MIN","UDP SRC MAX","UDP DST MIN","UDP DST MAX","ICMP TYPE","ICMP CODE","OCID" > /tmp/seclist-csv-temp.csv
cat /tmp/security-list-temp2.csv >> /tmp/seclist-csv-temp.csv
cat /tmp/nsg-rule-list-temp2.csv >> /tmp/seclist-csv-temp.csv
cp /tmp/seclist-csv-temp.csv seclist_`date +%Y%m%d_%H%M%S`.csv
```
# 実行結果
引数としてコンパートメントOCIDとVCN OCIDを使用してshell実行するとcsvファイルが作成されます。
```
[user@linux]$ /bin/bash sample.sh 【コンパートメントOCID】 【VCN OCID】
:
略
:
[user@linux]$ ls | grep csv
seclist_yyyymmdd_HHMMSS.csv
```
作成されたCSVを開いて内容を確認します。
- ルール全体を確認して不要なルールがないか確認可能です
- 定期的に取得することによって差分があれば適切な変更があったか管理することが可能です
<img width="700" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730992/1f43e649-0bca-46c7-1c1a-b9ee5ad8187b.png">