kube-benchの実行結果をAWS Security Hubに連携して管理する

この記事は [**AWS Advent Calendar 2020**](https://qiita.com/advent-calendar/2020/aws) 14日目の記事です。

## はじめに
2020/12/4 に AWS Security Hub に統合可能な 3rd Party パートナーの製品として
Aqua Security の kube-bench が追加されたことが発表されました。

:rocket: **AWS Security Hub adds open source tool integrations with Kube-bench and Cloud Custodian**
https://aws.amazon.com/jp/about-aws/whats-new/2020/12/aws-security-hub-adds-open-source-tool-integration-with-kube-bench-and-cloud-custodian/

この統合により kube-bench で実行した CIS Kubernetes Benchmark および
CIS Amazon EKS Benchmark のチェック結果を AWS Security Hub で集中管理できるようになりました。

EKS クラスターで kube-bench を実行し、Security Hub に結果を取り込んでみましたのでご紹介します。

### CIS Benchmark とは
CIS Benchmark は 米国の非営利団体である CIS (Center for Internet Secuirty) が公開している
各種 OS、サーバー、クラウド環境などを強化するためのガイドラインです。
CIS は 140 以上の CIS Benchmark を発行しており、PCI DSS などのコンプライアンス要件で
業界で認められているシステム強化標準との記載があった場合などに参照されます。

CIS Benchmark は CIS のサイトから PDF 形式でダウンロードできます。
https://www.cisecurity.org/cis-benchmarks/

### kube-bench とは
[kube-bench](https://github.com/aquasecurity/kube-bench) は 対象の環境が CIS Kubernetes Benchmark に記載されている推奨事項に
準拠しているかどうかをチェックすることができる Go 製のアプリケーションです。
Aqua Security 社が開発、OSS として公開しています。

CIS Kubernetes Benchmark だけでなく、CIS Amazon Elastic Kubernetes Service (EKS) Benchmark や
CIS Google Kubernetes Engine (GKE) Benchmark のチェックもサポートしています。

### AWS Security Hub とは
[AWS Security Hub](https://aws.amazon.com/jp/security-hub/) は AWS 環境全体の様々なセキュリティデータを集約して
一元的に管理するためのサービスです。
Amazon GuardDuty や Inspector, Macie などの AWS サービスはもちろんのこと
多くの 3rd Party セキュリティ製品と統合されており、対応する製品のデータを Security Hub に
送信したり、逆に Security Hub のデータを製品側で受信するといったことが可能です。

内部的には [AWS Security Finding Format (ASFF)](https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-findings-format.html) と呼ばれる JSON 型のフォーマットで結果情報が
管理されているため、この形式に準拠していれば独自のデータをインポートすることもできます。

## 前提条件
以下のバージョンで実施しています。

* Amazon EKS: 1.17
* eksctl: 0.30.0
* kube-bench: 0.40.0

CIS Amazon EKS Benchmark v1.0 のチェックを EKS クラスター内で実行します。
EKS クラスターの構築、Security Hub の有効化といった手順は割愛します。

## 統合の有効化
Security Hub コンソールの統合から kube-bench を検索し、**結果を受け入れる** をクリックすると
調査結果を Security Hub に送信するために必要な IAM ポリシーに関する情報が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/00689356-d0e0-c195-d50e-fdfb581c62e8.png)
確認画面で再度結果を受け入れるを選択すると、ステータスが有効になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/aa864548-3f6c-0a75-4be5-ff37199bd80a.png)

## IAM ロールの設定
kube-bench を EKS クラスター内で実行するため、実行される Pod が Security Hub へ
チェック結果を送信する権限を持っている必要があります
Pod に対して AWS リソースへのアクセス権限を割り当てる方法は 2 通りあります。

* [IAM Roles for Service Accounts (IRSA)](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) を使用する
* ノードグループに設定する IAM ロールを使用する

IRSA を使用すると、IAM ロールを Kubernetes のサービスアカウントに関連付けることができます。
これにより、kube-bench が起動する pod にのみ Security Hub へのアクセス許可を提供できます。
ノードグループに設定する IAM ロールを使用した場合、そのグループ配下で起動するすべての Pod に
Security Hub へのアクセス権限が割り当てられてしまう点にご注意ください。

今回は IRSA を使用します。
IRSA を使用するために、IAM OIDC Provider を作成していない場合は先に作成しておきます。

```shell-session
$ eksctl utils associate-iam-oidc-provider --cluster {CLUSTER_NAME} --approve --region ap-northeast-1
```

次に以下のポリシーをアタッチした IAM ロールを作成します。
リージョンは適宜読み替えてください。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "securityhub:BatchImportFindings",
            "Resource": [
                "arn:aws:securityhub:ap-northeast-1::product/aqua-security/kube-bench"
            ]
        }
    ]
}
```

IAM ロールの信頼関係に以下のようなポリシーを追記します。
`<ACCOUNT_ID>`, `<OIDR_PROVIDER_ID>`, リージョンはご自身のものを設定してください。
Namespace に `kube-bech` を使用する想定で記述しています。

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::<ACCOUNT_ID>:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/<OIDC_PROVIDER_ID>"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringLike": {
          "oidc.eks.ap-northeast-1.amazonaws.com/id/<OIDC_PROVIDER_ID>:sub": "system:serviceaccount:kube-bench:*",
          "oidc.eks.ap-northeast-1.amazonaws.com/id/<OIDC_PROVIDER_ID>:aud": "sts.amazonaws.com"
        }
      }
    }
  ]
}
```

## コンテナイメージの作成
kube-bench のコンテナイメージをビルドし、ECR に push しておく必要があります。
最初にイメージを格納するための ECR リポジトリを作成します。

```shell-session
$ aws ecr create-repository --repository-name k8s/kube-bench --image-tag-mutability MUTABLE
{
    "repository": {
        "repositoryArn": "arn:aws:ecr:ap-northeast-1:123456789012:repository/k8s/kube-bench",
        "registryId": "123456789012",
        "repositoryName": "k8s/kube-bench",
        "repositoryUri": "123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/k8s/kube-bench",
        "createdAt": 1607747704.0,
        "imageTagMutability": "MUTABLE",
        "imageScanningConfiguration": {
            "scanOnPush": false
        },
        "encryptionConfiguration": {
            "encryptionType": "AES256"
        }
    }
}
```

kube-bench のソースコードを GitHub から clone します。

```shell-session
$ git clone https://github.com/aquasecurity/kube-bench.git
Cloning into 'kube-bench'...
remote: Enumerating objects: 4115, done.
remote: Total 4115 (delta 0), reused 0 (delta 0), pack-reused 4115
Receiving objects: 100% (4115/4115), 7.69 MiB | 5.28 MiB/s, done.
Resolving deltas: 100% (2644/2644), done.

$ cd kube-bench
```

Security Hub に実行結果を連携する場合、イメージをビルドする前に `cfg/eks-1.0/config.yaml` を編集し
実行対象の AWS アカウントおよびリージョン、クラスター名を書き換えておく必要があります。

```cfg/eks-1.0/config.yaml
---
AWS_ACCOUNT: "123456789012"
AWS_REGION: "ap-northeast-1"
CLUSTER_ARN: "arn:aws:eks:ap-northeast-1:123456789012:cluster/{YOUR_CLUSTER_NAME}"
```

コンテナイメージをビルドし、ECR に push します。

```shell-session
$ aws ecr get-login-password | docker login --username AWS --password-stdin https://123456789012.dkr.ecr.ap-northeast-1.amazonaws.com
Login Succeeded

$ docker build -t k8s/kube-bench .
...
Successfully built 6ad073f96455
Successfully tagged k8s/kube-bench:latest

$ docker tag k8s/kube-bench:latest 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/k8s/kube-bench:latest
$ docker push 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/k8s/kube-bench:latest
The push refers to repository [123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/k8s/kube-bench]
bd6c279efeaa: Pushed 
3032a8c3bf7a: Pushed 
b0efa7564210: Pushed 
fe4cf80d4f2c: Pushed 
31c3d3db74eb: Pushed 
d2e36eff2b5d: Pushed 
f4666769fca7: Pushed 
latest: digest: sha256:da95de0edccad7adb6fd6c80137a65b5458142efe14efa94ac44cc5c6ce6b2ef size: 1782
```


## kube-bench の実行
### サービスアカウントの作成
以下のマニフェストファイルで IAM ロールを関連付けたサービスアカウントを作成します。

```sa.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: kube-bench-sa
  namespace: kube-bench
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/<IAM_ROLE_NAME>
```

Namespace `kube-bench` を作成し、apply します。

```shell-session
$ kubectl create ns kube-bench
kubectl create ns kube-bench

$ kubectl apply -f sa.yaml
serviceaccount/kube-bench-sa created

$ kubectl get sa -n kube-bench
NAME            SECRETS   AGE
default         1         6m32s
kube-bench-sa   1         11s
```

### ジョブの実行
GitHub から clone した `jobs-eks.yaml` を開き、`image`, `command` を編集します。
command に `--asff` フラグを追加することで、Security Hub へ結果の送信が行われます。
更に先ほど作成したサービスアカウント名を追記します。
編集後のファイルは以下のようになります。

```job-eks.yaml
---
apiVersion: batch/v1
kind: Job
metadata:
  name: kube-bench
spec:
  template:
    spec:
      hostPID: true
      serviceAccountName: kube-bench-sa
      containers:
        - name: kube-bench
          image: 123456789012.dkr.ecr.region.amazonaws.com/k8s/kube-bench:latest
          command: ["kube-bench", "node", "--benchmark", "eks-1.0", "--asff"]
          volumeMounts:
            - name: var-lib-kubelet
              mountPath: /var/lib/kubelet
              readOnly: true
            - name: etc-systemd
              mountPath: /etc/systemd
              readOnly: true
            - name: etc-kubernetes
              mountPath: /etc/kubernetes
              readOnly: true
      restartPolicy: Never
      volumes:
        - name: var-lib-kubelet
          hostPath:
            path: "/var/lib/kubelet"
        - name: etc-systemd
          hostPath:
            path: "/etc/systemd"
        - name: etc-kubernetes
          hostPath:
            path: "/etc/kubernetes"
```

Kubernetes の job として kube-bench を実行し、正常終了することを確認します

```shell-session
$ kubectl apply -f job-eks.yaml -n kube-bench
job.batch/kube-bench created

$ kubectl get all -n kube-bench                                                                                                                                                    
NAME                   READY   STATUS      RESTARTS   AGE
pod/kube-bench-q4sbd   0/1     Completed   0          55s

NAME                   COMPLETIONS   DURATION   AGE
job.batch/kube-bench   1/1           5s         55s
```

Pod のログに以下のようなメッセージが出力され、ジョブがエラーとなる場合は
`cfg/eks-1.0/config.yaml` の記載内容が正しいか確認してください。

```shell-session
failed to output to ASFF: finding publish failed: MissingEndpoint: 'Endpoint' configuration is required for this service
```

## Security Hub の検出結果を確認
通常、kube-bench の実行結果は Pod のログに出力されます。

<details><summary>出力例 (クリックで展開します)</summary><div>

```shell-session
$ kubectl logs -f pod/kube-bench-2j2ss -n kube-bench
[INFO] 3 Worker Node Security Configuration
[INFO] 3.1 Worker Node Configuration Files
[PASS] 3.1.1 Ensure that the proxy kubeconfig file permissions are set to 644 or more restrictive (Scored)
[PASS] 3.1.2 Ensure that the proxy kubeconfig file ownership is set to root:root (Scored)
[PASS] 3.1.3 Ensure that the kubelet configuration file has permissions set to 644 or more restrictive (Scored)
[PASS] 3.1.4 Ensure that the kubelet configuration file ownership is set to root:root (Scored)
[INFO] 3.2 Kubelet
[PASS] 3.2.1 Ensure that the --anonymous-auth argument is set to false (Scored)
[PASS] 3.2.2 Ensure that the --authorization-mode argument is not set to AlwaysAllow (Scored)
[PASS] 3.2.3 Ensure that the --client-ca-file argument is set as appropriate (Scored)
[PASS] 3.2.4 Ensure that the --read-only-port argument is set to 0 (Scored)
[PASS] 3.2.5 Ensure that the --streaming-connection-idle-timeout argument is not set to 0 (Scored)
[PASS] 3.2.6 Ensure that the --protect-kernel-defaults argument is set to true (Scored)
[PASS] 3.2.7 Ensure that the --make-iptables-util-chains argument is set to true (Scored) 
[PASS] 3.2.8 Ensure that the --hostname-override argument is not set (Scored)
[WARN] 3.2.9 Ensure that the --event-qps argument is set to 0 or a level which ensures appropriate event capture (Scored)
[PASS] 3.2.10 Ensure that the --rotate-certificates argument is not set to false (Scored)
[PASS] 3.2.11 Ensure that the RotateKubeletServerCertificate argument is set to true (Scored)

== Remediations node ==
3.2.9 If using a Kubelet config file, edit the file to set eventRecordQPS: to an appropriate level.
If using command line arguments, edit the kubelet service file
/etc/systemd/system/kubelet.service on each worker node and
set the below parameter in KUBELET_SYSTEM_PODS_ARGS variable.
Based on your system, restart the kubelet service. For example:
systemctl daemon-reload
systemctl restart kubelet.service


== Summary node ==
14 checks PASS
0 checks FAIL
1 checks WARN
0 checks INFO

== Summary total ==
14 checks PASS
0 checks FAIL
1 checks WARN
0 checks INFO
```

</div></details>

`--asff` フラグを指定して、Security Hub に結果を送信した場合は
Security Hub にインポートされた件数のみがログに記載されています。

```shell-session
$ kubectl logs -f pod/kube-bench-q4sbd -n kube-bench
2020/12/12 14:51:07 Number of findings that were successfully imported:1
```

今回の環境の場合、1件であることが確認できます。

Security Hub に結果が送信されるのはチェック結果が `FAIL` または `WARN` となった
項目のみであることに注意してください。
(全てのチェックを PASS した場合、Security Hub 側には結果は生成されません。)

Security Hub コンソールの検出結果を確認すると、送信された 1 件の結果が
正常に取り込まれていることを確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/ded18a34-e170-2b1e-7e67-d7f9af25def4.png)

## カスタムインサイトの活用
Security Hub には特定のグループ化条件とフィルタを使用して、リソースや環境毎に
データを集約することができる、カスタムインサイトという機能が用意されています。
作成方法はコンソールのインサイトから任意のグループ化条件、フィルタを設定し、保存するだけです。


例えば Security Hub をマルチアカウント構成で利用している場合、マスターアカウントでは
メンバー AWS アカウントごとに検出結果を集約するような使い方ができます。

以下はグループ化条件を AWS アカウント ID に設定し、製品名 `Kube-bench` をフィルターに
設定したカスタムインサイトの例です。アカウント ID をクリックして、対象アカウントの
kube-bench の結果を即座に確認できるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/2fe49f1e-c274-7ba3-9d7a-c9f274d7f91d.png)
また単一アカウントで運用している場合でも、グループ化条件をリソース ID に設定すれば、
EKS クラスター毎の検出結果を一覧化するといった使い方も可能です。

複数アカウントやクラスターを運用している環境では、カスタムインサイトの活用で
より結果を管理しやすくなるのでおすすめです。

## 参考
**Integrating kube-bench with AWS Security Hub**
https://github.com/aquasecurity/kube-bench/blob/master/docs/asff.md
**Managing custom insights**
https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-custom-insights.html

以上です。
参考になれば幸いです。