ちょっと話題の記事

FutureVuls で ECRのコンテナイメージを脆弱性スキャンしてみた

FutureVulsを使って、ECRのイメージを脆弱性スキャンしてみた。
2018.11.19

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

おはようございます、加藤です。FutureVulsを使ってAmazon EC2 Container Registry(以降、ECR)にアップロードされているコンテナイメージを脆弱性スキャンしてみました。

やってみた

スキャン用EC2インスタンス

スキャン用のEC2インスタンスをが必要です。

OSはAmazon Linux 2を使用し、ECRにアクセスする為のIAMロールを作成・関連付けします。

IAMロールの条件はAWS管理ポリシーAmazonEC2ContainerRegistryReadOnlyが関連付けされている事です。

インスタンスが作成できたら、FutureVulsのスキャナをインストールします。インストールコマンドはFutureVulsにログインして下記の操作を行うことで取得できます。

  1. 右上の人のアイコンをクリック
  2. グループ設定をクリック
  3. 左メニューのスキャナをクリック
  4. VULS_SCAN_MODEFASTに変更
  5. 表示されるコマンドをコピー

EC2インスタンスにログインしてコンテナスキャン用としてセットアップします。

# rootにスイッチユーザー
sudo su -
# パッケージの最新化
yum  -y update
# FutureVulsスキャナのインストール
curl -s http://installer.vuls.biz/vuls-installer.sh | VULS_SAAS_GROUPID="XXX" VULS_SAAS_TOKEN="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" VULS_SCAN_MODE="fast" bash -s inst
# Dockerとjq(後述のスクリプト内で使用)のインストール
yum -y install docker jq
# Dockerの起動
systemctl enable docker && systemctl start docker
# 初回スキャンの実行
/opt/vuls-saas/vuls-saas.sh

コンテナスキャンの設定

こちらのスクリプトを使ってやってみます。

https://gist.github.com/usiusi360/8704fbbd1e9e8931db65aea53535c411

#!/bin/bash

BASE_URL="XXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com"
IGNORE="dev-,stg-"
SUFFIX="image"

export AWS_DEFAULT_REGION="ap-northeast-1"
export AWS_DEFAULT_OUTPUT="json"

##########

function progress() {
    logger -i -s -p user.$1 "$2"
}

 function get_repolist() {
     local arr=( `echo ${IGNORE} | tr -s ',' ' '`)
     local ignores=""
     for ignore_str in ${arr[@]}; do
         ignores="${ignores}|${ignore_str}"
    done
     ignores=`echo ${ignores} | sed 's/^|//g'`

     aws ecr describe-repositories \
               | jq -r ".repositories[].repositoryName" \
               | sort \
               | egrep -v "${ignores}"
 }


function get_latest_tag() {
    local taglist=()
    for line in `get_repolist`
    do
        local repository_name=${line}
        local tag=`aws ecr describe-images \
                    --repository-name ${repository_name} \
                    --filter '{"tagStatus": "TAGGED"}' \
                    | jq -r ".imageDetails[] | [.imagePushedAt, .repositoryName, .imageTags[] ] | @csv" \
                    | sort | sed 's/"//g' | tail -1`
        taglist+=(${tag})
    done

    echo ${taglist[@]}
}

function login_ecr() {
    progress notice "login ecr"
    login_str=`aws ecr get-login --no-include-email --region ${AWS_DEFAULT_REGION}`
    if [ $? -ne 0 ]; then
        progress err "fail login ECR"
        exit
    fi

    ${login_str}
    if [ $? -ne 0 ]; then
        progress err "fail login ECR"
        exit
    fi
}

function start_container() {
    login_ecr

    progress notice "docker pull & docker run"
    for line in `get_latest_tag`
    do
        repository_name=`echo ${line} | cut -d "," -f2`
        image_tag=`echo ${line} | cut -d "," -f3`

        progress notice "docker pull [${repository_name}: ${image_tag}]"
        docker pull ${BASE_URL}/${repository_name}:${image_tag}
        if [ $? -ne 0 ]; then
             progress err "fail docker pull"
             exit
        fi

        docker run --detach --rm --name ${SUFFIX}-${repository_name} --entrypoint="" ${BASE_URL}/${repository_name}:${image_tag} tail -f /dev/null
        if [ $? -ne 0 ]; then
             progress err "fail docker run"
             exit
        fi
    done
}

function vuls_scan() {
    progress notice "start vuls scan"
    ./vuls-saas.sh
    if [ $? -ne 0 ]; then
        progress err "fail vuls scan"
    fi
}

function stop_container() {
    progress notice "stop container"
    docker kill $(docker ps -q -f name=${SUFFIX}-)
    if [ $? -ne 0 ]; then
        progress err "fail stop container"
    fi
}

function delete_image() {
    progress notice "delete old container images"
    duplicate_images=`docker images | cut -d " " -f1 | sort | uniq -d`
    for line in ${duplicate_images}
    do 
        docker rmi -f `docker images | grep $line | tail -n +2 | awk '{print $3}'`
    done
}


##### MAIN
cd $(dirname $0)

start_container
vuls_scan
stop_container
delete_image

内容を確認してみると

  1. ECRからイメージのダウンロードしコンテナを起動
  2. Vuls Scan
  3. コンテナを停止
  4. イメージを削除

というステップで実行されています。これをBASE_URLを自分のAWS環境に合わせて変更してください。

# FutureVulsのディレクトリに移動
cd /opt/vuls-saas/
# スクリプトのダウンロード
curl -O https://gist.githubusercontent.com/usiusi360/8704fbbd1e9e8931db65aea53535c411/raw/3d13b7f37ded810c1f6f614e2d9d5f42e559af30/vuls-ecr-scan.sh
# スクリプトに実行権限を付与
chmod +x ./vuls-ecr-scan.sh
# BASE_URLの変更
vi ./vuls-ecr-scan.sh

デフォルトではFutureVulsは起動中のコンテナをスキャン対象に含めてくれません。config.tomlを変更します。scanMode = ["fast"]の次行にcontainersIncluded = ["${running}"]を追加します。

--- config.toml.bak 2018-11-18 14:31:53.432606612 +0000
+++ config.toml 2018-11-18 14:31:22.044601085 +0000
@@ -29,5 +29,6 @@
     host = "localhost"
     port = "local"
     scanMode = ["fast"]
+    containersIncluded = ["${running}"]
     [servers.ip-XXX-XXX-XXX-XXX_ap-northeast-1_compute_internal.uuids]
       ip-XXX-XXX-XXX-XXX_ap-northeast-1_compute_internal = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

準備ができました!ECRにあるイメージをスキャンしてみましょう。

/opt/vuls-saas/vuls-ecr-scan.sh

ECRにはあらかじめ、Nginxのコンテナを用意しておきました。イメージのスキャンが無事に完了しています!

Cronの修正

FutureVulsスキャナはデフォルトでは、毎日スキャンを行うように設定されています。下記の様に実行するスクリプトをvuls-saas.shからvuls-ecr-scan.shに変更してください。(実行時間は環境のよって異なります)

#/etc/cron.d/vuls-saas-scan
59 00 * * * vuls-saas /opt/vuls-saas/vuls-ecr-scan.sh >/dev/null 2>&1

あとがき

コンテナをスキャンする際はscanModeFast-RootではなくFastにする必要があることに気づいておらずハマってしまいました...(触っていて気づいただけでソースにたどり着いて居ないので知っている方はコメントくれると嬉しいです)

今回はECRのイメージに対してスキャンしましたが、要は実行中のコンテナであればスキャンが可能なのでPrivateなDockerリポジトリに対してもスクリプトを書いてあげれば実行が可能ですね!