Kubernetesにおけるコンテナイメージスキャン

[1.はじめに](#1-はじめに)
[2.検証環境の構成](#2-検証環境の構成)
[3.前作業](#3-前作業)
[4.minikubeのインストール](#4-minkubeのインストール)
[5.Gitlabのインストール](#5-Gitlabのインストール)
[6.Helmのインストール](#6-Helmのインストール)
[7.KubernetesとGitlabを連携](#7-KubernetesとGitlabを連携)
[8.CICDのパイプラインにTrivyによるコンテナイメージスキャンを追加](#8-CICDのパイプラインにTrivyによるコンテナイメージスキャンを追加)


#1. はじめに
この記事は、[NTTコムウェア Advent Calendar 2021](https://qiita.com/advent-calendar/2021/nttcomware) 23日目の記事です。
NTT コムウェアでセキュリティエンジニアの仕事をしている山原です。
この記事では、社内のProxy環境下でMinikubeを使用してKubernetes環境を構築し、
GitLabのCI/CDのパイプラインの中にコンテナのイメージスキャンを組み込んだ検証結果をまとめました。
KubernetesやCI/CDには興味があるけど、社内環境で構築するのがしんどいと思う人が参考にできる内容となっています。

また、コンテナイメージの脆弱性スキャンが必要な理由としては、DockerHubに公開されている400万イメージのうち51パーセントに重大な脆弱性を抱えています。
https://www.infoq.com/jp/news/2021/03/dockerhub-image-vulnerabilities/

DockerコンテナやKubernetesは、現在のエンジニアリングには欠かせない便利なツールとなっていますが、システムにコンテナの脆弱性を盛り込めば、外部からの攻撃対象となってしまいます。安全にコンテナイメージを使用するために、脆弱性スキャンを取り入れることを検討してください。
**皆様良きコンテナライフを!!**



##やりたいこと
オンプレ環境にGitlabを構築し、minikubeで作成したkubernetes環境と連携をします。
CICDのパイプラインの上でプライベートコンテナレジストリにおける脆弱性スキャンを実施し、
コンテナイメージの安全性を確かめます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2348308/a0dc2a1e-0cb5-770c-7334-6bfad52428a7.png)



#2. 検証環境の構成
OS:CentOS Stream release 8
GitLab CE 14.5.2
minikube version: v1.24.0
Docker Version 19.03.9




#3. 前作業
- サーバ証明書の設定
- Proxyの設定
- kubectlをインストール
- Dockerのインストール
- Dockerのレジストリの証明書を設定

## サーバ証明書の設定
社内で配布しているルート証明書を以下のディレクトリに配置する

```
cp ca.crt /etc/pki/ca-trust/source/anchors/
update-ca-trust extract
```

## Proxyの設定

/etc/profileにproxyの設定を追記する
no_proxyの設定まで実施すること

```
vi /etc/profile

PROXY='XXXXXXXXX:8080' # proxyサーバのドメインとポートを指定
export http_proxy=$PROXY
export HTTP_PROXY=$PROXY
export https_proxy=$PROXY
export HTTPS_PROXY=$PROXY
export no_proxy=127.0.0.1,localhost,192.168.1.1/16
```


## kubectlのインストール
1.yumにkubernetesのrepoを追加する

```
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF
```

2.kubectlをインストールする

```
yum install -y kubectl

インストール済み:
  kubectl-1.22.4-0.x86_64
```


## Dockerのインストール
1.インストール用のtarファイルをダウンロードする

```
wget --no-check-certificate  https://download.docker.com/linux/static/stable/x86_64/docker-19.03.9.tgz 
```

2.tarファイルを展開する

```
tar xzvf docker-19.03.9.tgz
```


3.コマンドを使用可能な状態にする

```
chown root:root docker/*
cp docker/* /usr/bin/
```


4.dockerサービスの設定をする

```
[root@hogehoge]#  cat << EOF > /usr/lib/systemd/system/docker.service
[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
BindsTo=containerd.service
After=network-online.target firewalld.service containerd.service
Wants=network-online.target
Requires=docker.socket
[Service]
Type=notify
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
ExecReload=/bin/kill -s HUP \$MAINPID
TimeoutSec=0
RestartSec=2
Restart=always
StartLimitBurst=3
StartLimitInterval=60s
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
Delegate=yes
KillMode=process

[Install]
WantedBy=multi-user.target
EOF
```

5.Dockerソケットを設定する

```
#cat << EOF > /usr/lib/systemd/system/docker.socket
[Unit]
Description=Docker Socket for the API
PartOf=docker.service

[Socket]
ListenStream=/var/run/docker.sock
SocketMode=0660
SocketUser=root
SocketGroup=docker

[Install]
WantedBy=sockets.target
EOF

# cat << EOF >/usr/lib/systemd/system/containerd.service
[Unit]
Description=containerd container runtime
Documentation=https://containerd.io
After=network.target

[Service]
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/bin/containerd
KillMode=process
Delegate=yes
LimitNOFILE=1048576
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity

[Install]
WantedBy=multi-user.target
EOF
```


##Dockerレジストリの証明書を設定

Proxy配下では使用するDockerレジストリに証明書を設定しないとコンテナイメージをpullすることができない。

1.証明書を格納するためのディレクトリを作成する

```
cd /etc/docker/certs.d
mkdir registry-1.docker.io
mkdir k8s.gcr.io
mkdir ghcr.io
```

2.ルート証明書を配置する

```
cp /tmp/ca.crt /etc/docker/certs.d/registry-1.docker.io
cp /tmp/ca.crt /etc/docker/certs.d/k8s.gcr.io
cp /tmp/ca.crt /etc/docker/certs.d/ghcr.io

```

3.Dockerに証明書を反映させる

```
sudo systemctl daemon-reload
sudo systemctl restart docker
```


#4. minikubeのインストール

1.swapをoffにする

```
swapoff -a
```

2.minikubeのインストール資材をダウンロードし、コマンドに実行権限を与える

```
wget https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
cp minikube-linux-amd64 /usr/local/bin/minikube
chmod 755 /usr/local/bin/minikube
```

3.minikubeのバージョンを確認する

```
minikube version
minikube version: v1.24.0
commit: 76b94fb3c4e8ac5062daf70d60cf03ddcc0a741b
```



4.minikubeをスタートする
今回はkvm2を指定する。Dockerを指定しても大丈夫
※**minikubeはroot実行できない**

```
minikube start --vm-driver=kvm2  --cache-images=false --docker-env HTTP_PROXY=$HTTP_PROXY --docker-env HTTPS_PROXY=$HTTPS_PROXY --listen-address=0.0.0.0
```


5.minikubeにも証明書をインストールする
一旦minikubeのシングルクラスタを削除して、所定のディレクトリにPEMファイルを配置する

```
minikube delete    
openssl x509 -in ca.crt -out tmp.der -outform DER
openssl x509 -in tmp.der -inform DER -out ca.pem -outform pem
cp ca.pem /home/${USER}/.minikube/certs/
minikube start --vm-driver=kvm2  --cache-images=false --docker-env HTTP_PROXY=$HTTP_PROXY --docker-env HTTPS_PROXY=$HTTPS_PROXY --listen-address=0.0.0.0
```

6.kubectlコマンドを使用できるようにminikubeのユーザコンテキストに切り替える

```
kubectl config use-context minikube
```


#5. Gitlabをインストールする
minikube上でGitlabのpodを作成すると、メモリの制約の問題でおすすめできない
今回はサーバOS上にGitlabをインストールする

1.インストール資材をダウンロードして実行する

```
dnf install gitlab-ce
```

2./etc/gitlab/gitlab.rbを編集し、外部からGitlabとコンテナレジストリを公開する

```
vi /etc/gitlab/gitlab.rb
external_url 'http://XXXXXXXXXXXXX:1010' ←　コメントを外して外部からアクセスできるURLを設定する。IPアドレスでも良い
registry_external_url 'http://XXXXXXXXXXXXX:2020'　←　コメントを外して外部からアクセスできるURLを設定する。IPアドレスでも良い
```


3.gitlabのコンフィグを読み直す

```
/opt/gitlab/embedded/bin/runsvdir-start &
gitlab-ctl reconfigure
```

4.Gitlabの初期パスワードを確認する
rootでログインするパスワードとなる

```
cat /etc/gitlab/initial_root_password

````

5.ブラウザで外部に公開したURLにアクセスし、適当な**グループ**と**プロジェクト**を作成する


#6. helmをインストールする
1.インストール資材をダウンロードする

```
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
```

2.資材を使ってインストールする


```
chmod 700 get_helm.sh
./get_helm.sh
```



#7. KubernetesとGitlabを連携

1.kubernetes上にgitlab-runner用のサービスアカウントを作成する

```
kubectl apply -f gitlab-admin-service-account.yaml
```


(gitlab-admin-service-account.yaml)

```
apiVersion: v1
kind: ServiceAccount
metadata:
  name: gitlab
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: gitlab-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: gitlab
    namespace: kube-system
```


2.Kubernetesと連携する
Gitlabにアクセスして、AdminエリアよりKubernetesを選択し、「Connect existing cluster」をクリックし、必要な情報を入力する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2348308/f262da85-0468-5419-1282-dc87e59ae7fe.png)


**Kubernetes cluster name: minikube**
**API URL: /home/XXXXX/.kube/config  のserverを参照する**
**CA Certificate : /home/XXXXX/.kube/config  のclient-certificateを参照する**
残りはデフォルトの設定


3.[Add Cluster]をクリックする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2348308/5378f957-1e69-1c75-287e-4ad1210e54d7.png)


4.gitlabrunnerのRegistrationTokenを確認する
作成したグループのSettingsよりCI/CDを選択し、Runnersの詳細を見ると[RegistrationToken]が表示されているので、コピーする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2348308/2623b384-f8b1-f9f0-c1bc-bfdee88aee9e.png)


5.Helmを使用してgitlab runnnerをデプロイする
runnerRegistrationTokenに先ほどコピーしたTokenを入力する

```
helm install --namespace gitlab gitlab-runner gitlab/gitlab-runner --set gitlabUrl=http://XXXXXXXXXXX:1010/  --set runnerRegistrationToken=XXXXXXXXXXXXX--set privileged=true --set rbac.create=true --set runners.config="
[[runners]]
  clone_url = \"http://xxxxxxxxxx:1010/\"
  [runners.kubernetes]
    image = \"ubuntu:16.04\"
 "
```


6.gitlab runnerが正常に立ち上がっているかを確認する
正常にRunning状態になっていることを確認しておく

```
$ kubectl get po -n gitlab
NAME                                           READY   STATUS    RESTARTS   AGE
gitlab-runner-gitlab-runner-8546485df9-f58c6   1/1     Running   0          6d20h
```


#8. CICDのパイプラインにTrivyによるコンテナイメージスキャンを追加

1.Gitlabのパイプラインの中でビルドをする時にDocker in Docker(Dind)を使用するため、
　コンテナが外部と疎通ができるように、Proxyと証明書の設定をしておく必要がある。
  [settings]-[CI/CD]-[Variables]に証明書を変数として登録しておく。（管理は厳重に！）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2348308/a206dac2-34dd-5798-eb0c-19fe562c2bd5.png)





2.Gitlabに作成したプロジェクトの上に**Dockerファイル**と**.gitlab-ci.yml**を作成すれば、
　コンテナのイメージスキャンは実施できる。
　今回は、どなたでもパイプライン上でイメージスキャンを実行できるように[環境準備]、[テスト]、[デプロイ]をスキップする。
　

```(.gitlab-ci.yml)
stages:
  - prepare
  - test
  - build
  - scan
  - deploy

prepare-job:
  stage: prepare
  script:
    - echo "Suceess"

test-job:
  stage: test
  script:
    - echo "Suceess test"

build-job:
  stage: build
  image: docker:dind
  variables:
    DOCKER_DRIVER: overlay2
    http_proxy: http://XXXXXXXXXXXX:8080
    https_proxy: http://XXXXXXXXXXXXX:8080
    NO_PROXY: 127.0.0.1,localhost,192.168.1.1/16
    DOCKER_HOST: /var/run/docker.sock
  services:
    - docker:dind
  script:
    - touch /usr/local/share/ca-certificates/root.crt
    - echo "${rootca}" >> /usr/local/share/ca-certificates/root.crt
    - update-ca-certificates
    - docker build ./-t (GitlabregistryのURI)/trivy-test2:1.0
    - dockerd --insecure-registry=XXXXXXXXXXX &
    - sleep 5
    - docker login　XXXXXXXX:2020 -u (Gitlabのユーザ） -p （Gitlabのパスワード）
    - docker build ./-t GitlabregistryのURI)/trivy-test1.0
    - docker push GitlabregistryのURI)t/trivy-test1.0

scan-job:
  stage: scan
  image: docker.io/aquasec/trivy
  variables:
    http_proxy: http://XXXXXXXXXXXX:8080
    https_proxy: http://XXXXXXXXXXXXX:8080
    NO_PROXY: "127.0.0.1,localhost,192.168.1.1/16"
    TRIVY_USERNAME: "Gitlabユーザ"
    TRIVY_PASSWORD: "Gitlabパスワード"
    TRIVY_NON_SSL: "true"
  script:
    - touch /usr/local/share/ca-certificates/root.crt
    - echo "${rootca}" >> /usr/local/share/ca-certificates/root.crt
    - update-ca-certificates
    - trivy GitlabregistryのURI)/trivy-test1.0
  artifacts:
    when: always
    reports:
      scan-job: gl-container-scanning-report.json


test-deploy:
  stage: deploy
  script:
    - echo "Suceess deploy"

```

(Dockerfile)

```Dockerfile
FROM node:8.11-alpine

WORKDIR /usr/src/app
ENV http_proxy="XXXXXXXX:8080"
ENV https_proxy="XXXXXXXXXXX:8080"

ARG NODE_ENV
ENV NODE_ENV $NODE_ENV

COPY package*.json /usr/src/app/
RUN npm install

COPY . /usr/src/app

ENV PORT 5000
EXPOSE $PORT
CMD [ "npm", "start" ]
```


3.**Dockerファイル**と**.gitlab-ci.yml**をGitlabの任意のプロジェクトにpushすれば、CICD＋コンテナイメージスキャンのジョブが自動起動する
パイプラインの実行中の画面

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2348308/250b1758-63fb-a1db-4e5b-232f1e7a3761.png)



4.パイプラインの実行が成功するとすべてグリーンのチェック状態となる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2348308/d05baa57-e652-a94c-36e0-913358016a9a.png)

5.イメージスキャンジョブの中身を見ると、脆弱性スキャンが実行されていることがわかる。
　gl-container-scanning-report.jsonとしてジョブの結果はレポートとして保存される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2348308/31ae1522-3b54-6819-4dd7-9e5fa0254f7b.png)