AuditableなEKSコントロールプレーンログを目指して
皆さん初めまして、セキュリティ部の池添です。
仕事何やってるの?と聞かれるとつい面倒くさくなり、なんかセキュリティ色々と答えてしまうようなポジションやってます。
本記事では、Amazon EKS(以下EKS)の監査ログ周りがちょっとイケてないなーと思い、色々調べた結果を共有させて頂こうと思います。
はじめに
何がイケていないと思ったのかと言いますと、EKSのドキュメントを参考に設定を行うと、IAM Role利用時においてはEKSコントロールプレーンの監査ログのユーザ識別性が弱すぎる、と。
どういうことかと言いますと、弊社のAWSアカウントはIdP連携によるSSO化がされており、ログインをするとIdPから渡された情報を元にIAM Roleへのマッピングが行われます。ログイン後はAssumeRoleされた状態になるため、IAM RoleとEKSコントロールプレーン間でロールマッピングを行い利用することになります。しかし、EKSのドキュメント通りに設定した場合、EKSコントロールプレーンの監査ログに出力されるのはIAM Role情報もしくはユーザ指定のロール名のみで、実際のユーザを特定することが出来ません。これでは、EKSコントロールプレーン周りで発生したインシデントに対して調査が困難になってしまいます。
そこで、ユーザ識別性を改善できないか調査を行った結果を共有します。
結論から書いてしまうと、ユーザ識別性の改善は可能です。
設定
では、実際にユーザ識別性を確保するための設定についてご紹介します。
まずは、EKSでkubernetes(以下k8s)コントロールプレーンのログをCloudwatch Logsへ出力させる設定を行います。必要となるログはk8sの監査(audit)ログですが、設定についてはEKSのドキュメントに記載されているため割愛させて頂きます。
次に、EKSコントロールプレーンの認証周りの設定を行います。
EKSコントロールプレーンの認証はaws-iam-authenticatorという、IAMでの認証をEKSコントロールプレーンへ引き回すための認証バックエンドが担当しています。
- ref: https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/managing-auth.html
- ドキュメントではクライアント側で使う説明になっていますが、現在ではIAMとEKSコントロールプレーン間にも導入されているような挙動です
この aws-iam-authenticator は、k8s ConfigMapの aws-auth に設定された内容に従い、アクセスした際のuserarn/rolearnに応じて、EKSコントロールプレーンでの認可とユーザ名の割り当てを行います。このとき、usernameに特定の文字列が含まれる場合、AWS側の情報で置換を行います。
例えば、EKSのドキュメントでは、ノードインスタンスに対して権限を付与するために以下のような設定をしてくださいと記載されています。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
apiVersion: v1 kind: ConfigMap metadata: name: aws-auth namespace: kube-system data: mapRoles: | - rolearn: <ARN of instance role (not instance profile)> username: system:node:{{EC2PrivateDNSName}} groups: - system:bootstrappers - system:nodes |
この設定サンプルのusernameの部分に使われている {{EC2PrivateDNSName}} は、アクセスしてきたノードインスタンスのプライベートDNS名へ置換が行われ、EKSコントロールプレーンのログ上でどのノードインスタンスでの動作なのかが特定できるようになっています。これと同じ様なテンプレートパラメータが他にもあり、それらを使うことでロールマッピングを行っている場合のユーザ識別性を向上させることが出来ます。
2019/06現在、usernameに使えるテンプレートパラメータは下記の3つとなります。
- {{AccountID}}
- EKSクラスタが存在するAWSアカウントIDに置換されます
- {{SessionName}}
- AssumeRoleしたときのrole-session-nameパラメータに置換されます
- {{EC2PrivateDNSName}}
- EC2インスタンスのプライベートDNS名に置換されます
このテンプレートパラメータについては、公開されているリポジトリ・コードに情報が記載されています。
- 参考: https://github.com/kubernetes-sigs/aws-iam-authenticator
- 参考: https://github.com/kubernetes-sigs/aws-iam-authenticator/blob/fb78cc25775f921cd4b3931fa004eae031505e21/pkg/server/server.go#L394-L418
実際にこれらのテンプレートパラメータを使い、ユーザ識別性を向上させる設定のサンプルです。 この設定は、ノードインスタンスへのアクセス権付与と同様に、k8s ConfigMapのaws-auth へ追記を行うことで設定を行います。なお、サンプル内に含まれる <AWS_ACCOUNT_ID> は、皆さんがお使いのAWSアカウントIDへ読み替えてください。
1 2 3 4 5 |
- rolearn: arn:aws:iam::<AWS_ACCOUNT_ID>:role/staff username: role:{{AccountID}}:staff:{{SessionName}} groups: - system:masters |
この設定をmapRolesに加えることで、staffというIAM Roleでアクセスを行ったユーザは、role:<AWS_ACCOUNT_ID>:staff:<SESSION_NAME> というユーザ名が割り当てられ、EKSコントロールプレーン上での権限はsystem:masters が付与されます。<SESSION_NAME> は、AssumeRoleした際のrole-session-nameの値が使われます。AWS CLIによる自動AssumeRoleを設定している場合は、botocore-session-xxxxxxxxxxx のような値となります。この値は、Cloudtrail側のAssumeRoleログにも出力されるため、誰がそのセッションを利用しているかが特定可能となり、ユーザ識別性が確保出来るようになります。
実験
では、前述の設定をした環境で、staffロールへ自動的にAssumeRoleするプロファイルstaffがある想定で、kubectlによるクラスタ操作した場合のログを見てみましょう。
コマンド例としてはこんな感じです。
1 2 |
aws --profile staff eks update-kubeconfig --name test-cluster kubectl describe configmap -n kube-system aws-auth |
このときのログは以下のようになります。AWSアカウントIDは111111111111としておきます。
まずはEKSコントロールプレーンの監査ログです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
{ "kind": "Event", "apiVersion": "audit.k8s.io/v1beta1", "metadata": { "creationTimestamp": "2019-06-05T09:52:50Z" }, "level": "Metadata", "timestamp": "2019-06-05T09:52:50Z", "auditID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "stage": "ResponseComplete", "requestURI": "/api/v1/namespaces/kube-system/configmaps/aws-auth", "verb": "get", "user": { "username": "role:111111111111:staff:botocore-session-1559728369", "uid": "heptio-authenticator-aws:111111111111:AROXXXXXXXXXXXXXXXXXX", "groups": [ "system:masters", "system:authenticated" ] }, "sourceIPs": [ "xxx.xxx.xxx.xxx" ], "userAgent": "kubectl/v1.10.11 (darwin/amd64) kubernetes/637c7e2", "objectRef": { "resource": "configmaps", "namespace": "kube-system", "name": "aws-auth", "apiVersion": "v1" }, "responseStatus": { "metadata": {}, "code": 200 }, "requestReceivedTimestamp": "2019-06-05T09:52:50.128779Z", "stageTimestamp": "2019-06-05T09:52:50.133049Z", "annotations": { "authorization.k8s.io/decision": "allow", "authorization.k8s.io/reason": "" } } |
user.username にAWSアカウントIDとbotocoreにより生成されたセッション名が含まれています。
AssumeRole時のセッション名が判明したので、Cloudtrailで当該セッションを生成したログを確認してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
{ "eventVersion": "1.05", "userIdentity": { "type": "IAMUser", "principalId": "AIDXXXXXXXXXXXXXXXXXX", "arn": "arn:aws:iam::111111111111:user/iam-user1", "accountId": "111111111111", "accessKeyId": "AKIXXXXXXXXXXXXXXXXXX", "userName": "iam-user1" }, "eventTime": "2019-06-05T09:52:49Z", "eventSource": "sts.amazonaws.com", "eventName": "AssumeRole", "awsRegion": "us-east-1", "sourceIPAddress": "xxx.xxx.xxx.xxx", "userAgent": "Botocore/1.12.161 Python/2.7.15 Darwin/18.6.0", "requestParameters": { "roleArn": "arn:aws:iam::111111111111:role/staff", "roleSessionName": "botocore-session-1559728369" }, "responseElements": { "credentials": { "accessKeyId": "ASIAXXXXXXXXXXXXXXXX", "expiration": "Jun 5, 2019 19:52:49 PM", "sessionToken": "xxxxxxxxxxxxxxxxxxxxxxxx" }, "assumedRoleUser": { "assumedRoleId": "AROAXXXXXXXXXXXXXXXX:botocore-session-1559728369", "arn": "arn:aws:sts::111111111111:assumed-role/staff/botocore-session-1559728369" } }, "requestID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "eventID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "resources": [ { "ARN": "arn:aws:iam::111111111111:role/staff", "accountId": "111111111111", "type": "AWS::IAM::Role" } ], "eventType": "AwsApiCall", "recipientAccountId": "111111111111" } |
userIdentity.userNameを確認するとiam-user1がAssumeRoleしていることが分かります。
この二つのログから、EKSコントロールプレーンの操作をいつ誰が行ったか追跡することができ、何かあった場合のログとして有効活用が出来そう、という事が分かりました。ただし、EKSコントロールプレーンのログにはEKSクラスタが存在するAWSアカウントID(※)とクラスター名が無いので、集約をして横断的な管理をするにはまだ不十分です。集約する場合にはAWSアカウントIDとクラスター名を付与する仕組みやパーティションを分ける等を検討すると良いでしょう。
なお、IdP連携(SAML等)でAWS CLIを利用している場合は、SessionNameにメールアドレス等のユーザ識別が容易な値を使うと、EKSコントロールプレーン側のログだけで誰が何をしたかを容易に特定が出来るようになり楽が出来ます。
※ システムアカウントによる自動的な操作はsystem:serviceaccountのような形式になり、AWSアカウントIDを含めることが出来ません。
集約
先ほど集約について触れましたが、複数のAWSアカウントでEKSを利用している場合は、当然集約したくなりますよね?
集約については、Cloudwatch Logsに流れてきたログを、サブスクリプションフィルターで集約先に転送することで容易に実現出来ます。実に簡単ですね。
詳細については割愛させて頂きますが、弊社の場合は、Cloudwatch Logs サブスクリプションフィルタで集約先AWSアカウントのKinesis streamへ投入、Kinesis Firehose配信ストリームのRecord transformationで送信元AWSアカウントIDとEKSクラスター名を全ログに付与し、ログ分析システムへ一括投入、と言う構成で構築してみました。Record transformationでAWSアカウントIDとクラスター名を付与して、環境とユーザ識別性をあげてから投入するのが集約のポイントとなっています。
まとめ
Amazon EKSコントロールプレーンにおけるログのユーザ識別性向上設定について解説しました。
今回の調査に当たりaws-iam-authenticatorのテンプレートパラメータについての日本語情報があまりにもなさ過ぎたのですが、ログのユーザ識別性は皆さんあまり気にしないのでしょうか?
それとも、yamlファイルをVCSで管理+ServiceAccountを使ったCI/CDと言ったワークフローで運用をされてるのでしょうか?
この記事が皆さんのお役に立てれば幸いです。