Cloud Native Security Inspector (Project Narrows) を触ってみる

本記事は、「[TUNA-JP Advent Calendar 2022]()」の19 日目のエントリです。

VMware が今年のVM Explore で（ひっそりと）アナウンスされた Cloud Native Security Inspector （通称Project Narrows）を実際に触ってみたので、その紹介します。

# Cloud Native Security Inspector とは
一言で表現すると、Harbor のセキュリティ機能の拡張であり、動的アプリケーションセキュリティテスト（DAST）を実現します。Harbor はコンテナイメージのレポジトリとして機能しますが、他にも様々な機能があり、代表的なものが保存したイメージの脆弱性スキャンです（このあたりは[今さら聞けないHarborのレジストリ以外の機能](https://qiita.com/ipppppei/items/b23bdfb234962e015869)にまとまっておりますので、そちらをご参照ください）。しかしながら、これは静的アプリケーションセキュリティテスト（SAST）であり、実行中のアプリケーションの脆弱性については担保しません。そこで、Cloud Native Security Inspector はこれを補完する形で、コンテナを終了することなく、実行時になって初めて検出可能なランタイムの脆弱性をリアルタイムに把握することを目指しています。

2022/12/19 現在では下記の3 種類の検査に対応しています。
- Harbor による脆弱性検査
- kube-bench に基づくクラスタの検査
- Arksec に基づくリスク検査

このうち3 つめのArksec については、私も知らず、OSS でもないようで情報があまりなかったですが、おそらくhttps://arksec.cn/page/cp/163.php の製品を活用していると思われます。

# さっそく触ってみる
初めに、Cloud Native Security Inspector はまだアナウンスがされたばかりの状態であり、ご自身で試される場合は必ず検証用のKubernetes クラスタをデプロイして利用してください。本番環境はNG です。本記事を作るにあたり、私は実際にKubernetes クラスタを3 台ほど破壊してしまいました。記事を書いている際にもコードのアップデートが多々あったので、そういうものだとご理解の上お試しください。

## インストール要件
現在テストされている環境が下記のようです。Kubernetes 1.24 と脆弱性スキャンを有効にしたHarbor の準備に留意します。

https://github.com/vmware-tanzu/cloud-native-security-inspector/
- Ubuntu Linux as the operating system of the installation machine
- Kubernetes 1.24
- Harbor 2.5.0+ is deployed and vulnerability scanning in Harbor is configured properly.
- kubectl and docker commands are ready to use.


なお、Harbor のインストールについては[公式ドキュメント](https://goharbor.io/docs/2.6.0/install-config/) を参考にしてください。もしくは、[こちらのブログ](https://medium.com/swlh/setup-harbor-with-lets-encrypt-c9632cdd6fd9)が参考になります（インストールスクリプトを起動するときに--with-trivy を忘れずに）。後々面倒なので、Https 化しておくことを推奨します。本手順でも、Https 化して、イメージスキャナとしてtrivy を有効化していることを前提とします。

## インストール方法
下記を参考に進めます。

- https://github.com/vmware-tanzu/cloud-native-security-inspector/blob/main/docs/TUTORIAL.md

基本的にチュートリアルの通り進めていけば問題ありませんし、インストールは下記3 つのコマンドで完了しますが、いくつか罠があるので補足します。
```
git clone https://github.com/vmware-tanzu/cloud-native-security-inspector.git
cd cloud-native-security-inspector
./deploy.sh install
```

まず、インストールスクリプトであるdeploy.sh を見ていただければ分かりますが、このスクリプトではhelm を使ってOpenSearch と呼ばれる分析ツールをインストールします。

```shell
***
function install_opensearch() {
    note "Installing opensearch"
    check_helm
    helm repo add opensearch https://opensearch-project.github.io/helm-charts/
    helm repo update
    helm install opensearch-deployment-for-narrows opensearch/opensearch -n opensearch --version 2.8.0 --create-namespace --set persistence.enabled=false
    success "OpenSearch installed"
}
***
```

このまま進めると、環境によってはOpenSearch のPod が下記ログとともにCrashLoopBackOff する場合があります。

```
ERROR: [1] bootstrap checks failed
[1]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
```

これについては下記ドキュメントやGithub で記載がありますが、vm.max_map_count (検索インデックス保存のための仮想メモリアドレス空間のサイズ) の値を調整する必要があります。

https://opensearch.org/docs/latest/install-and-configure/install-opensearch/index/

https://github.com/opensearch-project/helm-charts/issues/302

回避策として、Helm でのインストール時に渡すパラメータとしてsysctlVmMaxMapCount の値が調整できるようになっていますが、これが失敗する場合があり、その際には下記の回避策を取ります。

https://github.com/opensearch-project/helm-charts/issues/87#issuecomment-939127256

そのため、values.yaml を取得し（https://github.com/opensearch-project/helm-charts/blob/main/charts/opensearch/values.yaml ）、上記の変更を反映させ、deploy.sh におけるOpenSearch をインストールする関数を以下のように書き換えます。

```
function install_opensearch() {
    note "Installing opensearch"
    check_helm
    helm repo add opensearch https://opensearch-project.github.io/helm-charts/
    helm repo update
    helm install -f ${YOUR_DIRECTORY}/values.yaml opensearch-deployment-for-narrows opensearch/opensearch -n opensearch --version 2.8.0 --create-namespace 
    success "OpenSearch installed"
}
```

基本的にはこれでインストールができるはずですが、デフォルトではダッシュボードアクセス時にNodePort を使う設定になっているため、これをLoadBalancer にしたい場合はcloud-native-security-inspector/src/frontend/scripts/cloud-native-security-inspector-portal-service.yaml を例えば下記のように修正します。
```yaml
# Copyright 2022 VMware, Inc.
# SPDX-License-Identifier: Apache-2.0
kind: Service
apiVersion: v1
metadata:
  name: cloud-native-security-inspector-portal-service
  namespace: cnsi-system
spec:
  selector:
    cloud-native-security-inspector-portal: portal
  ports:
  - protocol: TCP
    port: 3800
    targetPort: 3800
  type: LoadBalancer
```

インストールが完了すると、下記のようなKubernetes オブジェクトが作成されます（エラーが出ていないことを確認してください）。

```
[root@localhost cloud-native-security-inspector]# kubectl get all -n cnsi-system 
NAME                                                         READY   STATUS    RESTARTS   AGE
pod/cloud-native-security-inspector-portal-96bc875ff-h8mhz   1/1     Running   0          119m
pod/cnsi-controller-manager-d7d8d5446-kk8zv                  2/2     Running   0          119m

NAME                                                     TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)          AGE
service/cloud-native-security-inspector-portal-service   LoadBalancer   10.0.235.221   ${LB_IP}       3800:32737/TCP   119m
service/cnsi-controller-manager-metrics-service          ClusterIP      10.0.59.150    <none>         8443/TCP         119m

NAME                                                     READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/cloud-native-security-inspector-portal   1/1     1            1           119m
deployment.apps/cnsi-controller-manager                  1/1     1            1           119m

NAME                                                               DESIRED   CURRENT   READY   AGE
replicaset.apps/cloud-native-security-inspector-portal-96bc875ff   1         1         1       119m
replicaset.apps/cnsi-controller-manager-d7d8d5446                  1         1         1       119m

[root@localhost cloud-native-security-inspector]# kubectl get all -n opensearch 
NAME                              READY   STATUS    RESTARTS   AGE
pod/opensearch-cluster-master-0   1/1     Running   0          121m
pod/opensearch-cluster-master-1   1/1     Running   0          121m
pod/opensearch-cluster-master-2   1/1     Running   0          121m

NAME                                         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)             AGE
service/opensearch-cluster-master            ClusterIP   10.0.45.44   <none>        9200/TCP,9300/TCP   121m
service/opensearch-cluster-master-headless   ClusterIP   None         <none>        9200/TCP,9300/TCP   121m

NAME                                         READY   AGE
statefulset.apps/opensearch-cluster-master   3/3     121m

```

また、ポータルのService オブジェクトのExternal-IP （上記ログでは${LB_IP}）からダッシュボードにアクセスできるようになります（下記は既にセキュリティテストを行った状態のためグラフが表示されていますが、インストール直後は何も見えません）。LoadBalancer リソースを使用した場合はポート3800 でアクセスします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2326226/89500164-eede-9a3e-4507-c85b2964e686.png)

ダッシュボードにアクセスしたら、UI からシークレットの作成と初期設定を行います。シークレット作成画面ではHarbor のAdmin アカウントを入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2326226/9b1a4b3c-1764-ab45-adb8-a2aa02072d12.png)
![Screenshot_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2326226/7a224a69-4709-f5c7-7f82-55e7e7608b67.png)
設定値も同様にチュートリアルに従って入力します。入力後、ステータスが問題なくHealthy になっていることを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2326226/a454ac78-5c75-06b7-ee03-e7af6444c5a8.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2326226/11ae6103-0fc6-f176-687a-6bb437cb350a.png)

これでCloud Native Security Inspector の機能が使えるようになりました。さっそくテスト用のアプリをデプロイし、スキャンをしてみます。

## テスト用アプリケーションの作成

こちらもチュートリアルに従い、nginx-slim をデプロイします。なお、イメージはHarbor にある必要があるため、k8s.gcr.io/nginx-slim:0.26 のイメージをdocker pull した後、あらかじめデプロイしたHarbor にプッシュします。

また、デプロイする際はチュートリアルのマニフェスト中のregcred の値を（.dockerconfigjson）適切に変更してください。もしくは、マニフェスト中のNamespace とSecret リソースを削除し、代わりに下記コマンドで個別にNamespace とSecret をデプロイしてください。Namespace に対するラベルを忘れないよう注意してください。

```
kubectl create ns workloads
kubectl label ns workloads goharbor.io/watch="true"
kubectl create secret -n workloads docker-registry regcred --docker-server=${REPO_URL} --docker-username=admin --docker-password=${YOUR_PASSWORD}
```

Harbor から取得したイメージを使ってkubectl apply 等で問題なくテスト用アプリケーションの作成が完了したら、セキュリティスキャンを行います。

## セキュリティスキャンの実施

セキュリティスキャンはKubernetes 上でCronjob として定期実行されますが、Policy にはどれくらいの間隔でスキャンをかけるか、などが定義されます。

注意点として、OpenSearch のアドレスだけは、チュートリアルやデフォルトの値と異なり、opensearch-cluster-master.default:9200 ではなくopensearch-cluster-master.opensearch:9200 とすることに注意してください。User やPassword はそのままで大丈夫です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2326226/ee9b4ede-bd4c-7a5d-e65c-dd0bb26bd382.png)

**次が最も重要です。必ず、Namespace Labels Selectors の設定で、goharbor.io/watch : true を追加してください。これを忘れると、デフォルトではすべての名前空間にあるコンテナをスキャンしてしまい、デフォルトのCronjob の実行間隔3 分では終わらず、ループし、クラスタ全体のレスポンスが遅くなります。** Cronjob を個別に消したとしても、Cloud Native Security Inspector をアンインストールしたとしても（一部Namespace が消えず、Finalizer を削除しても残り続ける）、ノードを再起動したとしても、なぜかこの事象は解決されませんでした（仕方なくクラスタを再作成しました）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2326226/775eff78-1c00-01c8-9f95-e31a8047e419.png)

間違いなく上記設定を行ったと確信したら、次に進みます。Inspection Result Setting はデフォルトのまま、ポリシーの作成を完了します。

すると、Kubernetes クラスタで自動的にCronjob が作成されます。このJob が各種検査を行います。

```
[root@localhost cloud-native-security-inspector]# kubectl get cronjob -n cronjobs
NAME                          SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
demo-policyf8wxc--risk        */3 * * * *   False     0        105s            177m
demo-policyj8cpc--inspector   */3 * * * *   False     0        105s            177m
```

3 分経過すると1 回目のJob が立ち上がります。下記は15分以上放置した結果で、最大5 つの結果を保持します。

```
[root@localhost cloud-native-security-inspector]# kubectl get all -n cronjobs
NAME                                             READY   STATUS      RESTARTS   AGE
pod/demo-policyf8wxc--risk-27857250-jjpwx        0/2     Completed   0          14m
pod/demo-policyf8wxc--risk-27857253-md928        0/2     Completed   0          11m
pod/demo-policyf8wxc--risk-27857256-2r78h        0/2     Completed   0          8m48s
pod/demo-policyf8wxc--risk-27857259-nzrvr        0/2     Completed   0          5m48s
pod/demo-policyf8wxc--risk-27857262-6nb7c        0/2     Completed   0          2m48s
pod/demo-policyj8cpc--inspector-27857250-wcpxp   0/1     Completed   0          14m
pod/demo-policyj8cpc--inspector-27857253-t4254   0/1     Completed   0          11m
pod/demo-policyj8cpc--inspector-27857256-cnxkt   0/1     Completed   0          8m48s
pod/demo-policyj8cpc--inspector-27857259-rhcj7   0/1     Completed   0          5m48s
pod/demo-policyj8cpc--inspector-27857262-t5zzk   0/1     Completed   0          2m48s

NAME                                        SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
cronjob.batch/demo-policyf8wxc--risk        */3 * * * *   False     0        2m50s           178m
cronjob.batch/demo-policyj8cpc--inspector   */3 * * * *   False     0        2m50s           178m

NAME                                             COMPLETIONS   DURATION   AGE
job.batch/demo-policyf8wxc--risk-27857250        1/1           40s        14m
job.batch/demo-policyf8wxc--risk-27857253        1/1           40s        11m
job.batch/demo-policyf8wxc--risk-27857256        1/1           39s        8m50s
job.batch/demo-policyf8wxc--risk-27857259        1/1           40s        5m50s
job.batch/demo-policyf8wxc--risk-27857262        1/1           40s        2m50s
job.batch/demo-policyj8cpc--inspector-27857250   1/1           5s         14m
job.batch/demo-policyj8cpc--inspector-27857253   1/1           6s         11m
job.batch/demo-policyj8cpc--inspector-27857256   1/1           5s         8m50s
job.batch/demo-policyj8cpc--inspector-27857259   1/1           6s         5m50s
job.batch/demo-policyj8cpc--inspector-27857262   1/1           5s         2m50s
```

ダッシュボードを見てみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2326226/374f3040-2b02-0be8-e5bc-16f9959f9a90.png)

イメージに含まれる脆弱性の検査結果が時系列で表示されます（ここでは時間によって変わらず）。
また、リスク分析の結果、このコンテナには239 の脆弱性が含まれていました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2326226/99cef08f-89d3-02bf-baa9-4a6dc2d394f8.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2326226/26f938c8-2cd7-3793-d6f5-71da9e3e4c39.png)

kube-bench の結果もグラフィカルに表示されます・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2326226/a7951468-acd8-4415-d8b2-f8723494e1df.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2326226/d8922110-4820-f070-7c2e-940c72fa72d2.png)

実際にはさまざまなアプリケーションが次々とデプロイされていくわけですが、それらに対してこのようなセキュリティリスクの可視化は効果的だと思いますし、時系列で表示できることから、こうしたセキュリティリスクを徐々に削減していくという効果測定ができるようになるため、アクションをより定量的に定めることができるようになります。ただ、やはりまだアナウンスされたばかりということもあり、機能的には足りない部分も多いため今後に期待です。

## おわりに
本記事では、Cloud Native Security Inspector について紹介しました。興味を持った方は下記ブログやVM Explore のセッションも是非覗いてみてください。
- https://octo.vmware.com/project-narrows/
- https://octo.vmware.com/project-narrows-and-container-security/
- https://www.vmware.com/explore/video-library/video-landing.html?sessionid=1652103969454001I5hE&videoId=6311552390112