OpenShiftとサンプルアプリで始めるRed Hat Advanced Cluster Security for Kubernetes(RHACS)入門

# はじめに
Red Hatは、Kubernetes環境で動作しているコンテナのセキュリティ対策状況を管理する **Red Hat Advanced Cluster Security for Kubernetes(RHACS)** という製品を展開しています。
今回は2回の記事に分けて、**RHACSのインストール**から、**RHACSを使ったサンプルアプリのセキュリティ対策方法**についてまとめていこうと思います。

この記事では、RHACSを使ったサンプルアプリのセキュリティ対策手順についてまとめていきます。

* 前編：[OpenShiftへのRed Hat Advanced Cluster Security for Kubernetes(RHACS)のインストール手順](https://qiita.com/shin7446/items/69ba8cb23339031d9e40)
* 後編：[OpenShiftとサンプルアプリで始めるRed Hat Advanced Cluster Security for Kubernetes(RHACS)入門](https://qiita.com/shin7446/items/e0aeb58625b4586274f7)(当記事)



RHACSの概要については、赤帽エンジニアブログの以下のページにて詳しく書かれていますので、一読いただくと作業のイメージがしやすいかと思います。
[RHACS (Red Hat Advanced Cluster Security for Kubernetes) のご紹介](https://rheb.hatenablog.com/entry/2022/03/23/RHACS_%28Red_Hat_Advanced_Cluster_Security_for_Kubernetes%29_%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6%E3%81%94%E7%B4%B9%E4%BB%8B)  


# 前提条件
今回は[前編の記事](https://qiita.com/shin7446/items/69ba8cb23339031d9e40)にて作成したOpenShift&RHACS環境を使います。
その他、ローカル端末に以下のコマンドをインストールしておいてください。
* oc
* kubectl
* kustomize

# 目次


# サンプルアプリのデプロイ
まずはセキュリティ対策を進めるためのサンプルアプリをデプロイします。
今回は以下のGitHubにアップしたものを利用します。
https://github.com/skitamura7446/sample-blog-for-rhacs-tutorial
※サンプルアプリは[Django Girlsのチュートリアル](https://tutorial.djangogirls.org/ja/)をベースに多少編集を施したものになります。
Web+APP+DBの一般的なWeb3層アプリになっています。
アプリケーションの操作方法については[こちら](https://github.com/skitamura7446/sample-blog/blob/main/README.md#%E6%93%8D%E4%BD%9C%E6%96%B9%E6%B3%95)を参照ください。

![sample-blog](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/fb810bd0-fcdc-c9ea-7525-485a6dc00b6f.png)


まずは上記リポジトリをローカルにCloneします。
```
git clone https://github.com/skitamura7446/sample-blog-for-rhacs-tutorial.git
```

つぎにOpenShiftにログインし、Project(Namespace)を作成します。
```
oc new-project sample-blog
```
```出力結果.
Now using project "sample-blog" on server "https://api.aws-cluster01.xxx.com:6443".
You can add applications to this project with the 'new-app' command. For example, try:
    oc new-app rails-postgresql-example
to build a new example application in Ruby. Or use kubectl to deploy a simple Kubernetes application:
    kubectl create deployment hello-node --image=k8s.gcr.io/serve_hostname
```

では実際にサンプルアプリをデプロイしていきます。
今回はローカルにCloneしたソースコードとDockerfileを使ってOpenShift上でBuildを行います。
まずはNginx(Webコンテナ)のBuildConfigを作成します。
```
oc new-build --name=nginx --strategy=docker --binary
```
```出力結果.
    * A Docker build using binary input will be created
      * The resulting image will be pushed to image stream tag "nginx:latest"
      * A binary build was created, use 'oc start-build --from-dir' to trigger a new build

--> Creating resources with label build=nginx ...
    imagestream.image.openshift.io "nginx" created
    buildconfig.build.openshift.io "nginx" created
--> Success
```

BuildConfigとImageStreamが作成されたので、実際にBuildしていきます。
```
cd sample-blog/default/docker/nginx/
oc start-build nginx --from-dir=. --follow
```

これでコンテナのビルドが完了し、OpenShift上のコンテナレジストリにイメージが格納されました。
```
oc get istag
NAME           IMAGE REFERENCE                                                                                                                              UPDATED
nginx:latest   image-registry.openshift-image-registry.svc:5000/sample-blog/nginx@sha256:e952a1077decdc779f645962b5ffb3aa0e5819efd1d16d304e981ec68dad7bdf   15 seconds ago
```

同様にDjango(Appコンテナ)もデプロイしていきます。
```
oc new-build --name=django --strategy=docker --binary
cd ../django/
oc start-build django --from-dir=. --follow
```

これでサンプルアプリのコンテナイメージが作成できました。　

```
oc get istag
NAME            IMAGE REFERENCE                                                                                                                               UPDATED
django:latest   image-registry.openshift-image-registry.svc:5000/sample-blog/django@sha256:2ad792fccaf721b5ac894bfcce0e12485f9f668b02bf24c58e30a531e1694ab8   6 seconds ago
nginx:latest    image-registry.openshift-image-registry.svc:5000/sample-blog/nginx@sha256:e952a1077decdc779f645962b5ffb3aa0e5819efd1d16d304e981ec68dad7bdf    4 minutes ago
```

ではk8sマニフェストを使ってデプロイしていきます。
マニフェストのあるディレクトリに移動します。
```
cd ../../k8s/base/
```
ディレクトリ構成は以下の通りです。
```
tree
.
├── django-deployment.yaml
├── django-service.yaml
├── entrypoint.sh
├── kustomization.yaml
├── mysql-service.yaml
├── mysql-statefulset.yaml
├── nginx-deployment.yaml
├── nginx-route-template.yaml
├── nginx-service.yaml
├── redis-service.yaml
└── redis-statefulset.yaml
```

デプロイの前に、アプリをインターネット上に公開するためのRouteのk8sマニフェストを作成します。
`entrypoint.sh`を実行して、`nginx-route-template.yaml`から`nginx-route.yaml`を作成します。
※実行前にOpenShiftにログインしておいてください。
```
./entrypoint.sh
```

entrypoint.shでは、OpenShiftのベースドメインを取得してrouteの`spec.host`に反映させています。
```entrypoint.sh
#!/bin/bash

export BASE_DOMAIN=$(oc get ingresses.config/cluster -o jsonpath={.spec.domain})
envsubst < nginx-route-template.yaml > nginx-route.yaml
```

実行するとnginx-route.yamlが作成されます。
```nginx-route.yaml
apiVersion: route.openshift.io/v1
kind: Route
metadata:
  labels:
    app: sample-blog
  name: nginx
  namespace: sample-blog
spec:
  host: sample-blog.apps.aws-cluster01.xxxxx.com
  port:
    targetPort: 8080
  to:
    kind: Service
    name: nginx
    weight: 100
  wildcardPolicy: None
```

マニフェストの準備もできたので、kustomizeを使ってデプロイします。
applyの際に`-k`オプションを使うことでkustomization.yamlをベースにデプロイできます。
```
oc apply -k .
```
```出力結果.
service/django created
service/mysql created
service/nginx created
service/redis created
deployment.apps/django created
deployment.apps/nginx created
statefulset.apps/mysql created
statefulset.apps/redis created
route.route.openshift.io/nginx created
```

しばらく待ってからrouteのアドレスを取得してブラウザでアクセスしてみます。
```
oc get route nginx -o jsonpath="{.status.ingress[0].host}"
```
![sample-blog](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/d0fd3cd7-c6e9-9eec-b9b9-e54b5a7f0568.png)

これで無事にサンプルアプリをデプロイできました。
アプリケーションの操作方法については[こちら](https://github.com/skitamura7446/sample-blog/blob/main/README.md#%E6%93%8D%E4%BD%9C%E6%96%B9%E6%B3%95)に記載していますので、一通り触ってみてください。


# RHACSを使ったサンプルアプリのセキュリティ対策
さて、実際にサンプルアプリがデプロイできたので、このアプリのセキュリティ状況を確認していきましょう。
以下のコマンドでRHACSのコンソールのURLを取得してにブラウザでアクセスしてみます。
```
oc get route central -n stackrox -o jsonpath="{.status.ingress[0].host}"
```

![RHACSダッシュボード](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/98053979-21de-8a99-cddf-6cce08475983.png)

RHACSは脆弱性管理、DevSecOps、ネットワークセグメンテーション、リスク分析など、多角的な視点でKubernetes環境のセキュリティ対策状況を確認することができます。
ただ逆にできることが多いため、何から手をつけていいか分からないこともあるかと思います。
ここではあくまで入門編になりますが、RHACSを使ったセキュリティ対策を進め方の一例をご紹介します。

## Violations
まずはViolationsを確認して、先ほどデプロイしたサンプルアプリのポリシー準拠状況を確認してみます。
画面左の"Violations"を選択して、フィルターで`Namespace:` `sample-blog`を入力してみましょう。`Severity`で並び替えすると見やすくなります。
![Violations](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/d6651d44-3ca6-33ea-dde0-123b06f27a9b.png)

見ていただくとわかる通り、**Violation**はポリシーに基づきDeploymentやStatefulsetsなどのWorkloadリソースをチェックし、違反している場合はポリシーに従い重要度をつけて表示する機能になっています。
今回はRHACSをインストールした状態のままなので、ここに表示されているのは**RHACSのデフォルトのポリシーに違反したリソース**ということになります。

デフォルトのポリシーについてもコンソール上で確認可能です。
左メニューの"Platform Configuration"→"Policies"を選択すると、デフォルトを含むポリシー一覧が確認できます。
![policies](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/d0e5b358-4fd4-49a0-648d-f1cb4e727730.png)

Severityで重要度順に並べ替えてみましょう。
![policies severity](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/509e1661-92a2-4da4-16cf-502ae6ccb975.png)
みてみると「特権コンテナの実行」や、非常に危険度の高い脆弱性（Spring4Shell/Log4Shell/Apache Struts）については`Critical`に設定されています。`High`ではマニフェストの環境変数に`SECRET`や`PASSWORD`を含むものが平文で書かれていることなどをチェックしているようです。
ちなみに執筆時(2022/07)ではデフォルトポリシーは以下の項目数となっています。

| Severity | Policies |
|:-:|:-:|
|Critical|4|
|High|28|
|Medium|24|
|Low|25|

もちろん、RHACSでは[既存のポリシーを変更](https://access.redhat.com/documentation/ja-jp/red_hat_advanced_cluster_security_for_kubernetes/3.70/html/operating/modify-existing-security-policies_manage-security-policies)したり[カスタムポリシーを作成する](https://access.redhat.com/documentation/ja-jp/red_hat_advanced_cluster_security_for_kubernetes/3.70/html/operating/create-custom-policies)ことも可能ですが。いきなり自分達でポリシーを定義することも難しいかと思いますので、まずはデフォルトのポリシーをベースに対策を進めるとイメージがつきやすいかと思います。


では少し戻って、`Namespace:` `sample-blog`で違反しているポリシーを実際に確認していきます。
以下はポリシーの中でも`Severity:High`のものを抽出したものです。
![severity high](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/0b20fa53-2384-b3c0-17e6-2151451f0587.png)

みていただくと4つのサービス全てで`Fixable Severity at least Important`というポリシーに違反しています。
一番上のmysqlのリンクをクリックして、具体的にどんなポリシーに違反しているのかを確認してみます。

先に`Policy`タブを選択すると、このポリシーの概要を確認できます。
深刻度ランクが`Important`以上の修正可能な脆弱性が存在する場合に違反となるポリシーであることが分かります。
![Policy](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/21195da4-9238-7c1a-7c3e-81f700ba7d39.png)

`Violation`タブに移動すると、具体的にどのCVEの脆弱性があるかを確認できます。この違反をクリアするには、ここに表示されている脆弱性への対策を行う必要があることが分かります。
![Violation](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/aabd9659-31c2-cc57-e7c1-45fd0b65226c.png)


## Vulnerability Management

なお、RHACSでは**Vulnerability Management**という、コンテナイメージの脆弱性に対してより詳しく確認できる機能があります。
左側メニューの"Vulnerability Management"→"Dashboard"を選択します。
![Vulnerability Management Dashboard](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/153d8382-2729-da91-2f0d-8063fc5576d6.png)

このダッシュボードでクラスター上にデプロイされているリソース（が使っているコンテナイメージ）の脆弱性対策状況が確認できます。
が、情報量がかなり多いのでもう少し絞ってみます。
右上の"APPLICATION & INFRASTRUCTURE"から"Deployments"を選択します。その後`Namespace:` `sample-blog`でフィルターをかけると、サンプルアプリのDeployment(&Statefulsets)の脆弱性情報が確認できます。
![Deployments](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/7126587b-ae2c-a31d-2c3a-cbd4865a625e.png)

では先ほど確認したmysqlの情報をみてみます。該当のDeploymentをクリックすると、以下のようなウィンドウが表示されます。
![mysql](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/e1cc20aa-438f-35f2-9086-9e3f835bb66d.png)

中央円グラフの"POLICY VIOLATIONS BY SEVERITY"では、現在違反しているポリシー数が確認でき、右側円グラフではこのコンテナイメージが持つ脆弱性を確認できます。
Deployment Findingsでは違反しているポリシーや、FIXABLE（修正可能）な脆弱性情報を確認できます。試しにFIXABLEタブを選択すると、以下のように**Violations**で確認した脆弱性情報が記載されています。
このように、コンテナの脆弱性状況は基本に**Vulnerability Mangement**で確認し、ポリシーによってアラートを発火させる、というのが一般的な使い方になります。
![fixable](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/fe011a80-f7d6-f48d-92f3-58fdfda8c41d.png)

行ったり来たりになりますが、一個前の画面の"POLICY VIOLATIONS BY SEVERITY"の右上にある"VIEW ALL"を選択すると、このRHACSで設定されている脆弱性関連のポリシー一覧と、このDeploymentにおける各ポリシーへのステータスが確認できます。
みてもらうと、デフォルトでは脆弱性関連のポリシー（のうち有効化されているもの）は5つあり、その中の1つのポリシーに違反していることがわかります。
![POLICIES](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/099b91e8-813b-f65b-695f-1e2dd5cb0bd8.png)



## コンテナイメージの脆弱性への対策
コンテナイメージに対して修正可能な脆弱性がひそんでいることがわかったので、実際に対策していきましょう。
まずはこのコンテナイメージはどのベースイメージからつくられているのかを確認します。

先ほどgit clone　したリポジトリからDockerfileやk8sマニフェストを見ていきます。

### Nginx
　Nginxは以下のファイルでベースイメージを確認できます。
```
cat default/docker/nginx/Dockerfile
```
```Dockerfile
FROM docker.io/nginx:1.20

COPY nginx.conf /etc/nginx/nginx.conf
COPY default.conf /etc/nginx/conf.d/default.conf

USER 1001
EXPOSE 8080

CMD nginx -g "daemon off;"
```
どうやらdokcer.ioから取得しているようです。

### Django
DjangoもNginx同様、DockerビルドしているのでDockerfileを確認します。
```
cat default/docker/django/Dockerfile
```
```Dockerfile
FROM docker.io/python:3.9

ADD ./app .
RUN pip install -r requirements.txt

ENTRYPOINT /bin/bash docker-entrypoint.sh
```
同じくdocker.ioです。

### Mysql / Redis

MysqlとRedisはビルドせず、外部のレジストリから取得したイメージをそのままデプロイしているため、`statefulsets.yaml`を確認します。
```
cat default/k8s/base/mysql-statefulset.yaml
cat default/k8s/base/redis-statefulset.yaml
```
```mysql-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  labels:
    app: sample-blog
    app.kubernetes.io/part-of: sample-blog
spec:
...omit...
    spec:
      containers:
      - name: mysql
        image: docker.io/mysql:8.0.26
...omit...
```
```redis-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
  labels:
    app: sample-blog
    app.kubernetes.io/part-of: sample-blog
spec:
...omit...
    spec:
      containers:
      - name: redis
        image: docker.io/redis:6.0.8
...omit...
```
もうわかっていたかと思いますが、これらもdocker.ioからのイメージになります。

これらは全てdocker.ioにある**公式のイメージ**になりますが、このように採用するバージョンによっては修正可能な脆弱性が残っている場合もあるため、以下のような対策が必要になります。
* 脆弱性対策が施されたイメージを採用する
* 定期的に最新のイメージに更新する
* Dockerfileにて該当のPackageを更新するステップを含める 

今回は取り急ぎ、ひとつ目の"脆弱性対策が施されたイメージを採用する"にて対策してみます。

#### Red Hatが提供するイメージ
Red Hatでは独自に脆弱性対策を施したベースイメージを提供しております。
これらは[Ecosystem Catalog](https://catalog.redhat.com/software/containers/search?p=1&vendor_name=Red%20Hat)で確認でき、イメージの検索やイメージごとのセキュリティ対策状況を確認できます。
またこのイメージをOpenShiftなどのRed Hat基盤上で利用した場合、イメージに含まれるパッケージに関するサポートを受けることができます。
そのためOpenShiftを利用する際は、まずはこのイメージの利用を検討するといいかと思います。


では実際にRed Hatが提供するイメージを使ってみましょう。
それぞれのイメージをRed Hatのイメージに書き換えて再デプロイしてみます。

### Nginx
Dockerfileを書き換えます。
```
vi default/docker/nginx/Dockerfile
```
```Dockerfile
FROM registry.access.redhat.com/ubi8/nginx-120

COPY nginx.conf /etc/nginx/nginx.conf
COPY default.conf /etc/nginx/conf.d/default.conf

USER 1001
EXPOSE 8080

CMD nginx -g "daemon off;"
```

再ビルドします。今回はタグをv0.1としています。
```
cd defalut/docker/nginx
oc new-build --name=nginx-2 --strategy=docker --binary --to=nginx:v0.1
oc start-build nginx-2 --from-dir=. --follow
```
ImageStreamを見るとv0.1タグのイメージがビルドできました。
```
oc get is
```
```出力結果.
NAME     IMAGE REPOSITORY                                                      TAGS          UPDATED
django   image-registry.openshift-image-registry.svc:5000/sample-blog/django   latest        26 hours ago
nginx    image-registry.openshift-image-registry.svc:5000/sample-blog/nginx    v0.1,latest   22 seconds ago
```

### Django
Nginxと同様、Dockerfileを書き換えます。
```
cd ../django/
vi Dockerfile
```
```Dockerfile
FROM registry.access.redhat.com/ubi8/python-39
ADD ./app .
RUN pip install -r requirements.txt

ENTRYPOINT /bin/bash docker-entrypoint.sh
```

再ビルドします。
```
oc new-build --name=django-2 --strategy=docker --binary --to=django:v0.1
oc start-build django-2 --from-dir=. --follow
```

ImageStreamを確認します。
```
oc get is
```
```出力結果.
NAME     IMAGE REPOSITORY                                                      TAGS          UPDATED
django   image-registry.openshift-image-registry.svc:5000/sample-blog/django   v0.1,latest   5 seconds ago
nginx    image-registry.openshift-image-registry.svc:5000/sample-blog/nginx    v0.1,latest   43 minutes ago
```

### MySQL / Redis
MySQLは`mysql-statefulset.yaml`を修正します。
```
cd ../../k8s/base/
vi mysql-statefulset.yaml
```
```mysql-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  labels:
    app: sample-blog
    app.kubernetes.io/part-of: sample-blog
spec:
...omit...
    spec:
      containers:
      - name: mysql
        image: registry.redhat.io/rhel8/mysql-80
...omit...
```
同じくRedisも`redis-statefulset.yaml`を修正します。
```
vi redis-statefulset.yaml
```
```
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
  labels:
    app: sample-blog
    app.kubernetes.io/part-of: sample-blog
spec:
...omit...
    spec:
      containers:
      - name: redis
        image: registry.redhat.io/rhel8/redis-6
...omit...
```

イメージの書き換えが完了したので、実際にデプロイしてみます。
kustomization.yamlの`images.newTag`を書き換えることでマニフェスト側の修正なしでイメージのタグを変更できます。
```
vi kustomization.yaml
```
```kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: sample-blog

images:
- name: image-registry.openshift-image-registry.svc:5000/sample-blog/django
  newName: image-registry.openshift-image-registry.svc:5000/sample-blog/django
  newTag: v0.1
- name: image-registry.openshift-image-registry.svc:5000/sample-blog/nginx
  newName: image-registry.openshift-image-registry.svc:5000/sample-blog/nginx
  newTag: v0.1
...omit...
```

```
oc apply -k .
```
```出力結果.
service/django unchanged
service/mysql unchanged
service/nginx unchanged
service/redis unchanged
deployment.apps/django configured
deployment.apps/nginx configured
statefulset.apps/mysql configured
statefulset.apps/redis configured
route.route.openshift.io/nginx unchanged
```

しばらくして、Podが正常に起動していることを確認します。
```
oc get pods
```
```出力結果.
NAME                      READY   STATUS      RESTARTS   AGE
django-5cd45cd58c-wk4gm   1/1     Running     0          101s
mysql-0                   1/1     Running     0          96s
nginx-d64cf9495-d5dn5     1/1     Running     0          101s
redis-0                   1/1     Running     0          96s
```

この状態で**Violations**を確認してみましょう。
![Violations](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/ddc2af88-a7b9-827c-e107-1a22907de167.png)
元々あった`Fixable Severity at least Important`が消えていますね。

**Vulnerability Management**も確認してみます。
![Vulnerability Management](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/1614269e-5628-6260-9c60-84a83cedb2b0.png)
`Policy Status`がPassになりました！

このようにRed Hatのイメージを使うだけで脆弱性対策が施されたイメージで安全にアプリケーションをデプロイできます。
ただし以下のように注意事項もあるため、チームのセキュリティポリシーに合わせて適切なベースイメージを選択してください。
* Red Hatが提供していないイメージもある
* 使いたいバージョンのイメージがまだ提供されていないケースもある
    * たとえば、Redisの最新メジャーバージョンは7系ですが、Red Hatの提供イメージの最新は6系です。
* 全ての脆弱性が対策されている訳ではないため、必要に応じてビルド時に別途対策が必要


## Configuration Management
RHACSでは、コンテナイメージの脆弱性だけでなくKubernetesレイヤのマニフェスト構成をチェックし、セキュリティ的にリスクのある設定があるかどうかのチェックを行うことができます。

先ほどの**Violations**をもう一度みてみると、`High`と`Medium`の違反ポリシーはKubernetesの設定周りのものであることが分かります。
* `Environment Variable Contains Secret`
* `Pod Service Account Token Automatically Mounted`
* `No resource requests or limits specified`
![Violations](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/ddc2af88-a7b9-827c-e107-1a22907de167.png)


それぞれの違反ポリシーについて少し深掘りしていきます。
### `Environment Variable Contains Secret`
これはDeploymentの環境変数内に`SECRET`や`PASSWORD`のような文字を含むkeyが存在すると違反となるポリシーです。
`django`と`mysql`でこの違反が出ていますが、これはMySQLのパスワード情報を環境変数でそのまま渡していることが原因です。
`django-deployment.yaml`をみると`MYSQL_PASSWORD`や`BLOG_ADMIN_PASSWORD`といった環境変数が設定されています。

```django-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: django
  annotations:
    app.openshift.io/connects-to: '["mysql", "redis"]'
  labels:
    app: sample-blog
    app.kubernetes.io/part-of: sample-blog
spec:
  replicas: 1
  selector:
    matchLabels:
      app: django
  template:
    metadata:
      labels:
        app: django
    spec:
      initContainers:
      - name: init-connection
       ...omit...
        env:
        - name: MYSQL_HOST
          value: mysql
        - name: MYSQL_USER
          value: sampleblog
        - name: MYSQL_PASSWORD
          value: sampleblog
      containers:
      - name: django
        ...omit...
        env:
        - name: MYSQL_USER
          value: sampleblog
        - name: MYSQL_PASSWORD
          value: sampleblog
        - name: MYSQL_DATABASE
          value: sampleblog
        - name: MYSQL_HOST
          value: mysql
        - name: MYSQL_PORT
          value: "3306"
        - name: REDIS_HOST
          value: redis
        - name: BLOG_ADMIN_USER
          value: admin
        - name: BLOG_ADMIN_EMAIL
          value: admin@sample.com
        - name: BLOG_ADMIN_PASSWORD
          value: admin
        imagePullPolicy: Always
```

そのため、機密情報となるものはKubernetesの`Secret`で渡す方式にしましょう。



### `Pod Service Account Token Automatically Mounted`
このポリシーは、Podに対して自動的にKubernetesクラスターの`default`サービスアカウントを付与している場合に違反となります。
Kubernetesのセキュリティのベストプラクティスとして、PodからKubernetes APIを操作する必要がない際は`default`サービスアカウントは極力使わず、別のサービスアカウントを使うか`automountServiceAccountToken: false`設定でトークンをPodにマウントしない対策が推奨されています。

こういった根拠については、RHACSの"Policy overview"の**Rationale**を確認できます。
![Rationale](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/6dfc64fb-97e0-4003-d7fa-f32b483ff40c.png)

ということで、今回は別のサービスアカウントを作成し、`Deloyment`にてサービスアカウントを指定する形を取ります。

### `No resource requests or limits specified`
これはイメージしやすいものかと思います。それぞれの`deployment.yaml`や`statefulset.yaml`をみてもらうと分かりますが、`resources`の設定をしていないのでこの違反が出ています。適切なQuota設定を実施することが必要です。


### ポリシーに準拠したk8sマニフェストの適用
上記3つのポリシーに対応したマニフェストは、cloneしたリポジトリの`improved`に格納しています。
先述のベースイメージも書き換えも含めて反映されています。
※`network-policy.yaml`は後ほど説明します。
```
cd ../../../improved/k8s/base
tree
```
```出力結果.
.
├── django-configmap.yaml
├── django-deployment.yaml
├── django-secret.yaml
├── django-service.yaml
├── entrypoint.sh
├── kustomization.yaml
├── mysql-configmap.yaml
├── mysql-secret.yaml
├── mysql-service.yaml
├── mysql-statefulset.yaml
├── network-policy.yaml
├── nginx-deployment.yaml
├── nginx-route-template.yaml
├── nginx-service.yaml
├── redis-configmap.yaml
├── redis-service.yaml
├── redis-statefulset.yaml
└── sample-blog-sa.yaml
```

デプロイ前に、今デプロイされているアプリを削除します。
```
oc delete -k ../../../default/k8s/base/
```

次に`nginx-route.yaml`を作成します。
```
./entrypoint.sh
```

ではデプロイします。
```
oc apply -k .
```
```出力結果.
serviceaccount/sample-blog created
configmap/django-mysql-conf created
configmap/mysql-conf created
configmap/redis-conf created
secret/django-mysql-pass created
secret/mysql-pass created
service/django created
service/mysql created
service/nginx created
service/redis created
deployment.apps/django created
deployment.apps/nginx created
statefulset.apps/mysql created
statefulset.apps/redis created
route.route.openshift.io/nginx created
```

しばらくして、Podが正常に起動していることを確認します。
```
oc get pods
```
```出力結果.
NAME                      READY   STATUS      RESTARTS   AGE
django-74745f9ff6-jj84v   1/1     Running     0          71s
mysql-0                   1/1     Running     0          71s
nginx-5b7cff7576-7b9vk    1/1     Running     0          71s
redis-0                   1/1     Running     0          71s
```

では**Violations**を確認します。
![Violations](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/975d21e8-7d4e-3dbe-7962-643d8144d694.png)

`High`と`Medium`が全て消えました！
今回は`Low`は残したままの状態になっていますので、もし手元に試せる環境があればぜひ対策を施してみてください。

ちなみにこれまで対応したKuberetesの設定周りの状況については、RHACSの**Configuration Management**で確認できます。
画面左メニューの**Configuration Management**を選択します。
![Configuration Management](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/20f61e89-ab1d-e633-1f33-77a5835068c4.png)

ここで全クラスターの設定状況を確認できますが、かなり俯瞰的なものなので、**Vulnerability Management**と同様、対象を絞り込みます。
右上の"APPLICATION & INFRASTRUCTURE"から"Deployments"を選択します。その後`Namespace:` `sample-blog`でフィルターをかけると、サンプルアプリのDeployment(&Statefulsets)の設定情報が確認できます。
![deployments](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/7f10a97e-a7a1-b27b-2d63-2d2bbcbdb00a.png)

詳細を確認する場合は個別のDeploymentを選択します。
ポリシー違反状態ですが、先ほど残したLowのポリシーに引っ掛かっていることが分かります。
このように、**Vulnerability Management**同様、アラートはポリシーのViolationで拾い、状況を**Configuration Management**で確認する、という使い方になるかと思います。
![Summary](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/5809689d-6ff7-439c-f239-71194c71ed38.png)


## Network Policy Management
一通りデフォルトのポリシー違反のうち優先度の高いものは対応できましたが、もうひとつ、RHACSの機能である**Network Policy**関連の機能を試してみます。
RHACSではアプリケーションの通信フローをチェックし、適切なNetwork Policyを自動生成する機能を具備しています。

左メニューから**Network Graph**を選択し、`Namespaces`プルダウンで`Sample-blog`をチェックすると、以下のように4つのサービスを確認できる画面が表示されます。
![Network Graph](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/c28ec314-c0ed-103d-035b-2a9a22950e53.png)

この画面にて右上の**Network Policy Simulator**を選択すると、以下のような画面がでるので、**Generate and simulate network policies**を選択します。
![Network Policy Simulator](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/2036025b-5624-7427-d937-5dc24e85187e.png)

するとクラスター内の通信状況をベースに自動でNewtwork Policyが作成されます。
![スクリーンショット 2022-07-13 11.26.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/f0bf1747-aa7d-83ad-1767-4dfdbdc5d622.png)

作成されたNetwork Policyは以下の通りです。5つ出力されていますが、`Smaple-blog`Namespaceに関するものは4つになります。
中身を読むと、以下のようなNW制御を実現するNetwork Policyが作成されるようです。
* stackrox-generated-mysql
    * djangoからmysqlへの3306ポート通信
* stackrox-generated-redis
    * djangoからredisへの6379ポート通信
* stackrox-generated-nginx
    * ingress pod(Route)からnginxへの8080ポート通信
* stackrox-generated-django
    * nginxからdjangoへの8000ポート通信

ターゲットやポート番号についてはServiceから取得できそうですが、どのPodからの通信があるか、といった点は実際の通信フローからでないと取得できないので、この点はRHACSのNetwork Policy作成機能の大きな特徴かと思います。





```network-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  creationTimestamp: "2022-07-13T02:30:51Z"
  labels:
    network-policy-generator.stackrox.io/generated: "true"
  name: stackrox-generated-mysql
  namespace: sample-blog
spec:
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: django
    ports:
    - port: 3306
      protocol: TCP
  podSelector:
    matchLabels:
      app: mysql
  policyTypes:
  - Ingress

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  creationTimestamp: "2022-07-13T02:30:51Z"
  labels:
    network-policy-generator.stackrox.io/generated: "true"
  name: stackrox-generated-rhacs-operator-controller-manager
  namespace: rhacs-operator
spec:
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: openshift-kube-apiserver-operator
      podSelector:
        matchLabels:
          app: kube-apiserver-operator
    ports:
    - port: 9443
      protocol: TCP
  - ports:
    - port: 8081
      protocol: TCP
  podSelector:
    matchLabels:
      app: rhacs-operator
      control-plane: controller-manager
  policyTypes:
  - Ingress

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  creationTimestamp: "2022-07-13T02:30:51Z"
  labels:
    network-policy-generator.stackrox.io/generated: "true"
  name: stackrox-generated-redis
  namespace: sample-blog
spec:
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: django
    ports:
    - port: 6379
      protocol: TCP
  podSelector:
    matchLabels:
      app: redis
  policyTypes:
  - Ingress

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  creationTimestamp: "2022-07-13T02:30:51Z"
  labels:
    network-policy-generator.stackrox.io/generated: "true"
  name: stackrox-generated-nginx
  namespace: sample-blog
spec:
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: openshift-ingress
      podSelector:
        matchLabels:
          ingresscontroller.operator.openshift.io/deployment-ingresscontroller: default
    ports:
    - port: 8080
      protocol: TCP
  podSelector:
    matchLabels:
      app: nginx
  policyTypes:
  - Ingress

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  creationTimestamp: "2022-07-13T02:30:51Z"
  labels:
    network-policy-generator.stackrox.io/generated: "true"
  name: stackrox-generated-django
  namespace: sample-blog
spec:
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: nginx
    ports:
    - port: 8000
      protocol: TCP
  podSelector:
    matchLabels:
      app: django
  policyTypes:
  - Ingress
```

先ほどの画面で`Apply Network Policies`を選択すると、上記のNetwork Policyがデプロイされます。
ここでは`Sample-blog`Namespaceに閉じた設定にしたいのと、k8sマニフェストでGit管理するために、画面右上の"Download YAML"で取得した後、少し修正します。

修正後のファイルが以下になります。
```
cat network-policy.yaml
```
```network-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: nginx-network-policy
  namespace: sample-blog
spec:
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: openshift-ingress
      podSelector:
        matchLabels:
          ingresscontroller.operator.openshift.io/deployment-ingresscontroller: default
    ports:
    - port: 8080
      protocol: TCP
  podSelector:
    matchLabels:
      app: nginx
  policyTypes:
  - Ingress

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: django-network-policy
  namespace: sample-blog
spec:
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: nginx
    ports:
    - port: 8000
      protocol: TCP
  podSelector:
    matchLabels:
      app: django
  policyTypes:
  - Ingress

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: mysql-network-poliy
  namespace: sample-blog
spec:
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: django
    ports:
    - port: 3306
      protocol: TCP
  podSelector:
    matchLabels:
      app: mysql
  policyTypes:
  - Ingress

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: redis-network-policy
  namespace: sample-blog
spec:
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: django
    ports:
    - port: 6379
      protocol: TCP
  podSelector:
    matchLabels:
      app: redis
  policyTypes:
  - Ingress
```

kustomization.yamlを修正してデプロイします。
```
vi kustomization.yaml
---
network-policy.yamlのコメントアウトを消す
```
```
oc apply -k .
```
```出力結果.
serviceaccount/sample-blog unchanged
configmap/django-mysql-conf unchanged
configmap/mysql-conf unchanged
configmap/redis-conf unchanged
secret/django-mysql-pass unchanged
secret/mysql-pass unchanged
service/django unchanged
service/mysql unchanged
service/nginx unchanged
service/redis unchanged
deployment.apps/django unchanged
deployment.apps/nginx unchanged
statefulset.apps/mysql unchanged
statefulset.apps/redis unchanged
networkpolicy.networking.k8s.io/django-network-policy created
networkpolicy.networking.k8s.io/mysql-network-poliy created
networkpolicy.networking.k8s.io/nginx-network-policy created
networkpolicy.networking.k8s.io/redis-network-policy created
route.route.openshift.io/nginx unchanged
```

実際にデプロイされ、Network Graphを確認すると適用したNetwork　Policyを確認できます。
![network policy](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/724efe39-8583-a7b2-58ba-38e8e7d5d7b4.png)


## 番外編：コンテナの振る舞い検知
RHACSのポリシーチェックはコンテナイメージやK8sマニフェストだけでなく、コンテナ内のアプリケーションの挙動に対するものも存在します。
左側メニューの"Platform"→"Policies"を選択後、フィルターで`Lifecycle Stage:` `RUNTIME` と入力すると、実行中のコンテナに対するポリシーが表示されます。
画面を見ていただくとわかる通り、「kucectl execの実行」、「useraddの実行」、「暗号通貨マイニングプロセス」などのイベントを検知してViolationとしてアラートを発出することができます。
![RUNTIME](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/678bd8ac-2837-3b23-6544-176448f00b2c.png)

実際に実行中のPodにexecコマンドを実行して、useraddを試みてみます。
```
oc exec -it mysql-0 /bin/sh
```
```出力結果.
oc exec -it mysql-0 /bin/sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
```
```
sh-4.4$ useradd test
```
```出力結果.
useradd: Permission denied.
useradd: cannot lock /etc/passwd; try again later.
```

コンテナのユーザーIDがrootでないため、useraddは失敗しましたが、振る舞い検知としてアラートは発出されます。
実際に"Violations"に移動し、確認してみます。
![Violations](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/23278fd3-bca3-a017-3318-e807006190f5.png)

ご覧の通り、exec実行とuseradd実行の2つの違反が確認できます。
この機能も活用しながら、振る舞いを検知し早急に対策を施すことでセキュリティを強化できます。

# おわりに
今回はサンプルアプリを用いてRHACSの基本的な機能を確認しました。
RHACSは他にもCICDパイプラインとの連携やイメージ署名検証、3rdパーティツールとのインテグレーションなどさまざまな機能を具備しています。
Kuberentes環境のセキュリティレベルを向上してみたい方はこのツールの導入を検討してみてはいかがでしょうか。