日次でセキュリティグループの設定を更新するLambdaを作ってみた
「日次でとあるドメインからIPアドレスを取得し、セキュリティグループのアウトバウンドルールに登録する」という機能をLambdaで実装しました。 意外とネットにサンプルがなく、色々勉強になったのでブログ化します。
前提
- 「日次でとあるドメインから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 とは書き方が違う部分もあるので、以前のバージョンから移行する際には注意が必要だと思いました。