🐼

Amazon EC2 インスタンスへのIPv4 の自動割り当てを自動で削除する

に公開

先日、EC2 インスタンスに自動割り当てされたパブリック IPv4 アドレスの動的削除と追加が可能になりました。
https://aws.amazon.com/jp/about-aws/whats-new/2024/04/removing-adding-auto-assigned-public-ipv4-address/

それに伴い、本記事では、EC2 にデフォルトで割り当てられていたパブリック IPv4 アドレスを自動削除してみたいと思います。EC2 インスタンスにパブリック IP を割り当てるのを絶対許すまじという気持ちでやっていきます。

構成

今回は、 AWS Config → Amazon EventBridge → AWS Lambda という構成でやってみました。実は AWS Config → AWS Systems Manager という構成でも Automation runbook を使ってできて、そちらの方がスマートではあるのですが、今回は Lambda を使った方法を紹介したいと思います。

AWS Config の設定

まずは Config を設定します。"ec2-instance-no-public-ip" というマネージドルールがあるのでそちらを使います。ただしこのマネージドルールの評価モードはプロビジョニングされたリソースの評価を有効にするもので、インスタンスが起動した後にしか評価を行いません。そのため、起動時に修正したい場合は自分でルールを用意する必要があります。一方で、起動後の EC2 を対象にしたい場合はこのルールが適しているかと思います。

Config の設定はこれだけです。すごい簡単ですね。

AWS Lambda を作成する

実はここにちょっとしたハマりポイントがあります。今回、Python を選択していますが、 Lambda で Python を使って API を叩こうとすると一般的に Boto3 というライブラリを使うことになります。 Lambda では標準でこれが入っているのですが、今回のように API の仕様が変わった直後だと、 Boto3 が対応していなかったり、あるいは Lambda 自体の Boto3 のバージョンが若干古いため、エラーがでます。

[API 仕様](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_ModifyNetworkInterfaceAttribute.html) 通りにリクエストを送ってみると、

実際のエラー
Unknown parameter in input: "AssociatePublicIpAddress"
と言われちゃっているのがわかるかと思います。

ということで、まず Lambda の Boto3 のバージョンはいくつか調べてみましょう。
Lambda で下記を実行してみます。

これを実行すると 1.34.42 と表示されました(2024/5/14 時点)
しかしながら、この時点の Boto3 の最新バージョンは 1.34.103 です。そして、この EC2 インスタンスに自動割り当てされたパブリック IPv4 アドレスの動的削除と追加 の機能は、1.34.91 で追加されていることがわかります。

このままでは実行できないことがわかったので、Lambda Layer を作る必要があります。

ちなみに余談ですが、 Systems Manager で YAML で API 呼び出しをしても同様の結果になったので、Systems Manager も最新の API に追従するまでは若干時間がかかるようです。
そのため、新しい仕様が実装されたら数日は様子を見る必要があることを覚えておきましょう。

Lambda Layer の作成方法については今回は割愛しますが、作成が完了したら下記のように実装します。

import json
import boto3

def modify_network_interface_attribute(network_interface_id):
    ec2 = boto3.client('ec2')

    try:
        response = ec2.modify_network_interface_attribute(
            NetworkInterfaceId=network_interface_id, # 対象の Network Interface ID を指定する
            AssociatePublicIpAddress=False # False でパブリック IPv4 アドレスが削除される
        )
        print("Network Interface Modified Successfully:")
        print(response)
    except Exception as e:
        print("An error occurred:", e)



def lambda_handler(event, context):
    
    detail = event['detail']
    # network_interface_id = detail['configurationItem']['configuration']['networkInterfaceId']
    
    network_interface_ids = []
    
    # 変更されたリソースのリストを取得
    result = detail.get('newEvaluationResult')
    identifier = result.get('evaluationResultIdentifier')
    configuration_item = identifier.get('evaluationResultQualifier')
    
    # リソースタイプが EC2 インスタンスか確認
    if configuration_item and configuration_item['resourceType'] == 'AWS::EC2::Instance':
        instance_id = configuration_item['resourceId']
        
        ec2_client = boto3.client('ec2')
        
        # インスタンスの詳細を取得
        response = ec2_client.describe_instances(InstanceIds=[instance_id])
        reservations = response['Reservations']
        
        # すべてのネットワークインターフェースIDの auto assign を停止する
        for reservation in reservations:
            for instance in reservation['Instances']:
                for interface in instance['NetworkInterfaces']:
                    network_interface_ids.append(interface['NetworkInterfaceId'])
                    modify_network_interface_attribute(interface['NetworkInterfaceId'])
    
    print("Network Interface IDs: ", network_interface_ids)

    return {
        'statusCode': 200,
        'body': json.dumps('Success!!')
    }

結構雑に書いていて、リソースタイプが EC2 インスタンスかどうかに関わらず Success!! と返しちゃったり甘い部分もあるのですが、パブリック IPアドレスを自動で削除するだけであればこれでできます。

Lambda のテスト

下記を使ってテストを行うことができます。適当に立てたインスタンス ID を resourceId に記載してからテストを実行してみてください。

{
  "version": "0",
  "id": "ID",
  "detail-type": "Config Rules Compliance Change",
  "source": "aws.config",
  "account": "[アカウントID]",
  "time": "2024-05-19T12:19:54Z",
  "region": "ap-northeast-1",
  "resources": [],
  "detail": {
    "resourceId": "[インスタンスID]",
    "awsRegion": "ap-northeast-1",
    "awsAccountId": "[アカウントID]",
    "configRuleName": "ec2-instance-no-public-ip",
    "recordVersion": "1.0",
    "configRuleARN": "arn:aws:config:ap-northeast-1:[アカウントID]:config-rule/config-rule-qx4cpe",
    "messageType": "ComplianceChangeNotification",
    "newEvaluationResult": {
      "evaluationResultIdentifier": {
        "evaluationResultQualifier": {
          "configRuleName": "ec2-instance-no-public-ip",
          "resourceType": "AWS::EC2::Instance",
          "resourceId": "[インスタンスID]",
          "evaluationMode": "DETECTIVE"
        },
        "orderingTimestamp": "2024-05-19T00:03:43.749Z"
      },
      "complianceType": "NON_COMPLIANT",
      "resultRecordedTime": "2024-05-19T12:19:53.922Z",
      "configRuleInvokedTime": "2024-05-19T12:19:53.696Z",
      "annotation": "This Amazon EC2 Instance uses a public IP."
    },
    "notificationCreationTime": "2024-05-19T12:19:54.417Z",
    "resourceType": "AWS::EC2::Instance"
  }
}

正常に動作すれば当該インスタンスの設定が下記のように変わっているはずです。

Amazon EventBridge の設定

最後に EventBridge のルールを作っていきましょう。

イベントパターンは下記のとおりです。 AWS Config がソースで、ec2-instance-no-public-ip の Compliance Change を元に Lambda を呼び出します。

{
  "source": ["aws.config"],
  "detail-type": ["Config Rules Compliance Change"],
  "detail": {
    "configRuleName": ["ec2-instance-no-public-ip"]
  }
}

ターゲットで AWS のサービス Lambda 関数を選択し、先ほど作成した Lambda 関数を選択します。

あとは改めてインスタンスを作成して、自動で修正がされているのを確認するだけです。
ちゃんと動作しているかどうかはモニタリングで確認するとよいでしょう。
もしうまく動いていない場合は Lambda 関数の中でログを出したり、Lambda 自体がちゃんと呼ばれているかどうかなどデバッグしてみてください。

まとめ

以外とあっさり EC2 からパブリックIPを削除することができましたが Systems Manager の Automation runbook を使った場合に比べるとシンプルではないですし(個人の主観です)、コーディングの知識がそれなりに必要になってきます。今回もざっくり書いていますけど、もっとしっかりエラーケースも拾うべきですし、そういうところにも気を遣う必要がありますよね。
次回は、Systems Manager の Automation runbook を使うとどのように書けるのか同じ課題でやっていきたいと思います。

Discussion