お久しぶりです。株式会社協栄情報システム3部所属の寺尾です。

今回はEC2に特定のアクセス元からアクセス可能な時間を制限してみたいと思います。

これをどうやって実現するのかというと、以下の手順で実現できます

  1. セキュリティグループをアタッチ・デタッチする Lambda を用意します。
  2. そして特定のアクセス元を許可するセキュリティグループを用意します。
  3. そのセキュリティグループを EventBridge (CloudWatch Events) から時間を指定して Lambda よりアタッチ・デタッチします。

今回はPython(3.9)を利用してEC2にSGを自動アタッチ・デタッチしていきたいと思います。
それではよろしくお願いします。

Lambdaとロールの作成

1.まずLambdaのためのIAMロールを作成します。
今回作成するロールの設定は以下になります。

設定項目 設定値 理由
名前 Auto_SG_Role 入力内容は任意
信頼されたエンティティ Lambda Lambdaで利用するため
IAMポリシー AmazonEC2FullAccess EC2を操作するため
IAMポリシー AWSLambdaBasicExecutionRole Lambdaのログを書き込むため
Name Auto_SG 入力は任意

2.Lambda関数を作成しましょう。
今回作成するLambda関数の設定は以下になります。

設定項目 設定値 理由
関数名 Auto_SG 入力内容は任意
作成方法 一から作成 既存の例を利用しないため
ランタイム Python 3.9 Python 3 の最新版を利用
デフォルトの実行ロールを変更 既存のロールを使用する 先ほどのロールを利用
既存のロール Auto_SG_Role Lambdaのログの書き込み権限と
EC2の全ての権限を付与するため

これでlambda関数までは用意したので次にコードを用意します。

コードの作成

コードの概要

まず以下が完成したコードになります。

import boto3

def Get_current_sg_list(EC2ID):
    ec2 = boto3.resource('ec2')
    instance = ec2.Instance(EC2ID)

    current_sg_list = [i['GroupId'] for i in instance.security_groups] #json形式のデータからSGIDを抽出

    return current_sg_list

def lambda_handler(event, context):
    print(f'入力値 {event}')
    ec2 = boto3.resource('ec2')

    if event['add']: #SGを追加する場合
        for instance_id in event['EC2List']:
            current_sg_list = Get_current_sg_list(instance_id)
            print(f'{instance_id}の現在のSGは{current_sg_list}')
            after_sg_list = current_sg_list + event['SGList'] #更新後のSGを求める
            after_sg_list = list(set(after_sg_list)) #集合にして被りを排除する
            print(f'{instance_id}の更新後のSGは{after_sg_list}')

            instance = ec2.Instance(instance_id)
            response = instance.modify_attribute(Groups = after_sg_list)
            print(response)

    else: #SGを削除する場合
        for instance_id in event['EC2List']:
            current_sg_list = Get_current_sg_list(instance_id) 
            print(f'{instance_id}の現在のSGは{current_sg_list}')
            after_sg_list = set(current_sg_list) - set(event['SGList']) #集合にしてリスト同士で引き算する
            after_sg_list = list(after_sg_list) #更新後のSGを求める
            print(f'{instance_id}の更新後のSGは{after_sg_list}')

            instance = ec2.Instance(instance_id)
            response = instance.modify_attribute(Groups = after_sg_list)
            print(response)

    return

このコードは以下のようなjsonファイルを引数として(def lambda_handler のeventとして)受け取り、セキュリティグループのアタッチ・デタッチを実現します。

{
  "add": true,
  "EC2List": [
    "i-xxxxxxxxxxxxxxxxx",
    "i-xxxxxxxxxxxxxxxxx"
  ],
  "SGList": [
    "sg-xxxxxxxxxxxxxxxxx"
  ]
  }
入力項目 入力内容
add true もしくは false を入力し、セキュリティグループをアタッチ(true)するのかデタッチ(false)するのかを指定します
EC2List アタッチ・デタッチ対象となるEC2インスタンスのインスタンスIDをリスト形式で入力します
SGList アタッチ・デタッチするセキュリティグループのセキュリティグループIDをリスト形式で入力します

コードの構造

さて、このコードが何を入力するとアタッチ・デタッチを実現するのかについては説明したので、このコードがどういった構造になっているかを説明したいと思います。

まずこのコードは以下の二つのメソッドに分かれます。

  1. def lambda_handler
  2. def Get_current_sg_list

def lambda_handler は関数が実行されたときに実装されるメソッドです。
まずはこちらから実行されます。

def Get_current_sg_list はEC2インスタンスのIDを引数として受け取り、そのインスタンスのセキュリティグループを取得するメソッドになります。
def lambda_handler の中で呼び出して利用します。

この二つのメソッドを以下のようなフローで実行します。

  1. def lambda_handlerを実行
  2. boto3のインスタンスを取得
  3. ‘add’の値がtrueかどうかを判断し、分岐
  4. trueなら"EC2List"のインスタンスIDの数まで以下をループ
    1. def Get_current_sg_list を利用して"インスタンスID"の現在のセキュリティグループリストを取得
    2. 現在のセキュリティグループリストと"SGList"を足し合わせ、被りを排除して更新後のセキュリティグループリストを取得
    3. "インスタンスID"のセキュリティグループを更新後のセキュリティグループリストと同じように変更する
  5. trueでないなら"EC2List"のインスタンスIDの数まで以下をループ
    1. def Get_current_sg_list を利用して"インスタンスID"の現在のセキュリティグループリストを取得
    2. 現在のセキュリティグループリストから"SGList"を集合にしてから引き算し、更新後のセキュリティグループリストを取得
    3. "インスタンスID"のセキュリティグループを更新後のセキュリティグループリストと同じように変更する

以上のような形になっています。

リスト内包括表記

また、def Get_current_sg_listでは以下のようにリスト内包括表記を利用していますので少し触れます。

    current_sg_list = [i['GroupId'] for i in instance.security_groups]

一度リスト内包括表記を利用せずに記述したのですが、以下のように長くなってしまうので基本的にはリスト内包括表記を利用するのがおすすめです。

    current_sg_list = []        
    for i in range(instance.security_groups):
        current_sg_list.append(i['GroupId'])

list = [式 for 任意の変数名 in イテラブルオブジェクト]のように記載することで簡潔に表記できるようになります。
これはjson形式を頻繁に扱うことになるLambdaの開発では頻繁に活用する機会があるかと思います。

詳しくは以下をご参照下さい。
Pythonリスト内包表記の使い方

また、下記のようにmap関数とLambda式を利用することで記述することもできますが、リスト内包括表記の方が奇麗な上に高速なので非推奨です。

current_sg_list = list(map(lambda i : i['GroupId'] ,instance.security_groups ))  #map関数とLambda式を利用した場合のコード

boto3の利用方法については以下をご確認ください。
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#instance

EventBridge (CloudWatch Events)から起動

以下のように好きな時間のEventBridge (CloudWatch Events)を用意し、実際に起動してみましょう。

file
file

これで特定の時間のみセキュリティグループをデタッチするようにすれば、EC2に特定のアクセス元からアクセス可能な時間を制限することができます。

おわりに

ここまで読んでいただきありがとうございました。
間違いなどあればぜひご指摘ください。
この記事が誰かの助けとなれば幸いです。

Last modified: 2021-10-05

Author