前提
- 「日次でとあるドメインからIPアドレスを取得し、セキュリティグループのアウトバウンドルールに登録する」機能をLambdaで実装すること。
- 対象となるドメインのIPアドレスは、不定期に更新される。
- 対象となるセキュリティグループには、本Lambdaから登録したもの以外のアウトバウンドルールが存在する。(※その他のルールに影響があってはならない)
- Lambdaのランタイムは「Node.js 18.x」とする。
構築
① Lambdaの構築
処理フロー概要
ソース
import { EC2Client, AuthorizeSecurityGroupEgressCommand, DescribeSecurityGroupRulesCommand, RevokeSecurityGroupEgressCommand } from "@aws-sdk/client-ec2";
import * as dns from "dns";
const CLIENT = new EC2Client({ region: "ap-northeast-1" });
export const handler = async () => {
let domainName = "対象のドメイン名";
let securityGgroupId = "対象のセキュリティグループID";
let insertIpPermissions = [];
let deleteIpPermissions = [];
let ip = [];
// 1. ドメイン名からIPアドレスを取得
await dns.promises.lookup(domainName, { all: true }).then((result) => {
Object.keys(result).forEach((key) => {
insertIpPermissions.push(
{
"FromPort": 443,
"IpProtocol": "tcp",
"IpRanges": [
{
"CidrIp": result[key].address + "/32",
"Description": "XXXXXXXXXX",
}
],
"ToPort": 443
}
);
ip.push(result[key].address + "/32");
});
});
// 2. セキュリティグループからアウトバウンドルールを取得
let rules;
try {
const DESCRIBE_COMMAND_RESPONSE = await CLIENT.send(
new DescribeSecurityGroupRulesCommand({
Filters: [
{
Name: "group-id",
Values: [
securityGgroupId,
],
},
],
}),
);
rules = DESCRIBE_COMMAND_RESPONSE.SecurityGroupRules;
} catch (err) {
console.error(err);
return {
statusCode: err.$metadata.httpStatusCode,
body: JSON.stringify("セキュリティグループ情報取得処理でエラーが発生しました。"),
};
}
// 3. 1と2の取得結果を比較し、1で取得したIPアドレスがアウトバウンドルールに登録済かを判定
let insertFlg = false;
let rulesCount = 0;
Object.keys(rules).forEach((key) => {
if(rules[key].IsEgress) {
if(!ip.includes(rules[key].CidrIpv4) && rules[key].Description === "XXXXXXXXXX") {
insertFlg = true;
}
deleteIpPermissions.push(
{
"FromPort": 443,
"IpProtocol": "tcp",
"IpRanges": [
{
"CidrIp": rules[key].CidrIpv4,
"Description": "XXXXXXXXXX",
}
],
"ToPort": 443
}
);
rulesCount++;
} else if (rulesCount === 0) {
insertFlg = true;
}
});
// 4. 3の結果未登録であった場合、既存のアウトバウンドルールを削除し、1で取得したIPアドレスをアウトバウンドルールに登録する
if(insertFlg) {
if(deleteIpPermissions.length !== 0) {
try {
await CLIENT.send(
new RevokeSecurityGroupEgressCommand({
"GroupId": securityGgroupId,
"IpPermissions": deleteIpPermissions
}),
);
} catch (err) {
console.error(err);
return {
statusCode: err.$metadata.httpStatusCode,
body: JSON.stringify("アウトバウンドルール削除処理でエラーが発生しました。"),
};
}
}
try {
await CLIENT.send(
new AuthorizeSecurityGroupEgressCommand({
"GroupId": securityGgroupId,
"IpPermissions": insertIpPermissions
}),
);
} catch (err) {
console.error(err);
return {
statusCode: err.$metadata.httpStatusCode,
body: JSON.stringify("セキュリティグループ登録処理でエラーが発生しました。"),
};
}
return {
statusCode: 200,
body: JSON.stringify("アウトバウンドルールを更新しました。"),
};
} else {
return {
statusCode: 200,
body: JSON.stringify("IPアドレスは登録済です。"),
};
}
};
② トリガーの設定
LambdaのトリガーにEventBridgeを設定し、日次でLambdaを発火させるスケジュールを定義
ポイント
- ドメインからIPアドレスを取得する方法としてnslookupコマンドの使用を検討していたのですが、そもそもLambdaの実行環境にnslookupコマンドがなく、おとなしくライブラリを使用するようにしました。詳細な経緯については別の記事にまとめてあります。
Lambdaの実行環境で使用できるシェルコマンドについて調査しました - セキュリティグループから取得・登録するIPはCIDR形式でないと怒られるので、ドメインから取得したIPにコードの中で「/32」をつけています。
- 本Lambdaによって登録されたルールとそれ以外を区別する為に、登録時にDescriptionを設定するようにしました。
もうちょっとスマートな方法がないかSDKの仕様書を眺めていたのですが、それ以外の方法がなさそうでした、、、。
最後に
今回はネット上にサンプルが少なく、基本仕様書を見て実装したので思いのほか歯ごたえがありました。
AWS SDK for JavaScript v3の仕様書
また、Node.js18系は「AWS SDK for JavaScript v3」を使用する必要があり、v2 とは書き方が違う部分もあるので、以前のバージョンから移行する際には注意が必要だと思いました。