5分・タブ連打でConfigのカスタムルールが!?AWS RDK + Codewhisperer に9割方作ってもらうカスタムルールコーディング

本記事は、Japan Digital Design Advent Calendar 2023 の18日目の記事です。

adventar.org

三菱UFJフィナンシャル・グループ(以下MUFG)の戦略子会社であるJapan Digital Design(以下JDD)でSolution Architectをしている木美です。

本記事ではConfigのカスタムルールをなるべく他力本願で実装してみたいと思います。

「この統制・セキュリティルールをチェックできるAWS Configのマネージドルールはあるかなー。無いやん...」

そんなときに役立つ話です。

特にセキュリティ担当者でコーディングに不慣れな方におすすめの方法です。
セキュリティチェックリストのExcelシートを肥大させるのではなく、統制・セキュリティルールはコードで環境に実装してしまいましょう。

本記事を読むとできるようになること

  • 自分たちの組織に特有の統制・セキュリティルールへの準拠状況を継続的にチェックする仕組みを実装します
  • 継続的にチェックする仕組みは、AWS Configのカスタムルールで実装します
  • コーディングはなるべくAWS Config Rules Development Kit(AWS RDK)とAmazon CodeWhispererに頼ります

利用ツールの紹介

AWS Config

AWS Configは、AWSリソースの構成変更履歴を取得・記録できるサービスです。
Config Rulesを用いることでリソースの設定が組織のルールに準拠しているか継続的にチェックすることができます。

一般的な統制・セキュリティルールは「マネージドルール」としてAWSがチェックルールを事前定義してくれており、適用するだけで継続的に準拠状況をチェックできます。

自社特有の統制・セキュリティルールは「カスタムルール」として実装する必要があります。カスタムルールの実装方法には、次の2通りがあります。

  1. Lambda関数でチェックロジックを実装する方法
  2. Guardという宣言型言語を用いたポリシーチェック機能で実装する方法

今回はより柔軟性が高い「1. Lambda関数でチェックロジックを実装する方法」でカスタムルールを実装します。

AWS Config Rules Development Kit(AWS RDK)

Configのカスタムルール実装の支援ツールです。GitHub上で公開されています。

github.com

Configのカスタムルールを実装する際の共通的なコード実装やテスト、AWS環境へのデプロイを自動化・抽象化してくれます。

Amazon CodeWhisperer

Amazon CodeWhisperer は IDE 上でリアルタイムにコードの生成・提案をしてくれるサービスです。開発者が入力したコメントと既存のコードを見て、コーディング中にその場でAIがコードを順次生成・提案してくれます。

AWS RDK + CodeWhispererでConfigのカスタムルールを作っていく

実装する統制・セキュリティルール

今回は例として

「セキュリティグループのインバウンドルールにおいて22番ポートの開放は一切禁止(接続元のCIDRを制限していても)」

というルールをカスタムルールとして実装してみます。

インスタンスに対して、SSHでの接続は一切禁止し、AWS Systems Manger経由などで間接的に操作することを開発者に要求します。

事前準備

AWS RDKはGitHub上のREADMEで導入方法が説明されていますので、そちらに従って導入してください。
READMEに従って rdk init による環境の初期設定まで済ませておいてください。

今回はVS Codeでコードを開発します。VS Code拡張機能としてAWS ToolkitをインストールするとCodewhispererが利用可能になります。

まずはAWS RDKに共通的な部分を実装してもらう

次のコマンドを実行します。

rdk create CheckSGRule --runtime python3.11 --resource-types AWS::EC2::SecurityGroup --input-parameters '{"restrictedPort":22}'

--runtime はチェックロジックを動かすLambda関数のランタイムを指定します。

--resource-types で指定したリソースが今回のカスタムルールにおけるチェック対象リソースになります。Configが対象リソースの構成変更を検知してリアルタイムに設定をチェックします。resource typeはCloudFormationにおいてリソースを指定する際に使用する表記です。resource typeが分からなければ、Amazon Qさん(AIエージェント)に聞いてみてください。VS CodeAWS Toolkitを導入するとVS Code上で聞けます。

VS Code上でAmazon Qにresource typeを聞いてみた結果

--input-parameters はチェックロジックを実行するLambda関数に渡す引数です。今回は禁止するポート番号を引数として渡すようにしています。

実行すると、「あとはカスタムロジックだけここに書いてね」という状態まで持っていってくれます。 それ以外のカスタムルールの実装に必要なコードは自動で作成してくれているので、自社特有のロジック部分の実装のみに集中できます(正確には --input-parameters のvalidateも自分で実装する必要があります )

AWS RDKが生成した `CheckSGRule.py` の一部

あとはカスタムロジックを実装するだけですが、ここも他力本願で行きましょう。

Codewhispererにカスタムロジックを書いてもらう

以下のように、コメントでチェックしたい内容を書けばコードはどんどん生成・提案してくれますので、問題なければタブを連打すればOKです。ノッてくるとコメントさえCodewhispererが生成してくれます(最後の'COMPLIANT'を返却する部分等)

Codewhispererにカスタムロジックを書いてもらう様子

今回はCodewhispererが作ってくれたコードを尊重して手直しせずにそのまま進みます。

テストコードもCodewhispererに書いてもらう

AWS RDKの機能でカスタムロジックの挙動をローカル端末上でテストできます。 ローカルでテストするためのテストコードもAWS RDKが共通的な部分は作成してくれていて、テストケースだけを書けば良いようにしてくれています。

AWS RDKが生成したテストコード `CheckSGRule_test.py` の一部

テストケースもCodewhispererに書いてもらいましょう。

Codewhispererにテストコードを書いてもらう様子

大枠は生成してくれましたが、テストイベント(Config Ruleに構成変更内容を渡すイベント)の部分はかなり手直しが必要です。これはAWS RDKの機能で、

rdk sample-ci AWS::EC2::SecurityGroup

と実行すると、指定したリソースタイプ(今回だとセキュリティグループ)に対して構成変更を行った場合のサンプルイベントを確認することができます。サンプルイベントを参考にテストイベントを修正したコードは次の通りです(一部抜粋)

# セキュリティグループが指定したポート番号宛の許可ルールを含まない場合は'COMPLIANT'になることをテスト
    def test_COMPLIANT(self):
        # 80番ポート宛の許可ルールを含むセキュリティグループのinvoking_eventを作成
        invoking_event = """
            {
                "configurationItem":{
                    "configuration":{
                        "groupId":"XXXXXXXXXXX",
                        "ipPermissions":[{
                            "ipProtocol":"tcp",
                            "fromPort":80,
                            "toPort":80,
                            "userIdGroupPairs" : [{
                                "groupId":"XXXXXXXXXXX",
                                "groupName":"XXXXXXXXXXX"
                            }]
                        }]
                    },
                    "configurationItemStatus":"OK",
                    "resourceType":"AWS::EC2::SecurityGroup",
                    "resourceId":"sg-12345678",
                    "configurationItemCaptureTime":"xxx"
                },
                "messageType":"ConfigurationItemChangeNotification",
                "notificationCreationTime" : "xxx"
            }
        """
        rule_parameters = '{"restrictedPort":22}'
        event_to_return = build_lambda_configurationchange_event(invoking_event, rule_parameters)
        response = RULE.lambda_handler(event_to_return, {})
        resp_expected = []
        resp_expected.append(build_expected_response('COMPLIANT', 'sg-12345678', annotation='The port is not restricted.'))
        assert_successful_evaluation(self, response, resp_expected)

    # セキュリティグループが指定したポート番号宛の許可ルールを含む場合は'NOT_COMPLIANT'になることをテスト
    def test_NON_COMPLIANT(self):
        # 22番ポート宛の許可ルールを含むセキュリティグループのinvoking_eventを作成
        invoking_event = """
            {
                "configurationItem":{
                    "configuration":{
                        "groupId":"XXXXXXXXXXX",
                        "ipPermissions":[{
                            "ipProtocol":"tcp",
                            "fromPort":22,
                            "toPort":22,
                            "ipRanges":[{"cidrIp":"192.168.10.0/24"}]
                        }]
                    },
                    "configurationItemStatus":"OK",
                    "resourceType":"AWS::EC2::SecurityGroup",
                    "resourceId":"sg-12345678",
                    "configurationItemCaptureTime":"xxx"
                },
                "messageType":"ConfigurationItemChangeNotification",
                "notificationCreationTime" : "xxx"
            }
        """
        rule_parameters = '{"restrictedPort":22}'
        event_to_return = build_lambda_configurationchange_event(invoking_event, rule_parameters)
        response = RULE.lambda_handler(event_to_return, {})
        resp_expected = []
        resp_expected.append(build_expected_response('NON_COMPLIANT', 'sg-12345678', annotation='The port is restricted.'))
        assert_successful_evaluation(self, response, resp_expected)

# 以下省略

ローカルでテストしてAWS環境上に自動デプロイする

次のように実行することでローカル環境でチェックロジックをテストできます。

rdk test-local CheckSGRule

テストで問題なければ次のコマンドを実行し、AWS環境上にカスタムルールをデプロイします。

rdk deploy CheckSGRule  

このコマンドによりRDKがAWS環境上にConfigのカスタムルールを自動構築してくれます。 RDKがデプロイしたCloudFormation Stackを見てみると、Lambda関数、IAMロール、Config Ruleがデプロイされています。RDKはこのようなリソース作成も抽象化してくれているということです。

RDKがデプロイしたCloudFormation Stack

AWS環境上で動作確認してみる

動作確認用のセキュリティグループを2つ作りました。1つは22番ポート宛の許可ルールを含まないセキュリティグループ sg-010efa6b66e03674d 、もう1つは送信元CIDRを限定しているものの22番ポートを開放しているセキュリティグループ sg-042e4c369eac19c78 です。

Config Rulesの画面を見てみると、カスタムルールが作成・適用されています。検出結果を見ると、22番ポートを許可していない sg-010efa6b66e03674d は「準拠」、22番ポートを許可している sg-042e4c369eac19c78 は「非準拠」となっており、想定通りにルール違反を検出できています。

まとめ

今回はAWS RDK + Codewhispererに頼り切ってConfigのカスタムルールを作成してみました。 Codewhispererに寄り添ったコメントの書き方はコツがいりますが、慣れると結構いい感じにコードを生成してくれます。

統制・セキュリティ対応は「Job Zero」「Undifferentiated heavy lifting」(みんながまずやるべき差別化にならない重労働)と言われます。 だからこそ、統制・セキュリティルールはAWS RDK + Codewhispererを活用して効率的にコードとして環境に実装していくのはいかがでしょうか。

以上、JDDの木美でした。 最後までご覧いただきありがとうございました。




Japan Digital Design株式会社では、一緒に働いてくださる仲間を募集中です。カジュアル面談も実施しておりますので下記リンク先からお気軽にお問合せください。

japan-d2.com

この記事に関するお問い合わせはこちら

japan-d2.com

Technology & Development Div.
Yuta Kimi