はじめに
どこの企業でもセキュリティ対策のためのガイドラインやチェックリストなどがあると思うが、例えばこんな要件があるとする。
アカウントやユーザーの発行/変更/削除の記録を管理簿にて管理すること。
AWSではロールやグループを割り当てたIAMユーザーが発行される。
監査やコンプライアンスの観点からIAMユーザー管理の証跡を残すことは重要だ。1
今回はこのIAMユーザーの管理簿作成を自動化してみる。
ざっくり言うと
- IAM認証情報レポートを毎週一回メールで自動配信するようにした2
- CloudWatch/Lambda/SES/S3を組み合わせた
- 社内監査やコンプライアンスチェックのためにファイルを残すことが可能
- 具体的には下記のようなメールが送られてくる
対象とする人
- 堅めの企業でAWSのセキュリティを見ている人
- 特に社内のコンプライアンスチェックや監査目的でAWSのIAMユーザーのリストを作成する必要がある人
やること
- SESでメールアドレスを認証
- Lambda関数実行用のIAMロールの作成
- Lambda関数作成
やらないこと
- 各種アラートやチェックの自動化
- アクセスキーやパスワードが全然ローテーションされていないぞ!などの警告は他のサービスで実現可能なので、ここではやらない。
- Slackなどのチャットサービスへの通知
- 会社でSlack使えない場合があるため
※ここで並べたものは「やらなくていい」ではなくて「ほんとはやった方がいい」ものである。
IAM認証情報レポート
AWS アカウントの認証情報レポートの取得 - AWS Identity and Access Management
IAM認証情報レポートとはアカウントのすべてのユーザーと、ユーザーの各種認証情報 (パスワード、アクセスキー、MFA デバイスなど) のステータスがまとめられたものである。
ファイル自体はcsvファイルとして生成される。csvファイルには下記の情報が含まれる。3
列名 | 説明 |
---|---|
ユーザー | ユーザ名 |
arn | ユーザーの Amazon リソースネーム(ARN) |
user_creation_time | ユーザーが作成された日時 (ISO 8601 日付/時刻形式) |
password_enabled | ユーザーがパスワードを持っているかどうか |
password_last_used | AWS アカウントのルートユーザー または IAM ユーザーのパスワードを使用して最後に AWS ウェブサイトにサインインした日時 |
password_last_changed | ユーザーのパスワードが最後に設定された日時 |
password_next_rotation | 新しいパスワードを設定するようユーザーに求める日時 |
mfa_active | ユーザーに対して多要素認証 (MFA) デバイスが有効かどうか |
access_key_1_active | ユーザーがアクセスキーがACTIVEかどうか |
access_key_1_last_rotated | ユーザーのアクセスキーが作成または最後に変更された日時 |
access_key_1_last_used_date | AWS API リクエストの署名にユーザーのアクセスキーが直近に使用されたときの日付と時刻 |
access_key_1_last_used_region | アクセスキーが直近に使用された AWS リージョン |
access_key_1_last_used_service | アクセスキーを使用して最も最近アクセスされた AWS サービス |
cert_1_active | ユーザーのX.509 署名証明書がACTIVEかどうか |
cert_1_last_rotated | ユーザーの署名証明書が作成または最後に変更された日時 |
IAM認証情報レポートはマネジメントコンソールの下記の画面からダウンロード可能である。
ただし、これを定期的に行うのは人間のやる作業ではないため自動化する。
SESでメールアドレスを認証する
単純なメール通知ならSNSで十分だが、今回はcsvファイルを添付したいのでSESを利用して送信用&受信用のメールアドレスを登録する必要がある。
詳細な手順は省略するが
- 左上の
Verify a New Email Address
ボタンからメールアドレスを登録 - 受信したメールに記載されたリンクへアクセスする
これでメールアドレスの認証は完了する。Statusが[verified]になればOK。
SESは対応しているリージョンが少ないので注意が必要である。2019年時点では下記の3リージョンのみ利用可能。
- us-east-1(バージニア北部)
- us-west-2(オレゴン)
- eu-west-1(アイルランド)
IAMロールを作成する
Lambda関数に与えるIAMロールを事前に用意しておく。
IAMのコンソール画面の「このロールを使用するサービス」からLambda関数を選択。
今回追加で必要な権限はこの3つ。
- AmazonSESFullAccess
- AmazonS3FullAccess
- IAMReadOnlyAccess
信頼されたエンティティとしてLambdaが選択されているのを確認したらロールの作成
を行う。
Lambda関数を設定
とりあえず関数名とランタイムを指定して関数の作成
をクリック。ロールはあとで指定する。
ここでは下記の手順でLambda関数を作成する。
- ①Lambda関数コードを入力
- ②環境変数の入力
- ③実行ロールの設定
- ④CloudWatchEventの設定
① Lambda関数コードを入力
下記のコードをインラインエディタに貼り付ける。
import boto3
import re
import time
import datetime
import os
from botocore.exceptions import ClientError
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
AWS_S3_BUCKET_NAME = os.environ["AWS_S3_BUCKET_NAME"]
ACCOUNT_ID = os.environ["ACCOUNT_ID"]
SENDER = os.environ["SENDER"]
RECIPIENT = os.environ["RECIPIENT"]
AWS_REGION = "us-east-1"
SUBJECT = "IAMユーザーWeeklyReport"
# The character encoding for the email.
CHARSET = "utf-8"
# The email body for recipients with non-HTML email clients.
BODY_TEXT = "IAMユーザー管理リスト\n作成日時:{}".format(datetime.datetime.today())
# The HTML body of the email.
BODY_HTML = """\
<html>
<head></head>
<body>
<p>今週のIAMユーザー管理リスト</p>
<p>アカウントID:{}</p>
<p>作成日時:{}</p>
<p>ファイル格納先:{}</p>
</body>
</html>
""".format(ACCOUNT_ID, datetime.datetime.today(), AWS_S3_BUCKET_NAME)
def lambda_handler(event, context):
# 認証情報レポートを生成
iam = boto3.client('iam')
response = iam.generate_credential_report()
print(response)
# レポート作成が完了するまで待機
while(response['State'] != 'COMPLETE'):
time.sleep(1)
continue
result = iam.get_credential_report()
print(result)
# 取得日時からファイル名を生成
pattern = r"[0-9]{4}-[0-9]{2}-[0-9]{2}"
date = re.search(pattern, str(result['GeneratedTime'])).group(0)
fname = "{}_{}_iam_credential_report.csv".format(date.replace("-", ""), ACCOUNT_ID)
ATTACHMENT = "/tmp/"+fname
# S3へ保存
s3 = boto3.resource('s3')
bucket = s3.Bucket(AWS_S3_BUCKET_NAME)
bucket.put_object(Key='iam/'+fname, Body=result["Content"])
# tmpへ一時保存
with open('/tmp/'+fname, "wb") as f:
f.write(result["Content"])
# メール配信
# Create a new SES resource and specify a region.
ses = boto3.client('ses', region_name=AWS_REGION)
# Create a multipart/mixed parent container.
msg = MIMEMultipart('mixed')
# Add subject, from and to lines.
msg['Subject'] = SUBJECT
msg['From'] = SENDER
msg['To'] = RECIPIENT
# Create a multipart/alternative child container.
msg_body = MIMEMultipart('alternative')
# Encode the text and HTML content and set the character encoding. This step is
# necessary if you're sending a message with characters outside the ASCII range.
textpart = MIMEText(BODY_TEXT.encode(CHARSET), 'plain', CHARSET)
htmlpart = MIMEText(BODY_HTML.encode(CHARSET), 'html', CHARSET)
# Add the text and HTML parts to the child container.
msg_body.attach(textpart)
msg_body.attach(htmlpart)
# Define the attachment part and encode it using MIMEApplication.
att = MIMEApplication(result["Content"])
# Add a header to tell the email client to treat this part as an attachment,
# and to give the attachment a name.
att.add_header('Content-Disposition', 'attachment', filename=os.path.basename(ATTACHMENT))
# Attach the multipart/alternative child container to the multipart/mixed
# parent container.
msg.attach(msg_body)
# Add the attachment to the parent container.
msg.attach(att)
try:
# Provide the contents of the email.
response = ses.send_raw_email(
Source=SENDER,
Destinations=[
RECIPIENT
],
RawMessage={
'Data': msg.as_string(),
},
# ConfigurationSetName=CONFIGURATION_SET
)
# Display an error if something goes wrong.
except ClientError as e:
print(e.response)
print(e.response['Error']['Message'])
else:
print("Email sent! Message ID:"),
return (response['ResponseMetadata']['RequestId'])
やっていることは大雑把に言うと下記の通り。
- iam_credential_reportの生成、取得
- 取得したファイルをS3へ保存
- SESによりファイルを添付したメールを送信
② 環境変数を入力
この関数ではメールアドレスやアカウントIDなどは環境変数で管理している。Lambda関数内に直接書き込んでも動作するが、コードを直接変更するのはトラブルの元なので環境変数として外部に切り出す。
こうしておけば別のアカウントに移植する際にいちいちコードを書き換える必要がない。CloudFormationでLambdaをデプロイする場合などはコードをzipファイルにまとめる際などは非常に便利。
環境変数の設定は下記のようにコンソール上から行うことができる。4
③ 実行ロールの設定
既存のロールを使用する
を選択し、先ほど作成したロールを割り当てる。
④CloudWatchEventの設定
毎週一回レポートを作成する場合はLambda関数のトリガーを追加
をクリックして下記の通り設定すればOK。cronによるスケジュール設定はUTCで行う必要があるので注意。
参考にそのほかセキュリティ周りの作業を自動化する場合に使いそうな設定パターンをまとめておく。
時間帯 | Cron式 | 利用例 | ルール名 |
---|---|---|---|
毎日0:00(JST) | 0 15 * * ? * |
S3のデータ整形、ログ収集など | daily_task |
毎週月曜日の0:00(JST) | 0 15 ? * SUN * |
1週間のセキュリティレポート作成など | weekly_task |
毎月1日の0:00(JST) | 0 15 1 * ? * |
月間のコスト請求レポート作成など | monthly_task |
平日の夜22:00(JST) | 0 13 ? * MON-FRI * |
インスタンスの設定ミス、落とし忘れなどのチェック | closing_task |
その他ポイントとしては
- 必要に応じてメールの件名や文面を編集する
-
AWS_REGION
はSESを設定した時のリージョンを指定する -
tmp
ディレクトリに一時的に添付ファイルを保存→メール送信 - IAMユーザーが多いと処理に時間がかかるので時間は適当に設定する
など。
おわりに
とりあえずこれ動かしておけば毎週必ずレポートが送られてくる。
「アカウントの管理しっかりやってるよね?」と言われたときにすぐに出せるようにしておくと安心。
参考
Lambda・SESを使って、添付ファイル付きメールを送信する – サーバーワークスエンジニアブログ
E メールの高度なパーソナライズ - Amazon Simple Email Service
AWS SDKs を使用して raw E メールを送信する - Amazon Simple Email Service
-
「AWS Configで一発だろ」というツッコミはごもっともだが、非エンジニアが各種チェック作業を行う場合を考えると、ローカルファイルとして置いておくのが一番楽だと思われる。誰でも扱いやすい形式で情報を扱うことが、社内全体幸福最大化につながる。 ↩
-
応用すればコストレポートやS3バケット内のファイルの自動配信なども可能。 ↩
-
アクセスキーと証明書が複数ある場合はその分だけ列が増える。 ↩
-
CLIなどで登録することも可能。 ↩