※本記事は、個人の意見および個人的活動を記したものであり、会社を代表するものではありません
これまでの処理で、KeyVaultに保管したVMのパスワードを使って、システムオペレータがVMにログインできるようにしてきた。
KeyVaultの参照は正常にできるようになったものの、クライアントPCのIPがBastionSubnetのNSGで許可されていないため、今の状態ではBastionからVMへアクセスする時にNSGでブロックされてしまう。
今回やりたいのは以下の処理。
- BastionSubnetのNSG受信ルールに自分のPCのパブリックIPを追加する処理をスクリプト化し、必要な時に必要最低限の情報のみで受信ルールの登録を手動実行したい。
毎回実行するというよりは、クライアントPCの接続環境により登録するかどうかが変わるので、必要な時に手動でスクリプトを実行できるようにしておきたいと思った。
スクリプトを作成する時、「NSGの既存受信ルールの一番優先度の低いレコードを取得する」というJMESPathの定義に少し調査時間がかかったが、やってみると面白かったのでここにまとめる。
JMESPathについては以下を参照。
1. 新しい受信ルールを追加するスクリプトの定義
まずは作成したスクリプトはこちらなので、コメントから大体の流れを確認。
#!/bin/bash
# This script adds an inbound rule to a specified Network Security Group (NSG)
# to allow traffic from the client's public IP address.
# The rule is added with a priority that is 10 lower than the current lowest
# priority inbound rule with a priority less than 4096.
# If the calculated priority exceeds 4096, the script exits with an error.
#
# Usage:
# ./addInboundRule.sh <resource-group> <nsg-name>
#
# Parameters:
# <resource-group> : The name of the resource group containing the NSG.
# <nsg-name> : The name of the Network Security Group to which the rule
# will be added.
#
# Example:
# ./addInboundRule.sh myResourceGroup myNsg
#
# Recommendations:
# - Ensure that you have the necessary permissions to modify the NSG.
# - Run this script from a secure environment to avoid exposing your public IP
# address.
# - Verify the existing rules and priorities in the NSG to avoid conflicts.
# Check parameters.
if [[ "$#" -ne 2 ]]; then
echo "Usage: $0 <resource-group> <nsg-name>"
exit 1
fi
# Set parameters to variables that are easier to understand.
RESOURCE_GROUP=$1
NSG_NAME=$2
# Get public IP address of the client.
IP_ADDRESS=$(curl -s ifconfig.me)
echo " Your Public IP Address: $IP_ADDRESS"
# Check if the NSG rule already exists.
SAME_RULE_NAME=$(az network nsg rule list --resource-group $RESOURCE_GROUP --nsg-name $NSG_NAME --query "[?sourceAddressPrefix=='$IP_ADDRESS'&&direction=='Inbound'] | [0].name" -o tsv)
if [[ -n "$SAME_RULE_NAME" ]]; then
echo "Ignored: The IP address '$IP_ADDRESS' already exists in Inbound rule '$SAME_RULE_NAME' of NSG '$NSG_NAME' in resource group '$RESOURCE_GROUP'."
exit 1
fi
# Build rule name.
# Example: AllowInboundFrom_1.2.3.4
RULE_NAME="AllowInboundFrom_$IP_ADDRESS"
# Retrieve the lowest priority value among the Inbound rules with a priority less than 4096.
# If the lowest priority value cannot be obtained, set the default priority.
LOWEST_PRIORITY=$(az network nsg rule list --resource-group $RESOURCE_GROUP --nsg-name $NSG_NAME --query "[?priority < to_number('4096') && direction == 'Inbound'] | reverse(sort_by([], &priority)) | [0].priority" -o tsv)
echo " Current lowest Inbound rule priority: $LOWEST_PRIORITY"
if [[ -z "$LOWEST_PRIORITY" ]]; then
LOWEST_PRIORITY=100
fi
# Calculate the new priority.
# If the new priority exceeds the maximum allowed value of 4096 then exit with an error.
NEW_PRIORITY=$((LOWEST_PRIORITY + 10))
echo " New rule priority: $NEW_PRIORITY"
if [[ "$NEW_PRIORITY" -gt 4096 ]]; then
echo "Error: The calculated priority ($NEW_PRIORITY) exceeds the maximum allowed value of 4096."
exit 1
fi
# Add the new NSG rule.
az network nsg rule create --resource-group "$RESOURCE_GROUP" --nsg-name "$NSG_NAME" --name "$RULE_NAME" --priority "$NEW_PRIORITY" --direction Inbound --access Allow --protocol TCP --source-address-prefixes "$IP_ADDRESS" --source-port-ranges "*" --destination-address-prefixes "*" --destination-port-ranges "443"
if [[ $? -eq 0 ]]; then
echo "Success: Added NSG rule '$RULE_NAME' with priority '$NEW_PRIORITY' to NSG '$NSG_NAME' in resource group '$RESOURCE_GROUP'."
else
echo "Failed to add NSG rule."
exit 1
fi
実物はGithubリポジトリを参照。
2. スクリプト作成時に調査した事の整理
スクリプト内の要点を以下にまとめる。
2-1. 自分のパブリックIPの取得
37行目、自分のPCのパブリックIPを取得するコマンド。
パラメータで指定しても良いが、自動で取得できるならその方が良いと考えた。
もし、会社のPIPがある程度の範囲で定義されているのであれば、CIDR形式で範囲を指定したIPアドレスをパラメータ指定するように変えても良いかな。
curl -s ifconfig.me
2-2. 既存ルールの同じパブリックIPのルール名を取得
41行目、クライアントPCのパブリックIPが既に登録されているかどうかチェックするために、既存ルールのルール名を取得。
同じパブリックIPが登録されている場合は、そのルール名をstdoutして処理を中断する。
az network nsg rule list --resource-group $RESOURCE_GROUP --nsg-name $NSG_NAME --query "[?sourceAddressPrefix=='$IP_ADDRESS'&&direction=='Inbound'] | [].name" -o tsv
2-3. NSGの既存受信ルールの取得とフィルター
53行目、NSGの既存受信ルールの一番優先度が低いレコードを取得。
JMESPathは、MSドキュメントのサンプルを見てわかったようなフリをしていたが、自分のやりたいこと決めて試行錯誤しながら定義したのは初めて。このルールを定義するのに苦労したので後ほど詳解する。
ここで取得したレコードから”priority”属性値だけを取得して+10すれば今回付与したい優先度になる。
az network nsg rule list --resource-group $RESOURCE_GROUP --nsg-name $NSG_NAME --query "[?priority < to_number('4096') && direction == 'Inbound'] | reverse(sort_by([], &priority)) | [0].priority" -o tsv
2-4. NSGへの新しい受信ルールの追加
69行目、自分のPCのパブリックIPを受信ルールに追加。
IPアドレスと優先度は上記で取得・計算した値を指定する。
名前は、自分のPCのIPを許可することがわかるような名前にする。
az network nsg rule create --resource-group "RagSystem-dev" --nsg-name "RagSystem-BastionNsg-dev" --name "AllowFromClientIP220" --priority "220" --direction Inbound --access Allow --protocol TCP --source-address-prefixes "103.5.140.141" --source-port-ranges "*" --destination-address-prefixes "*" --destination-port-ranges "443"
3. 既存受信ルールを取得するためのフィルター詳解
調査結果として残したかったのはここ、53行目。
一旦フィルター処理している箇所を分解。
az network nsg rule list
--resource-group "RagSystem-dev"
--nsg-name "RagSystem-BastionNsg-dev"
--query "
[? # 1-1
priority < to_number('4096') # 1-2
&& # 1-3
direction == 'Inbound' # 1-4
]
|
reverse( # 2-1
sort_by( # 2-2
[], # 2-3
&priority # 2-4
)
)
|
[0].priority" # 3-1
-o tsv
以下3つのクエリーをパイプ(|)で繋いで実行する。
- 優先度が最大値(4096)より小さい受信ルールのレコード配列を抽出
- 抽出したレコード配列を優先度降順で並び替え
- 配列の先頭レコード(一番優先度が低い)のpriority値を取得
3-1. 優先度が最大より小さい受信ルールのレコード配列を抽出
- 1-1. 配列要素に対するクエリの実行を宣言
- 1-2. 配列内の”priority”という数値が、4096以下のレコード
この時、以下のようにすることができると考えたのだが、JMESPathでは数値をそのまま指定してもinvalid valueエラーとなる。
priority < 4096
数値の比較をどのように定義すれば良いのか、JMESPathのドキュメントには見つけることができず、あてずっぽうで「こうやればいけんじゃね?」的に探し当てたのが、この「文字列として定義しto_number関数で数値化して比較する」だった。ドキュメントとして明記されているサイトがわかる人、是非URLを教えてほしい。。
- 1-3. 配列要素に対するクエリーをAND条件で複数クエリーを組み合わせる
- 1-4. 配列内の”direction”に”Inbound”が指定されているレコード
ここまでで、NSGのルール配列の中から、優先度が4096未満、かつ受信ルールのレコードだけが抽出される。
3-2. 出したレコード配列を優先度降順で並び替え
- 2-1. reverse関数を用いて、レコードを逆順にする
- 2-2. sort_by関数を用いて、レコード全体をpriorityの昇順に並び替える
- 2-3. sort_by関数の処理対象は、配列全体
- 2-4. sort_by関数の並び替えのキーは”priority”の値
sort_by関数の中で、配列要素全体を指定しているが、これは元のJson全体ではなく、フィルター後にパイプで引き継がれた情報が対象。
3-3. 配列の先頭レコード(最も優先度が低いレコード)のpriority値を取得
- 3-1. 配列要素0番目(先頭=これまでの処理で一番優先度が低いレコードになっている)の”priority”値(優先度)のみを抽出
以上で、追加したいIPの優先度のベースとなる値を取得できた。
あとはスクリプトに記載した通り、この値に+10した値を、新たに追加する許可IPの優先度として、NSGの受信ルールに追加した。
4. JMESPathを使ったjsonデータのフィルタリングのまとめ
今回JMESPathについて少し詳細に調査しながら使ってみた。
言語仕様や関数の使い方は独特なものがあるものの、SQLもしくはKQLに慣れている人にはとっつきやすいかもしれないと感じた。
- 必要な条件でデータをフィルタリング
- (必要に応じて)必要な順番に並び替え
- 必要な情報を取得