東京リージョン以外で不正にEC2を起動されないように自動で防ぐ

アクセスキーを不正利用された時に高額なEC2インスタンスをありとあらゆるリージョンで起動される事がほとんどです。 この記事では東京リージョン以外でEC2の閲覧以外を禁止する方法を紹介していますのでご一読ください。
2021.07.07

AWSアクセスキーセキュリティ意識向上委員会って何?

昨今、AWSのアクセスキーを漏洩させてしまうことが原因でアカウントへの侵入を受け、
多額の利用費発生・情報漏洩疑いなど重大なセキュリティ事案が発生するケースが実際に多々起きています。

そこで、アクセスキー運用に関する安全向上の取組みをブログでご紹介する企画をはじめました。

アクセスキーを利用する場合は利用する上でのリスクを正しく理解し、
セキュリティ対策を事前に適用した上で適切にご利用ください。

はじめに

漏洩した AWS のアクセスキーを使って行われる「悪意のある行動」として、新しく作成した EC2 インスタンスを使ってマイニングをさせる、というものがあります。
マイニングの効率上げるために多くのインスタンスを起動しようとするのですが、リージョン毎に起動できる上限数が決まっているため、利用できる全てのリージョンで EC2 を起動してきます。

不正利用をされないためには認証情報を漏洩させない事が一番重要なのですが、万が一漏洩した時に被害を最小限に抑えることを目的として、普段使う必要が無いリージョンで EC2 インスタンスを起動できなくしておこう、というのが本ブログ記事の目的となります。

IAM ポリシーを作成する

Effect について

「Deny(拒否)」で構成します。

Action について

「EC2:*」で全て止めることもできますが、EC2 の状況確認もできなくなってしまいます。
「*(ワイルドカード)」をうまく使い、状況確認以外はできないようにしましょう。

Resource について

全てのリソースを対象とするため「*」としておきます

Condition について

ここで「東京以外」を指定しています

完成したポリシー

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Deny",
            "Action": [
                "ec2:Release*",
                "ec2:Reboot*",
                "ec2:Attach*",
                "ec2:Update*",
                "ec2:Modify*",
                "ec2:Reset*",
                "ec2:Assign*",
                "ec2:Disable*",
                "ec2:Delete*",
                "ec2:Create*",
                "ec2:Register*",
                "ec2:Purchase*",
                "ec2:Request*",
                "ec2:Run*",
                "ec2:Copy*",
                "ec2:Revoke*",
                "ec2:Stop*",
                "ec2:Allocate*",
                "ec2:Restore*",
                "ec2:Start*",
                "ec2:Terminate*",
                "ec2:Import*",
                "ec2:Associate*",
                "ec2:Enable*"
            ],
            "Resource": "*",
            "Condition": {
                "StringNotEquals": {
                    "aws:RequestedRegion": [
                        "ap-northeast-1"
                    ]
                }
            }
        }
    ]
}

このポリシーをどう使うか?

この内容の IAM ポリシーを作成して IAM ユーザーにアタッチする方法とインラインポリシーとしてアタッチする方法が考えられます。

IAM ポリシーを作成してアタッチすることのメリットとして、ポリシーの修正がアタッチされている全ての IAM ユーザーに反映される点がありますが、意図していない IAM ユーザーの権限変更も行われてしまいます。
どちらの方法でも効果は同じですので、どちらを選ぶかはご自身の運用方法と照らし合わせてご判断ください。

また、どちらの方法であっても新しく作成した IAM ユーザーに対してポリシーのアタッチをしなければ意味がありません。

自動でポリシーをアタッチする

今回のポリシーをアタッチしなければ意味がありませんし、運用を続けていればアタッチし忘れる事もあります。
なので、今回は IAM ユーザーを作成した時に自動で先ほどのポリシーをインラインポリシーとしてアタッチする仕組みを紹介します。

利用するサービス

  • Amazon EventBridge
    • IAM ユーザーの作成を検知し、Lambda 関数を起動します
  • AWS Lambda
    • インラインポリシーを用意し、IAM ユーザーへアタッチします

Lambda 関数の準備

Lambda の基本的な権限(ログ出力等)と、IAM ユーザーへインラインポリシーをアタッチするために必要な権限を与えます。
前者は Lambda 関数を新規作成したタイミングで自動的に付与されますので、後者に必要なポリシーを作成します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "iam:PutUserPolicy",
            "Resource": "*"
        }
    ]
}

作成したら、Lambda にアタッチされている IAM Role にこのポリシーを追加しておいてください。

Lambda 関数はこちら

import boto3
from botocore.exceptions import ClientError
import json
import logging

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

# 共通変数定義・初期化
error_str = 'error'


def lambda_handler(event, context):

    # iam の操作をするための boto3 クライアントを作成
    obj_iam = boto3.client('iam')

    # event から作成された IAM ユーザー名を取得
    user_name = event['detail']['responseElements']['user']['userName']

    # 作成された IAM ユーザーに東京リージョン以外でのEC2操作を却下するインラインポリシーをアタッチ
    put_user_policy(obj_iam, user_name)

    # logger.info('attach policy complete.')
    return


# 東京リージョン以外でのEC2操作を却下するインラインポリシーをアタッチ
def put_user_policy(obj_iam, user_name):

    # 関数の戻り値の初期化
    return_str = ''

    # ポリシーの準備
    tmp_policy_name = "policy_disable-launch-outside-of-tokyo"
    tmp_policy_document = {
        "Version": "2012-10-17",
        "Statement": [{
            "Sid": "VisualEditor0",
            "Effect": "Deny",
            "Action": [
                "ec2:Release*",
                "ec2:Reboot*",
                "ec2:Attach*",
                "ec2:Update*",
                "ec2:Modify*",
                "ec2:Reset*",
                "ec2:Assign*",
                "ec2:Disable*",
                "ec2:Delete*",
                "ec2:Create*",
                "ec2:Register*",
                "ec2:Purchase*",
                "ec2:Request*",
                "ec2:Run*",
                "ec2:Copy*",
                "ec2:Revoke*",
                "ec2:Stop*",
                "ec2:Allocate*",
                "ec2:Restore*",
                "ec2:Start*",
                "ec2:Terminate*",
                "ec2:Import*",
                "ec2:Associate*",
                "ec2:Enable*"
            ],
            "Resource": "*",
            "Condition": {
                "StringNotEquals": {
                    "aws:RequestedRegion": [
                        "ap-northeast-1"
                    ]
                }
            }
        }]
    }

    # アタッチ
    try:
        tmp_response = obj_iam.put_user_policy(UserName=user_name, PolicyName=tmp_policy_name, PolicyDocument=json.dumps(tmp_policy_document))

    # 例外発生
    except ClientError as e:
        return_str = error_str
        logger.exception("{}".format(e))

    # 終了
    return return_str

EventBridge の設定

EventBridge では、IAM ユーザーの作成イベントのみをトリガーとして Lambda 関数を実行するよう、イベントパターンは以下のように設定します。

{
  "source": ["aws.iam"],
  "detail-type": ["AWS API Call via CloudTrail"],
  "detail": {
    "eventSource": ["iam.amazonaws.com"],
    "eventName": ["CreateUser"]
  }
}

その他に必要な設定

画像を参考に設定してください。
     

IAM ユーザーを作成してみた

以上で設定は完了したので、IAM ユーザーを作成したところ以下のようにインラインポリシーがアタッチされました。
  

EC2 を起動してみた

まず、バージニアリージョンで試します。
キャプチャ画像のようにエラーとなり起動に失敗しました。
  

次に東京リージョンで試します。
こちらは起動に成功しています。
  

最後に

いかがでしたでしょうか。 今回のブログ記事が、いつの間にか普段使わないリージョンで EC2 が起動されていた!ということを防ぐためのお役に立てれば幸いです。

参考ページ