さくらのVPSのAPIを利用して設定変更してみた(パケットフィルター)
概要
2023年3月28日にさくらVPSのAPIが公開されています。
自宅の動的グローバルIPを自動でさくらVPSのパケットフィルタ(セキュリティグループ的なもの)に登録できないかなと思って調べたら、ちょうど先月その機能が実装されていたようなので、検証がてらAPIを操作していきたいと思います。

API
APIの概要は以下となります。
詳細なAPIのドキュメントは以下です。
更新履歴を見ると順次機能が追加されているようで、サーバの起動停止や情報取得の他にもNFSやスイッチなどをいじったりできるようです。
ただし、現状APIから新規サーバ追加はできないようです。
これは、さくらVPSはAWSなどと違い従量課金ではないため、サーバを新規追加するタイミングで支払い処理を行わないといけないことが原因だと思われます。
また、常識的な利用方法であれば問題ないとは思いますが、現状のレートリミットは各アカウントごとに100リクエスト/毎分となっています。
やりたいこと
VPSサーバの一部ポート(22, 3389等)を自宅IPからのみ接続できるようにする。
ただし、自宅のグローバルIPが固定でなく変動する可能性があるため、IPが変更された場合にパケットフィルターの設定を変更する。
自身のグローバルIPを確認し、パケットフィルターに設定されているIPと差異があれば更新するような処理を実装しようかと思います。
なお、自身のグローバルIPはOpenDNSを利用して取得します。
dig +short myip.opendns.com @resolver1.opendns.com
設定
以下のシェルスクリプトを作成しました。
RULE_NAMEに設定した名前でパケットフィルタのルールを作成していきます。
十分に検証をしていないので、ご利用は自己責任でお願いいたします。
API実行シェルスクリプト
#!/bin/bash
=== 設定 ===
API_TOKEN="APIトークン"
SERVER_ID="サーバID"
API_BASE="https://secure.sakura.ad.jp/vps/api/v7/servers/${SERVER_ID}/packet-filter"
LOGFILE="./packet_filter.log"
RULE_NAME="api_setting"
#=== 1. 自身のグローバルIPを取得 ===
MYIP=$(dig +short myip.opendns.com @resolver1.opendns.com)
if [ -z "$MYIP" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') [ERROR] グローバルIP取得失敗" | tee -a "$LOGFILE"
exit 1
fi
echo "$(date '+%Y-%m-%d %H:%M:%S') [INFO] グローバルIP:$MYIP" | tee -a "$LOGFILE"
#=== 2. パケットフィルタ取得 ===
FILTER_JSON=$(curl -s -H "Authorization: Bearer ${API_TOKEN}" "$API_BASE")
if [ -z "$FILTER_JSON" ] || echo "$FILTER_JSON" | grep -q '"not_found"'; then
echo "$(date '+%Y-%m-%d %H:%M:%S') [ERROR] パケットフィルタ取得失敗" | tee -a "$LOGFILE"
exit 1
fi
echo -e "$(date '+%Y-%m-%d %H:%M:%S') [INFO] 既存パケットフィルタ\n$FILTER_JSON" | tee -a "$LOGFILE"
#=== 3. ルール確認 ===
CURRENT_IP=$(echo "$FILTER_JSON" | jq -r --arg rn "$RULE_NAME" \
'.rules[]? | select(.name==$rn) | .source_ip')
if [ "$CURRENT_IP" == "$MYIP" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') [INFO] 既存IPと一致のため更新なし: ${MYIP}" | tee -a "$LOGFILE"
exit 0
elif [ -n "$CURRENT_IP" ] && [ "$CURRENT_IP" != "$MYIP" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') [INFO] IP変更: 旧=${CURRENT_IP} -> 新=${MYIP}" | tee -a "$LOGFILE"
fi
#=== 4. ルール編集 ===
NEW_RULES=$(echo "$FILTER_JSON" | jq \
--arg ip "$MYIP" \
--arg rn "$RULE_NAME" \
'if .rules then
if any(.rules[]; .name==$rn) then
# 既存のルールを上書き
.rules |= map(if .name==$rn then
.source_ip=$ip
| .source_ip_prefix_length=32
| .protocol="tcp"
| .ports=[22,3389]
else . end)
else
# ルールが存在しなければ追加
.rules += [{
"name":$rn,
"source_ip":$ip,
"source_ip_prefix_length":32,
"protocol":"tcp",
"ports":[22,3389]
}]
end
else
# rules自体が存在しない場合
.rules=[{
"name":$rn,
"source_ip":$ip,
"source_ip_prefix_length":32,
"protocol":"tcp",
"ports":[22,3389]
}]
end')
#=== 5. パケットフィルタ更新 ===
UPDATE_RESULT=$(echo "$NEW_RULES" | curl -s -X PUT \
-H "Authorization: Bearer ${API_TOKEN}" \
-H "Content-Type: application/json" \
-d @- "$API_BASE")
echo -e "$(date '+%Y-%m-%d %H:%M:%S') [INFO] 更新完了\n$UPDATE_RESULT" | tee -a "$LOGFILE"
if echo "$UPDATE_RESULT" | grep -q '"invalid"'; then
echo "$(date '+%Y-%m-%d %H:%M:%S') [ERROR] パケットフィルタ更新失敗" | tee -a "$LOGFILE"
exit 1
fi
echo "$(date '+%Y-%m-%d %H:%M:%S') [INFO] パケットフィルタ更新成功: ${MYIP}" | tee -a "$LOGFILE"
exit 0
RULE_NAME名のルールが存在しない場合、新規でパケットフィルタのルールを作成します。
すでに同名のルールが存在する場合には、
➀取得したグローバルIPがルールに設定されているIPと一致する場合、何もしないで終了
➁取得したグローバルIPがルールに設定されているIPと違う場合、取得したグローバルIPでルールを上書きする
という処理になっています。
ルールがない状態での実行結果は以下の通りです。
$ bash sakura_api.sh
2025-10-27 23:08:32 [INFO] グローバルIP:xxx.xxx.xxx.xxx
2025-10-27 23:08:32 [INFO] 既存パケットフィルタ
{"enabled":true,"rules":[{"name":"SAMPLE1","protocol":"tcp","ports":["80","443"],"source_ip":"0.0.0.0","source_ip_prefix_length":0},{"name":"SAMPLE2","protocol":"tcp","ports":["22"],"source_ip":"yyy.yyy.yyy.yyy","source_ip_prefix_length":32}
2025-10-27 23:08:33 [INFO] 更新完了
{"enabled":true,"rules":[{"name":"SAMPLE1","protocol":"tcp","ports":["80","443"],"source_ip":"0.0.0.0","source_ip_prefix_length":0},{"name":"SAMPLE2","protocol":"tcp","ports":["22"],"source_ip":"yyy.yyy.yyy.yyy","source_ip_prefix_length":32},{"name":"api_setting","protocol":"tcp","ports":["22","3389"],"source_ip":"xxx.xxx.xxx.xxx","source_ip_prefix_length":32}]}
2025-10-27 23:08:33 [INFO] パケットフィルタ更新成功: xxx.xxx.xxx.xxx
ルールは存在するが、IPが不一致の場合の結果は以下の通りです。
$ bash sakura_api.sh
2025-10-27 23:04:42 [INFO] グローバルIP:xxx.xxx.xxx.xxx
2025-10-27 23:04:42 [INFO] 既存パケットフィルタ
{"enabled":true,"rules":[{"name":"SAMPLE1","protocol":"tcp","ports":["80","443"],"source_ip":"0.0.0.0","source_ip_prefix_length":0},{"name":"SAMPLE2","protocol":"tcp","ports":["22"],"source_ip":"yyy.yyy.yyy.yyy","source_ip_prefix_length":32},{"name":"api_setting","protocol":"tcp","ports":["22","3389"],"source_ip":"zzz.zzz.zzz.zzz","source_ip_prefix_length":32}]}
2025-10-27 23:04:42 [INFO] IP変更: 旧=zzz.zzz.zzz.zzz -> 新=xxx.xxx.xxx.xxx
2025-10-27 23:04:44 [INFO] 更新完了
{"enabled":true,"rules":[{"name":"SAMPLE1","protocol":"tcp","ports":["80","443"],"source_ip":"0.0.0.0","source_ip_prefix_length":0},{"name":"SAMPLE2","protocol":"tcp","ports":["22"],"source_ip":"yyy.yyy.yyy.yyy","source_ip_prefix_length":32},{"name":"api_setting","protocol":"tcp","ports":["22","3389"],"source_ip":"xxx.xxx.xxx.xxx","source_ip_prefix_length":32}]}
2025-10-27 23:04:44 [INFO] パケットフィルタ更新成功: xxx.xxx.xxx.xxx
既存ルールとIPが一致した場合には何も起こりません。
$ bash sakura_api.sh
2025-10-27 23:10:15 [INFO] グローバルIP:xxx.xxx.xxx.xxx
2025-10-27 23:10:15 [INFO] 既存パケットフィルタ
{"enabled":true,"rules":[{"name":"SAMPLE1","protocol":"tcp","ports":["80","443"],"source_ip":"0.0.0.0","source_ip_prefix_length":0},{"name":"SAMPLE2","protocol":"tcp","ports":["22"],"source_ip":"yyy.yyy.yyy.yyy","source_ip_prefix_length":32},{"name":"api_setting","protocol":"tcp","ports":["22","3389"],"source_ip":"xxx.xxx.xxx.xxx","source_ip_prefix_length":32}]}
2025-10-27 23:10:15 [INFO] 既存IPと一致のため更新なし: xxx.xxx.xxx.xxx
コンソール上から確認すると、RULE_NAMEで設定した"api_setting"というルールが作成されていました。

まとめ
さくらVPSのAPIで無事パケットフィルタ設定の更新ができました。
なお、最初に検証した際(2025/10/26)にはパケットフィルタの情報取得APIがうまく動作しませんでした。
$ curl -H 'Authorization: Bearer APIトークン' 'https://secure.sakura.ad.jp/vps/api/v7/servers/サーバID/packet_filter'
<!doctype html>
<html lang="en">
<head>
<title>Not Found</title>
</head>
<body>
<h1>Not Found</h1><p>The requested resource was not found on this server.</p>
</body>
</html>
サポートに問い合わせをしたところ、APIドキュメントの記載が間違っていたとのことで、実際にはURL末尾がpacket-filterとなるところをpacket_filterと誤記してありました。

APIドキュメントは現在修正中とのことなので数日中には訂正されるものかと思われます。
...

(一ヵ月以上放置されるくらいには誰もAPI触ってないのだろうか、、、)
Discussion