あしざわです。
Amazon Security LakeがEKS監査ログをサポートし、単一アカウントもしくは組織内のアカウントのEKS監査ログの自動収集が可能になりました。
AWS What’s newのアップデート記事はこちらです。
Amazon Secuirty Lakeとは?
AWS環境上へセキュリティログ用のデータレイクがマネージドに構築できるサービスです。
データレイクはS3で構築され、S3へのアクセス管理はLake Formationを利用します。
S3に保管されたログはOCSF(Open Cybersecurity Schema Framework)という統一されたスキーマに変換され、データが正規化された形で利用できます。
サービスのOrganizations統合を行えば組織内のアカウントのログ収集が自動化でき、さらにアカウント単位で取得対象のリージョンを設定できます。
これまでは以下のAWSログをネイティブにサポートしていました。
- CloudTrailログ、Route 53 Resolverクエリログ、Security Hubログ、VPCフローログ
カスタマイズによって外部のデータソースを取り込むこともできます。
EKS監査ログとは?
Amazon EKSは、AWS上に独自のKubernetes コントロールプレーンをインストールでき、サーバーの運用管理が楽になるマネージド型サービスです。
EKSには、EKSクラスターのコントロールプレーンのログをCloudWatch Logsに直接送る機能があります。
現在送信できるEKSクラスターコントロールプレーンのログタイプは以下です。
- APIサーバー
- 監査
- Authenticator
- コントローラーマネージャー
- スケジューラー
EKS監査ログは、クラスターに影響を与えた個々のユーザー、管理者、またはシステムコンポーネントについて記録します。
監査ログを調査することで、EKSクラスターに関する以下の情報が得られます。
- 何が起きたのか、いつ起こったのか、誰がそれを始めたのか、何のために起こったのか
- それはどこで観察されたのか、それはどこから始まったのか、それはどこへ向かっていたのか
事前知識のインプットは以上です。
やってみた
ここからは本記事で紹介するアップデート内容を実際に試してみた結果をレポートします。
検証環境のAWS組織の全体構成はこちらです。
- 東京リージョンをSecurity Lakeのロールアップリージョンに設定
- バージニア北部リージョンのログが統合され、東京リージョンのS3には2つのリージョンのログが保存されている
EKSクラスターを作成しますが、それらの監査ログの出力設定は行いません。Security Lakeの魅力の1つは個別のリソースでログの出力設定をしなくても問題ないところです。
それではここから各種設定を実施、EKS監査ログがどのように収集するところまでを検証してみましょう。
サービスロールの更新(場合によっては不要)
はじめにマネジメントコンソールでSecurity Lakeにアクセスしたところ、新しいバージョンのデータソースが利用可能という通知が出ていました。問題の表示もThe account does not have the required role permissions〜
と権限エラーになっていますね。
アップデート内容の利用に影響があるかもしれないので、まずはこちらを更新してみます。
今回の更新のデフォルトでは、サービスロールは新規のロールとして作成されるようです。
既存のサービスロールはAmazonSecurityLakeMetaStoreManager
でしたが、新しいものはAmazonSecurityLakeMetaStoreManagerV2
とバージョン2であることがわかるようにリネームされていました。
新バージョンのAmazonSecurityLakeMetaStoreManagerV2
から見た旧バージョンのAmazonSecurityLakeMetaStoreManager
との差分は以下です。
- CloudWatch LogsのロググループのARNが固定値ではなく正規表現に変更
- Glueのテーブルに対する書き込み権限が追加
- SQSのARNが固定値ではなく正規表現に変更
- S3バケットへの読み取り・書き込み権限が追加
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowWriteLambdaLogs",
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:CreateLogGroup"
],
"Resource": [
"arn:aws:logs:*:*:log-group:/aws/lambda/AmazonSecurityLake*",
"arn:aws:logs:*:*:/aws/lambda/AmazonSecurityLake*"
]
},
{
"Sid": "AllowGlueManage",
"Effect": "Allow",
"Action": [
"glue:CreatePartition",
"glue:BatchCreatePartition",
"glue:GetTable",
"glue:UpdateTable"
],
"Resource": [
"arn:aws:glue:*:*:table/amazon_security_lake_glue_db*/*",
"arn:aws:glue:*:*:database/amazon_security_lake_glue_db*",
"arn:aws:glue:*:*:catalog"
]
},
{
"Sid": "AllowToReadFromSqs",
"Effect": "Allow",
"Action": [
"sqs:ReceiveMessage",
"sqs:DeleteMessage",
"sqs:GetQueueAttributes"
],
"Resource": [
"arn:aws:sqs:*:*:AmazonSecurityLake*"
]
},
{
"Sid": "AllowMetaDataReadWrite",
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:PutObject",
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::aws-security-data-lake*/*",
"arn:aws:s3:::aws-security-data-lake*"
]
}
]
}
特にエラーなく更新できました。
ロール更新後はエラーも出なくなっています。
ソースの追加
Security Lakeのソースを追加します。
EKS監査ログ - 推奨
のラジオボタンを選択し、設定するをクリックしました。
EKS監査ログを有効にする
、バージョン: v2.0(最新)
が現時点ではデフォルトで選択されていました。バージョン管理機能(?)以前はなかったですが見ないうちに追加されてましたね。
EKS監査ログは新規で追加されたソースなのでバージョン2.0が一番古いものですが、その他のデータソースはバージョン1.0/2.0と複数バージョン指定可能になっていました。この辺りの差分を確認したい思いもありますが、また別の機会にします。
リージョンは管理リージョンをすべて選択し、確認をクリックします。
EKS 監査ログ v 2.0 は、アジアパシフィック (東京), 米国東部 (バージニア北部) のすべてのアカウントで有効になっています。
というメッセージとともにEKS監査ログのソースが有効化されたようです。
この時点で、Security Lakeのログ保管先S3バケットを確認すると、<S3バケット名>/aws/EKS_AUDIT/2.0/metadata/
配下にメタデータ関連(?)のデータが既に格納されていました。
ファイルの内容の抜粋は以下です。配信されるデータのスキーマやパーティション、プロパティが指定されているようです。
{
"format-version": 2,
"table-uuid": "42315a72-4ce3-4a49-a5c3-c9927b7b79ec",
"location": "s3://aws-security-data-lake-ap-northeast-1-xxxx/aws/EKS_AUDIT/2.0",
"last-sequence-number": 0,
"last-updated-ms": 1712200857714,
"last-column-id": 94,
"current-schema-id": 0,
"schemas": [
〜中略〜
],
"default-spec-id": 0,
"partition-specs": [
{
"spec-id": 0,
"fields": [
{
"name": "asl_version",
"transform": "identity",
"source-id": 25,
"field-id": 1000
},
{
"name": "region",
"transform": "identity",
"source-id": 24,
"field-id": 1001
},
{
"name": "accountid",
"transform": "identity",
"source-id": 23,
"field-id": 1002
},
{
"name": "time_dt_day",
"transform": "day",
"source-id": 10,
"field-id": 1003
}
]
}
],
"last-partition-id": 1003,
"default-sort-order-id": 0,
"sort-orders": [
{
"order-id": 0,
"fields": []
}
],
"properties": {
"metadata_location": "metadata",
"write.metadata.delete-after-commit.enabled": "true",
"write.update.isolation.level": "snapshot"
},
"current-snapshot-id": -1,
"refs": {},
"snapshots": [],
"statistics": [],
"snapshot-log": [],
"metadata-log": []
}
メタデータの定義ファイルの全文はこちら
{
"format-version": 2,
"table-uuid": "42315a72-4ce3-4a49-a5c3-c9927b7b79ec",
"location": "s3://aws-security-data-lake-ap-northeast-1-xxxx/aws/EKS_AUDIT/2.0",
"last-sequence-number": 0,
"last-updated-ms": 1712200857714,
"last-column-id": 94,
"current-schema-id": 0,
"schemas": [
{
"type": "struct",
"schema-id": 0,
"fields": [
{
"id": 1,
"name": "metadata",
"required": false,
"type": {
"type": "struct",
"fields": [
{
"id": 27,
"name": "log_level",
"required": false,
"type": "string"
},
{
"id": 28,
"name": "product",
"required": false,
"type": {
"type": "struct",
"fields": [
{
"id": 31,
"name": "version",
"required": true,
"type": "string"
},
{
"id": 32,
"name": "vendor_name",
"required": true,
"type": "string"
},
{
"id": 33,
"name": "name",
"required": true,
"type": "string"
},
{
"id": 34,
"name": "feature",
"required": false,
"type": {
"type": "struct",
"fields": [
{
"id": 35,
"name": "name",
"required": false,
"type": "string"
}
]
}
}
]
}
},
{
"id": 29,
"name": "profiles",
"required": false,
"type": {
"type": "list",
"element-id": 36,
"element": "string",
"element-required": true
}
},
{
"id": 30,
"name": "version",
"required": true,
"type": "string"
}
]
}
},
{
"id": 2,
"name": "api",
"required": false,
"type": {
"type": "struct",
"fields": [
{
"id": 37,
"name": "request",
"required": false,
"type": {
"type": "struct",
"fields": [
{
"id": 42,
"name": "uid",
"required": false,
"type": "string"
},
{
"id": 43,
"name": "containers",
"required": false,
"type": {
"type": "list",
"element-id": 44,
"element": {
"type": "struct",
"fields": [
{
"id": 45,
"name": "name",
"required": false,
"type": "string"
},
{
"id": 46,
"name": "image",
"required": false,
"type": {
"type": "struct",
"fields": [
{
"id": 47,
"name": "name",
"required": false,
"type": "string"
},
{
"id": 48,
"name": "path",
"required": false,
"type": "string"
},
{
"id": 49,
"name": "uid",
"required": false,
"type": "string"
}
]
}
}
]
},
"element-required": true
}
}
]
}
},
{
"id": 38,
"name": "operation",
"required": false,
"type": "string"
},
{
"id": 39,
"name": "version",
"required": false,
"type": "string"
},
{
"id": 40,
"name": "response",
"required": false,
"type": {
"type": "struct",
"fields": [
{
"id": 50,
"name": "code",
"required": false,
"type": "int"
},
{
"id": 51,
"name": "message",
"required": false,
"type": "string"
},
{
"id": 52,
"name": "error",
"required": false,
"type": "string"
},
{
"id": 53,
"name": "error_message",
"required": false,
"type": "string"
},
{
"id": 54,
"name": "containers",
"required": false,
"type": {
"type": "list",
"element-id": 55,
"element": {
"type": "struct",
"fields": [
{
"id": 56,
"name": "name",
"required": false,
"type": "string"
},
{
"id": 57,
"name": "image",
"required": false,
"type": {
"type": "struct",
"fields": [
{
"id": 58,
"name": "name",
"required": false,
"type": "string"
},
{
"id": 59,
"name": "path",
"required": false,
"type": "string"
},
{
"id": 60,
"name": "uid",
"required": false,
"type": "string"
}
]
}
}
]
},
"element-required": true
}
}
]
}
},
{
"id": 41,
"name": "group",
"required": false,
"type": {
"type": "struct",
"fields": [
{
"id": 61,
"name": "name",
"required": false,
"type": "string"
}
]
}
}
]
}
},
{
"id": 3,
"name": "message",
"required": false,
"type": "string"
},
{
"id": 4,
"name": "http_request",
"required": false,
"type": {
"type": "struct",
"fields": [
{
"id": 62,
"name": "url",
"required": false,
"type": {
"type": "struct",
"fields": [
{
"id": 64,
"name": "path",
"required": false,
"type": "string"
}
]
}
},
{
"id": 63,
"name": "user_agent",
"required": false,
"type": "string"
}
]
}
},
{
"id": 5,
"name": "actor",
"required": false,
"type": {
"type": "struct",
"fields": [
{
"id": 65,
"name": "user",
"required": false,
"type": {
"type": "struct",
"fields": [
{
"id": 67,
"name": "name",
"required": false,
"type": "string"
},
{
"id": 68,
"name": "uid",
"required": false,
"type": "string"
},
{
"id": 69,
"name": "groups",
"required": false,
"type": {
"type": "list",
"element-id": 71,
"element": {
"type": "struct",
"fields": [
{
"id": 72,
"name": "name",
"required": false,
"type": "string"
}
]
},
"element-required": true
}
},
{
"id": 70,
"name": "type_id",
"required": false,
"type": "int"
}
]
}
},
{
"id": 66,
"name": "session",
"required": false,
"type": {
"type": "struct",
"fields": [
{
"id": 73,
"name": "credential_uid",
"required": false,
"type": "string"
},
{
"id": 74,
"name": "issuer",
"required": false,
"type": "string"
},
{
"id": 75,
"name": "uid",
"required": false,
"type": "string"
}
]
}
}
]
}
},
{
"id": 6,
"name": "cloud",
"required": false,
"type": {
"type": "struct",
"fields": [
{
"id": 76,
"name": "account",
"required": false,
"type": {
"type": "struct",
"fields": [
{
"id": 78,
"name": "uid",
"required": false,
"type": "string"
}
]
}
},
{
"id": 77,
"name": "provider",
"required": true,
"type": "string"
}
]
}
},
{
"id": 7,
"name": "src_endpoint",
"required": false,
"type": {
"type": "struct",
"fields": [
{
"id": 79,
"name": "ip",
"required": false,
"type": "string"
},
{
"id": 80,
"name": "intermediate_ips",
"required": false,
"type": {
"type": "list",
"element-id": 81,
"element": "string",
"element-required": true
}
}
]
}
},
{
"id": 8,
"name": "resources",
"required": false,
"type": {
"type": "list",
"element-id": 82,
"element": {
"type": "struct",
"fields": [
{
"id": 83,
"name": "namespace",
"required": false,
"type": "string"
},
{
"id": 84,
"name": "name",
"required": false,
"type": "string"
},
{
"id": 85,
"name": "uid",
"required": false,
"type": "string"
},
{
"id": 86,
"name": "version",
"required": false,
"type": "string"
},
{
"id": 87,
"name": "type",
"required": false,
"type": "string"
}
]
},
"element-required": true
}
},
{
"id": 9,
"name": "start_time_dt",
"required": false,
"type": "timestamp"
},
{
"id": 10,
"name": "time_dt",
"required": false,
"type": "timestamp"
},
{
"id": 11,
"name": "time",
"required": true,
"type": "long"
},
{
"id": 12,
"name": "severity_id",
"required": true,
"type": "int"
},
{
"id": 13,
"name": "severity",
"required": true,
"type": "string"
},
{
"id": 14,
"name": "class_name",
"required": true,
"type": "string"
},
{
"id": 15,
"name": "class_uid",
"required": true,
"type": "int"
},
{
"id": 16,
"name": "category_name",
"required": true,
"type": "string"
},
{
"id": 17,
"name": "category_uid",
"required": true,
"type": "int"
},
{
"id": 18,
"name": "activity_name",
"required": true,
"type": "string"
},
{
"id": 19,
"name": "activity_id",
"required": true,
"type": "int"
},
{
"id": 20,
"name": "type_name",
"required": true,
"type": "string"
},
{
"id": 21,
"name": "type_uid",
"required": true,
"type": "long"
},
{
"id": 22,
"name": "unmapped",
"required": false,
"type": {
"type": "map",
"key-id": 88,
"key": "string",
"value-id": 89,
"value": "string",
"value-required": true
}
},
{
"id": 23,
"name": "accountid",
"required": false,
"type": "string"
},
{
"id": 24,
"name": "region",
"required": false,
"type": "string"
},
{
"id": 25,
"name": "asl_version",
"required": false,
"type": "string"
},
{
"id": 26,
"name": "observables",
"required": false,
"type": {
"type": "list",
"element-id": 90,
"element": {
"type": "struct",
"fields": [
{
"id": 91,
"name": "name",
"required": false,
"type": "string"
},
{
"id": 92,
"name": "value",
"required": false,
"type": "string"
},
{
"id": 93,
"name": "type",
"required": false,
"type": "string"
},
{
"id": 94,
"name": "type_id",
"required": false,
"type": "int"
}
]
},
"element-required": true
}
}
]
}
],
"default-spec-id": 0,
"partition-specs": [
{
"spec-id": 0,
"fields": [
{
"name": "asl_version",
"transform": "identity",
"source-id": 25,
"field-id": 1000
},
{
"name": "region",
"transform": "identity",
"source-id": 24,
"field-id": 1001
},
{
"name": "accountid",
"transform": "identity",
"source-id": 23,
"field-id": 1002
},
{
"name": "time_dt_day",
"transform": "day",
"source-id": 10,
"field-id": 1003
}
]
}
],
"last-partition-id": 1003,
"default-sort-order-id": 0,
"sort-orders": [
{
"order-id": 0,
"fields": []
}
],
"properties": {
"metadata_location": "metadata",
"write.metadata.delete-after-commit.enabled": "true",
"write.update.isolation.level": "snapshot"
},
"current-snapshot-id": -1,
"refs": {},
"snapshots": [],
"statistics": [],
"snapshot-log": [],
"metadata-log": []
}
次章で検証する前の確認として、検証するAWSアカウントの対象リージョンでSecurity Lakeおよびソース(EKS監査ログv2.0)が有効化されている旨をチェックしておきます。Security Lake > アカウント
から確認できます。
EKSクラスターの作成
監査ログを出力させるEKSクラスターはこちらを参考に作成します。
まずはアカウントAから。東京リージョンにEKSクラスターを作成します。
EKSクラスターの構築はCloudShellからAWS CLI経由で実行します。事前にバージョン確認を行ったところ、eksctlコマンドはCloudShellにデフォルトでインストールされていませんでした。
$ aws --version
aws-cli/2.15.34 Python/3.11.8 Linux/6.1.79-99.167.amzn2023.x86_64 exec-env/CloudShell exe/x86_64.amzn.2023 prompt/off
$ eksctl version
-bash: eksctl: command not found
eksctl
コマンドのパッケージは、こちらのサイトを参考にインストールしました。
インストールできました。
$ eksctl version
0.175.0
eksctl create cluster
を実行することでEKSクラスターが作成されるCloudFormationテンプレートが自動で実行されます。
$ eksctl create cluster \
--name=eks-cluster-member1-ap-northeast-1 \
--version 1.29 \
--nodegroup-name=eks-nodegroup-member1-ap-northeast-1 \
--region ap-northeast-1
テンプレートの実行は15分ほどで完了しました。
クラスターの作成が完了したら、eksctl get cluster
コマンドでクラスターの状態確認を行います。ちなみにこの時点で監査ログは主力されていませんでした。
$ eksctl get cluster
NAME REGION EKSCTL CREATED
eks-cluster-member1-ap-northeast-1 ap-northeast-1 True
kubernetesコントロールプレーンに対してkubectl get nodes
コマンドを実行し、ノードの状態を確認します。
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
ip-192-168-12-155.ec2.internal Ready <none> 5m49s v1.29.0-eks-5e0fdde
ip-192-168-49-207.ec2.internal Ready <none> 5m49s v1.29.0-eks-5e0fdde
続いてアカウントB。バージニア北部リージョンにEKSクラスターを作成します。
※アカウントA同様、コマンドのインストールは済ませてください。
$ eksctl create cluster \
--name=eks-cluster-member2-us-east-1 \
--version 1.29 \
--nodegroup-name=eks-nodegroup-member2-us-east-1 \
--region us-east-1
完了後、EKSクラスター、kubernetesコントロールプレーンの状態確認を実施します。
$ eksctl get cluster
NAME REGION EKSCTL CREATED
eks-cluster-member2-us-east-1 us-east-1 True
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
ip-192-168-10-10.ec2.internal Ready <none> 6m10s v1.29.0-eks-5e0fdde
ip-192-168-49-207.ec2.internal Ready <none> 6m10s v1.29.0-eks-5e0fdde
ログの確認
まずは、Security Lakeのデータレイクであるログ格納先のS3バケットログを確認してみます。
<S3バケット名> > aws/ > EKS_AUDIT/ > 2.0/ > region=ap-northeast-1/ > accountId=123456789012/ > eventDay=yyymmdd/
のディレクトリが作成されており、その配下に〜.gz.parquet
というファイルが作成されています。こちらが集約されたEKS監査ログのようです。
ちなみにロールアップリージョンが設定された東京リージョンにあるバケットには東京・バージニア北部両方のログが保存されていました。想定通りですね。
もちろんバージニア北部リージョンのバケットの場合はバージニア北部リージョンのログのみです。
先述した通り、個別のAWSアカウント側で監査ログを有効化していないにも関わらずログが収集できます。設定漏れや設定不備の心配がなく、運用担当に優しいサービスですね。
後片付け
検証が完了したのち、コスト最適化のためEKSクラスターを削除しました。2環境とも忘れずに削除しておきましょう。
$ eksctl delete cluster \
--name eks-cluster-member1-ap-northeast-1
$ eksctl delete cluster \
--name eks-cluster-member2-us-east-1
さいごに
今回はSecurity Lakeのアップデートによって追加されたEKS監査ログの収集機能を試し、ログの出力ができるところまでを検証しました。
誰かの役に立てれば幸いです。
以上です。最後までお読みいただきありがとうございました。