手元でPowerDNSのAPIを叩くためのDockerイメージを作った(その2)

こちらの記事で、手元でPowerDNSのAPIを叩くためのDockerイメージを作りましたが、

takapi86.hatenablog.com

加えて、

  • レコードの保存先をMySQL
  • PowerDNS-Adminを使ってGUIで管理できるようにする

というのをやっていきます。

レコードの保存先をMySQL

docker-composeに以下の設定を追加しました。

  mysql:
    image: mysql:5.7
    healthcheck:
      test: "echo 'SELECT version();'| mysql -u pdns -h 127.0.0.1 --password=pdns pdns"
      timeout: 30s
      retries: 5
    ports:
      - "13306:3306/tcp"
    volumes:
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
      - mysql_data:/var/lib/mysql
    environment:
      - "MYSQL_DATABASE=pdns"
      - "MYSQL_USER=pdns"
      - "MYSQL_PASSWORD=pdns"
      - "MYSQL_RANDOM_ROOT_PASSWORD=yes"
volumes:
  mysql_data:
  powerdns:
    depends_on:
      mysql:
        condition: service_healthy

また、設定に以下を追加します。

launch=gmysql # launch=bind から変更
gmysql-host=mysql
gmysql-user=pdns
gmysql-password=pdns
gmysql-dbname=pdns

具体的に何をやっているかというと・・・

healthcheck, depends_on

DBの準備ができる前にpowerdnsを起動してしまうと、DBのコネクションエラーになってしまうので、MySQLが立ち上がり、DBがセットアップできるまでpowerdnsを起動させないようにしています。

volumes

/docker-entrypoint-initdb.d/ 以下にSQL, シェルスクリプトなどのファイルを置いておくと、MySQLコンテナ起動時に実行してくれるようになります。 それを利用して、PowerDNSのテーブルをCREATEするようにしています。

SQLはこちら https://github.com/PowerDNS/pdns/blob/rel/auth-4.3.x/modules/gmysqlbackend/schema.mysql.sql

environment

MySQLのDB情報を設定しています。

詳しくはこちら https://hub.docker.com/_/mysql

PowerDNS-Adminを使ってGUIで管理できるようにする

以下の設定を追加しました。

  powerdns-admin:
    image: ngoduykhanh/powerdns-admin:latest
    ports:
      - "9191:80"
    logging:
      driver: json-file
      options:
        max-size: 50m
    environment:
      - SQLALCHEMY_DATABASE_URI=mysql://pdns:pdns@mysql/pdns
      - GUNICORN_TIMEOUT=60
      - GUNICORN_WORKERS=2
      - GUNICORN_LOGLEVEL=DEBUG
      - OFFLINE_MODE=False

これは公式の設定通り

https://github.com/ngoduykhanh/PowerDNS-Admin

動かしてみる

起動は通常通り docker-compose up で起動、http://localhost:9191/ へアクセスし、GUIからのAレコードを追加してみます。

test.takapi-example.jp からIPアドレス127.0.0.1が引けるようにしました。

dig +norec @localhost -p 1053 test.takapi-example.jp A

; <<>> DiG 9.10.6 <<>> +norec @localhost -p 1053 test.takapi-example.jp A
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 57925
;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;test.takapi-example.jp.                IN      A

;; ANSWER SECTION:
test.takapi-example.jp. 60      IN      A       127.0.0.1

;; Query time: 7 msec
;; SERVER: ::1#1053(::1)
;; WHEN: Fri Jan 01 17:42:52 JST 2021
;; MSG SIZE  rcvd: 67

構築した環境はこちらで公開しております。 github.com

とりあえず、今日はここまで

手元でPowerDNSのAPIを叩くためのDockerイメージを作った

PowerDNSのAPIを手元で叩ける環境が必要だったので用意しました。

要件は以下です。

  • ローカルで動作すること
  • 最低限ビルトインのAPIが動くこと
  • digで問い合わせができること

インストールする

こちらを参考にDockerfileを作成します。 https://repo.powerdns.com/

CentOS7, PowerDNS4.3を使いたかったので、PowerDNS Authoritative Server - version 4.3.X に記載されているコマンドを使います。

yum install epel-release yum-plugin-priorities &&
curl -o /etc/yum.repos.d/powerdns-auth-43.repo https://repo.powerdns.com/repo-files/centos-auth-43.repo &&
yum install pdns

起動する

/usr/lib/systemd/system/pdns.serviceExecStart をみてみると、/usr/sbin/pdns_server で起動するようなので、

CMD ["/usr/sbin/pdns_server"]

を追加します。

Dockerfileはこんな感じになりました。

FROM centos:7

RUN yum update -y \
  && yum install -y epel-release yum-plugin-priorities \
  && curl -o /etc/yum.repos.d/powerdns-auth-43.repo https://repo.powerdns.com/repo-files/centos-auth-43.repo \
  && yum install -y pdns \
  && rm -rf /var/cache/yum \
  && yum clean all

CMD ["/usr/sbin/pdns_server"]

設定する

以下を参考に設定 https://doc.powerdns.com/recursor/http-api/#enabling-the-api

開発環境なので、どこからでもAPIを叩けるようにします。

設定はこんな感じになりました。

api=yes
api-key=changeme
launch=bind
setgid=pdns
setuid=pdns
webserver=yes
webserver-address=0.0.0.0
webserver-allow-from=0.0.0.0/0
webserver-port=8081

設定をvolumesで読み込ませ、portをbindします。

version: '3'

services:
  powerdns:
    build: ./
    volumes:
      - ./pdns.conf:/etc/pdns/pdns.conf:ro
    ports:
      - "8081:8081"
      - "1053:53/tcp"
      - "1053:53/udp"

動作確認

上記で準備ができたので、動作確認します。

APIを叩く

なんか返ってきた

curl -H 'X-API-Key: changeme' http://127.0.0.1:8081/api/v1/servers/localhost | jq .
{
  "config_url": "/api/v1/servers/localhost/config{/config_setting}",
  "daemon_type": "authoritative",
  "id": "localhost",
  "type": "Server",
  "url": "/api/v1/servers/localhost",
  "version": "4.3.1",
  "zones_url": "/api/v1/servers/localhost/zones{/zone}"
}

digで問い合わせをする

こちらもなんか返ってきた

dig +norec @localhost -p 1053 test.example.com ANY
;; Truncated, retrying in TCP mode.

; <<>> DiG 9.10.6 <<>> +norec @localhost -p 1053 test.example.com ANY
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 28280
;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;test.example.com.              IN      ANY

;; Query time: 4 msec
;; SERVER: ::1#1053(::1)
;; WHEN: Fri Jan 01 17:11:39 JST 2021
;; MSG SIZE  rcvd: 45

まとめ

とりあえず、APIを手元で叩けるようになりました。 実際にレコードを登録するなどは、後ほどやっていこうと思います。

参考

https://doc.powerdns.com/

Smartyでのエスケープをまとめた

SmartyでのXSS対応方法をまとめます。

足りてないパターンがあれば追加予定。

参考

要素内容

<p>ここに文字が入る場合</p>

XSS対策方法

<&文字参照にする

Smartyでは

<{$hoge|escape:'html'}> を使います。 ※ & " ' < >エスケープされます。

例:

<div>
<p>名前:</p><span><{$name|escape:'html'}></span>
</div>

属性値

<input type="text" name="name" value="ここに文字が入る場合">

XSS対策方法

Smartyでは

<{$hoge|escape:'html'}> を使います。 ※ & " ' < >エスケープされます。

例:

<input type="text" name="name" value="<{$name|escape:'html'}>">

属性値(URL)

<a href="ここに文字が入る場合">hogehoge</a>

XSS対策方法

  • URLの形式チェックをする
  • 属性値としてエスケープ

Smartyでは

<{$hoge|escape:'html'}> を使います。 ※ & " ' < >エスケープされます。

また、PHP側で形式のチェックをします。

例:

<a href="<{$url|escape:'html'}>">hogehoge</a>
if (preg_match('/\Ahttps?:\/\//', $url) || preg_match('/\A\//', $url)) {
  return ture;
} else {
  return false;
}

イベントハンドラ

<body onload="init('ここに文字が入る場合')">

XSS対策方法

Smartyでは

<{$hoge|escape:'javascript'|escape:'html'}> を使います。 ※ \ " ' 改行 </& " ' < >エスケープされます。

例:

<body onload="init('<{$hoge|escape:'javascript'|escape:'html'}>')">

script要素内の文字列リテラル

※基本的には、JavaScriptに直接文字を埋め込まないのが推奨


<script type="text/javascript">
  var x = "hoge";
  document.write('ここに文字が入る場合');
</script>

XSS対策方法

Smartyでは

<{$hoge|escape:'javascript'}> を使います。 ※ \ " ' 改行 </エスケープされます。

例:

<script type="text/javascript">
  var x = "hoge";
  document.write('<{$hoge|escape:'javascript'}>');
</script>

オープンソースのSNSをDockerで動かしてみたら懐かしすぎた

こんにちは、たかぴーです。

このエントリは、GMOペパボエンジニア Advent Calendar 2020 - Adventarの8日目の記事です。7日目はしばっちさんの「AWSでDNSをRoute53を使わずに構築する」でした。

社内のPHPアプリケーションをDocker化するにあたりどういった流れで構築していくのか、自分はこうやっているよ!というのを書いていこうと思ったのですが、社内のアプリケーションの設定やソースコードは表に出す訳にはいかないので、今回は代わりにOpenPNEという、PHP製のオープンソースSNSをDocker化し構築の流れを書いていきます。

今回は最低限動かすことが目的なので、ローカルで動くところまでをゴールとします。

1. まずは、ざっくり動かしてみる

まずは細かいことは気にせず、ざっくり動かしていきます。

情報を集める

最新版(12/8時点)のOpenPNEセットアップ手順をの通りにやれば良さそうです。

推奨構成はこんな感じ

OS: Debian jessie
Apache 2.4.10 以降
PHP: PHP 7 以降
DB MySQL 5.5.53以降

いわゆるLAMP環境ってやつですね。 Docker Hubの公式イメージを使って作れそうです。

docker-composeを使って大枠を作る

最終的には、docker-composeがなくてもイメージ単体で動作させる予定ですが、 イメージ構築時は、設定をyamlに追加しながら作業できるので、個人的にはdocker-composeを使いながら構築するのがやりやすいです。

こんな感じで大枠を作成

version: '3'

services:
  app:
    build: ./
  db:
    image: mysql:5.7.32
FROM php:7.4.10-apache-buster

php:7.4.10-apacheで、jessieのイメージはなかったので、busterを使いました。(多分動くはず)

基本的に野良イメージは使わず、Officialなイメージを使うようにしています。

ライブラリを追加

引き続き、セットアップ手順を参考に必要なライブラリを追加していきます。

Dockerfileは後ほどきれいにするので、まずは雑にライブラリをインストールしていきます。

Dockerfileは上から各命令ごと順番にキャッシュされます。 そのため、構築時に上の方に記述されている命令を変更すると、それ以下はキャッシュが効かなくなるため、一気にインストールせずキャッシュを効かせながらインストールしています。

FROM php:7.4.10-apache-buster

RUN apt update && apt -y upgrade
RUN apt install -y libonig-dev
RUN docker-php-ext-install mbstring
RUN apt install -y libxml2-dev
RUN docker-php-ext-install xml
RUN docker-php-ext-install pdo_mysql
RUN docker-php-ext-install json
RUN apt -y install zlib1g-dev
RUN apt -y install libpng-dev
RUN apt -y install exif
RUN apt -y install mcrypt
RUN pecl install apcu
RUN docker-php-ext-enable apcu

RUN pecl install xdebug
RUN docker-php-ext-enable xdebug

RUN a2enmod rewrite
RUN docker-php-ext-install exif

ソースコード・設定ファイルをマウント

docker-compose側で、ソースコードOpenPNEの設定ファイルをマウントするようにします。 また、ここには出していないですがphp.ini, apache2.confの内容もいい感じに設定しています。

volumes:
  - ./src:/var/www/html
  - ./config/OpenPNE.yml:/var/www/html/config/OpenPNE.yml
  - ./config/ProjectConfiguration.class.php:/var/www/html/config/ProjectConfiguration.class.php
  - ./config/apache2.conf:/etc/apache2/apache2.conf
  - ./config/php.ini:/usr/local/etc/php/php.ini

個人的に、設定ファイルはconf.dなどメインのファイルにインクルードするような場所に置くと使用するイメージの設定によって設定内容が変わってくるため、conf.dなどのメインに設定にインクルードするような箇所にはマウントせず、メインのファイルのみ書き換えインクルードさせないようにするのが好みです。(が、みなさんどうやっているのだろう??)

初期化する

初期設定では、コマンドを叩きテーブルを初期化する必要があるようなので、mysqlのDBにユーザ・パスワード等の設定を追加し、実行します。

DBの設定を追加

  db:
    image: mysql:5.7.32
    volumes:
      - data_volume:/var/lib/mysql
    environment:
      TZ: Asia/Tokyo
      LANG: ja_JP.UTF-8
      MYSQL_DATABASE: openpne
      MYSQL_USER: openpne_user
      MYSQL_PASSWORD: openpne_password
      MYSQL_RANDOM_ROOT_PASSWORD: "yes"
volumes:
  data_volume:

初期化コマンドを実行!

docker-compose run --rm app bash -c "./symfony openpne:fast-install --dbms=mysql --dbuser=openpne_user --dbpassword=openpne_password --dbhost=db --dbport=3306 --dbname=openpne"

>> installer installation is completed! と出力されたので上手くいったっぽい。

動かす!

docker-compose up して動かします!

おお、なんかすごくm○xiっぽい。構築がおわったらゆっくり見ていきます。

f:id:takapi86:20201207031017p:plain

とりあえず、それっぽいコミュニティを設定しときました。

動かない箇所の修正

ポチポチ動かしてみると画像のアップロードが上手く行きませんでした。 エラーログやXdebugなどでデバッグで確認していきます。

ちなみに、DockerへのXdebug導入はこんな感じ

takapi86.hatenablog.com

今回は、拡張モジュールであるGDが漏れていたようなので追加しました。

RUN apt-get install -y libfreetype6-dev libjpeg-dev
RUN docker-php-ext-configure gd --with-jpeg=/usr/include/ --with-freetype=/usr/include/
RUN docker-php-ext-install -j$(nproc) gd

これで一通り動きました!

2. 設定を見直す

Dockerfileを整理する

キャッシュが効くようにババっと書いていってしまいましたが、 見た目も悪いですし、イメージのサイズも大きくなってしまうので整理していきます。

FROM php:7.4.10-apache-buster

RUN apt update && apt -y upgrade && apt install -y --no-install-recommends \
    exif \
    libfreetype6-dev \
    libjpeg-dev \
    libonig-dev \
    libpng-dev \
    libxml2-dev \
    mcrypt \
    zlib1g-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

RUN pecl install apcu && docker-php-ext-enable apcu
RUN docker-php-ext-install -j$(nproc) \
    exif \
    json \
    mbstring \
    pdo_mysql \
    xml

RUN docker-php-ext-configure gd --with-jpeg=/usr/include/ --with-freetype=/usr/include/ \
    && docker-php-ext-install -j$(nproc) gd
RUN a2enmod rewrite

こんな感じで整理しましたが、整理した以外にも上記の設定はこんなことを意識しています。

apt updateと一緒にapt installする

apt installをする際に古いパッケージ情報をキャッシュしたままだと、更新できないケースがありますので、それを避けるために、apt update apt installまとめています。 これにより、パッケージを追加したときにすべて新しいパッケージ情報で、更新されるようになります。

--no-install-recommendsを追加する

必須ではないパッケージが入ってくることを防いでいます。 https://manpages.debian.org/unstable/apt/apt-get.8.ja.html#%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3

キャッシュを削除する

&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

aptキャッシュをクリーンにし、/var/lib/apt/lits を削除することで、イメージのサイズを減らしています。

Dockerfile のベストプラクティス — Docker-docs-ja 1.9.0b ドキュメント

docker-composeに設定した値をDockerfileに寄せる

今回はDockerイメージ単体でも動作するようにしたいので、ソースコード・設定ファイルをDockerfileに移動させます。

COPY ./src /var/www/html
COPY ./config/OpenPNE.yml ./config/ProjectConfiguration.class.php /var/www/html/config/
COPY ./config/apache2.conf /etc/apache2/apache2.conf
COPY ./config/php.ini /usr/local/etc/php/php.ini

コピーする命令はCOPYの他にADDがありますが、ADDはローカル上でのtar展開や、リモートURLのサポートなど多くの機能を持っていて混乱の元なので、明確にCOPY以外の機能を使いたいとき以外は使わないようにしています。

こちらでもそう言っていますね。

Dockerfile のベストプラクティス — Docker-docs-ja 1.9.0b ドキュメント

その他、細かな設定を追加

WORKDIR,、EXPOSE、タイムゾーン、言語設定を追加します。

ENV TZ=Asia/Tokyo
ENV LANG=ja_JP.UTF-8
WORKDIR /var/www/html
EXPOSE 80

また、先に、DBが起動して欲しいので、depends_on を追加します。

depends_on:
  - db

3.ちょっとだけ、セキュアにする

今回はローカル環境でしか動かさないのですが、もし今後公開した環境で稼働させたくなったときにスムーズに移行できるよう、今回少しだけセキュリティ設定はしておきます。

アプリケーションのパーミッションに設定する

ソースコードを置いているディレクトリは最低限必要な権限のみを付与するようにします。

RUN chown -R www-data:www-data /var/www/html \
    && chmod -R 400 /var/www/html \
    && find /var/www/html -type d | xargs -I{} chmod 500 {} \
    && chmod 700 /var/www/html/log /var/www/html/cache

ReadOnlyで起動する

docker-compose.ymlに read_only: true を追加します。

書き込みが必要なディレクトリは次のように匿名ボリュームをマウントすることで書き込めるようにします。

read_only: true
volumes:
  - /var/www/html/log
  - /var/run/apache2/
  - /tmp

こちらに関しては先日記事を書きましたので良かったらご覧ください

takapi86.hatenablog.com

dockerignoreを設定する

イメージに含める必要のないファイルは.dockerignoreで除外しました。

4. 最終チェック

--no-cache オプションを利用して念の為キャッシュを使わず最初からビルドをし直して問題ないか確認します。

COMPOSE_DOCKER_CLI_BUILD=1 docker-compose build --no-cache

※COMPOSE_DOCKER_CLI_BUILD=1を設定することで、BuildKitを使って高速にビルドする様にしています。BuildKitについてはこちら

いろいろイジってみる

どうやら、インストール直後はプロフィールとコミュニティ機能くらいしか無いようなのですが、プラグインを追加することで、タイムラインや日記、あしあと機能などを追加ができるようです。

プラグインはこのページの各プラグインをクリックしたあとに表示されるコマンドを入力することで追加できるようです。

OpenPNE プラグイン|OpenPNE

プラグインを追加していった結果こんな見た目になりました!! なんかすごく大学生の頃を思い出すような懐かしい見た目になった!!!

f:id:takapi86:20201207025834p:plain

本家(UIの参考になったであろうサービス)の方もたまにはログインしてみようと思います。

まとめ

今回は、OpenPNEを使ったローカル環境を構築してみましたが、業務で使用している開発環境も大体同じような流れで構築をしています。もしこうすると良いよ!などアドバイスなどがあればコメントいただけると嬉しいです。

Twitterやっているので、良かったらマイミク申請フォローお願いします。

https://twitter.com/takapi86

明日9日目は、mochikoさんです〜

書き込みが必要なDockerコンテナをReadOnlyで動かせるようにする

コンテナを触っている人であれば当たり前っちゃ当たり前のテクニックな感じだと思いますが、最近k8s環境でDockerコンテナを読み取り専用で動かすことができたので、メモしておきます。

Dockerコンテナを安全に運用するための手段の一つとして、コンテナを読み取り専用(ReadOnly)にすることがあります。 しかし、ログやキャッシュディレクトリなどアプリケーションの仕組み上、どうしても書き込みが必要な場合があり、そのままでは読み取り専用で動作させることはできません。

例えば、nginxの公式イメージだと起動時にこうなります。

docker run --rm --read-only nginx:1.18.0
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: error: can not modify /etc/nginx/conf.d/default.conf (read-only file system?)
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2020/11/23 XX:XX:XX [emerg] 1#1: mkdir() "/var/cache/nginx/client_temp" failed (30: Read-only file system)
nginx: [emerg] mkdir() "/var/cache/nginx/client_temp" failed (30: Read-only file system)

--read-only をつけると読み取り専用で動作させることができます。

これを解決するために、書き込み可能なボリュームをマウントしてあげることで回避することができました。

以下、設定例です。

  • dockerコマンドの場合
docker run --rm \
  -v /var/cache/nginx \
  -v /var/run \
  -v /etc/nginx/conf.d \
  --read-only \
  -p 80:80 \
  nginx:1.18.0
  • docker-composeの場合

設定箇所のみピックアップして載せています

services:
  nginx:
    image: nginx:1.18.0
    read_only: true
    volumes:
      - /var/cache/nginx
      - /var/run
      - /etc/nginx/conf.d

設定箇所のみピックアップして載せています

template:
  spec:
    containers:
    - name: nginx
      image: nginx:1.18.0
      securityContext:
        readOnlyRootFilesystem: true
      volumeMounts:
      - name: nginx-cache
        mountPath: /var/cache/nginx
      - name: pid-dir
        mountPath: /var/run
      - name: nginx-conf
        mountPath: /etc/nginx/conf.d
    volumes:
    - name: nginx-cache
      emptyDir: {}
    - name: pid-dir
      emptyDir: {}
    - name: nginx-conf
      emptyDir: {}

今回の例では匿名ボリューム、emptyDirを設定しましたが必要に応じて、適切なボリュームを設定してあげてください。

もっと良い方法があれば教えてください。

コンテナイメージの脆弱性スキャンツールTrivyを使ってみる 〜とりあえず試す編〜

Docker Bench for Securityに引き続き、コンテナイメージの脆弱性スキャンツールTrivyを使ってみます。

Docker Bench for Securityについてのエントリーはこちら takapi86.hatenablog.com

Trivyとは何か

Docker Bench for Securityがコンテナイメージの作成方法や動作環境が基準に沿ったセキュアな環境かどうか診断するのに対し、Trivyはイメージに含まれる具体的な脆弱性情報を検知してくれるツールです。

対象はyum/rpm, apt/apt-get/dpkg, apkやGemfile.lockやcomposer.lockなど、アプリケーションのライブラリの依存関係から脆弱性情報を検知できます。

https://github.com/aquasecurity/trivy#os-packages https://github.com/aquasecurity/trivy#application-dependencies

また、リポジトリのAboutにもある通り、

A Simple and Comprehensive Vulnerability Scanner for Containers, Suitable for CI

と、CIに組み込みやすい特徴があるようです。

公式リポジトリ

https://github.com/aquasecurity/trivy

今回はお試しということで、CIには組み込まず、コマンドで実行し脆弱性情報を確認するところまでやっていきます。

インストールする

様々なパッケージ管理システムを使ってインストールできるようです。 https://github.com/aquasecurity/trivy#installation

実行する

  • 直接コマンドで実行する場合
trivy image [your_image]
  • コンテナイメージを使う場合
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v [YOUR_CACHE_DIR]:/root/.cache/ aquasec/trivy [YOUR_IMAGE_NAME]

試す

2年ほど放置していて稼働していないPrivateリポジトリにあるRailsアプリケーションのイメージで試してみます。

trivy image rails_app

このようにたんまり脆弱性情報が出てしまいました 🥺

usr/src/app/Gemfile.lock
========================
Total: 22 (UNKNOWN: 0, LOW: 0, MEDIUM: 11, HIGH: 9, CRITICAL: 2)

+----------------------+------------------+----------+-------------------+--------------------------------+--------------------------------+
|       LIBRARY        | VULNERABILITY ID | SEVERITY | INSTALLED VERSION |         FIXED VERSION          |             TITLE              |
+----------------------+------------------+----------+-------------------+--------------------------------+--------------------------------+
| XXXXXXXXXX           | XXXXXXXXXX       | HIGH     | XXXXX             | XXXXXXXXXX                     | XXXXXXXXXX                     |
|                      |                  |          |                   |                                | XXXXXXXXXX                     |
+                      +------------------+----------+                   +                                +--------------------------------+
|                      | XXXXXXXXXX       | MEDIUM   |                   |                                | XXXXXXXXXX                     |
|                      |                  |          |                   |                                | XXXXXXXXXX                     |
|                      |                  |          |                   |                                | XXXXXXXXXX                     |
+----------------------+------------------+          +                   +--------------------------------+--------------------------------+
(この下にもばーっと脆弱性情報が出ています)

GitHub Actionsに組み込んで試す

続いて、CIで検知できるようGitHub Actionsに組み込んでみようと思います。 こちらはCI導入編ということで別エントリーであげようと思います。

今回はここまで

Docker Bench for Securityを使ってみる 〜とりあえず試す編〜

これは何?

CIS Docker BenchmarksというDockerのセキュリティ基準に沿って、自動診断してくれるツール

診断項目はこんな感じ

  • Host Configuration
  • Docker daemon configuration
  • Docker daemon configuration files
  • Container Images and Build File
  • Container Runtime
  • Docker Security Operations
  • Docker Swarm Configuration

公式リポジトリはこちら https://github.com/docker/docker-bench-security

お試し環境の構築

実際使用する際には、本番環境もしくは、本番に近い環境で診断することが望ましいと思われるのですが、今回はVagrant環境を使ってお試し環境を作りました。

https://github.com/takapi86/docker-bench-security-test

実行までの流れ

VMを起動

vagrant up

VMの中に入る

vagrant ssh

dockerを起動

sudo systemctl start docker

docker-bench-securityをpull

docker pull docker/docker-bench-security

診断したいコンテナをあげる

docker run --rm -d nginx

今回は、nginxを例としていますが、診断したいコンテナを起動してください。

診断実行

docker run --rm --net host --pid host --userns host --cap-add audit_control \
    -e DOCKER_CONTENT_TRUST=1 \
    -v /etc:/etc:ro \
    -v /usr/lib/systemd:/usr/lib/systemd:ro \
    -v /var/lib:/var/lib:ro \
    -v /var/run/docker.sock:/var/run/docker.sock:ro \
    --label docker_bench_security \
    docker/docker-bench-security

診断結果を見る

項目が結構あるので、今回は Container Images and Build File を見てみる

[INFO] 4 - Container Images and Build File
[WARN] 4.1  - Ensure a user for the container has been created
[WARN]      * Running as root: serene_yonath
[NOTE] 4.2  - Ensure that containers use trusted base images
[NOTE] 4.3  - Ensure unnecessary packages are not installed in the container
[NOTE] 4.4  - Ensure images are scanned and rebuilt to include security patches
[PASS] 4.5  - Ensure Content trust for Docker is Enabled
[WARN] 4.6  - Ensure HEALTHCHECK instructions have been added to the container image
[WARN]      * No Healthcheck found: [docker.io/nginx:latest]
[PASS] 4.7  - Ensure update instructions are not use alone in the Dockerfile
[NOTE] 4.8  - Ensure setuid and setgid permissions are removed in the images
[INFO] 4.9  - Ensure COPY is used instead of ADD in Dockerfile
[INFO]      * ADD in image history: [docker.io/nginx:latest]
[INFO]      * ADD in image history: [docker.io/docker/docker-bench-security:latest]
[NOTE] 4.10  - Ensure secrets are not stored in Dockerfiles
[NOTE] 4.11  - Ensure verified packages are only Installed

WARNINGとして、コンテナがrootで実行されていることと、HEALTHCHECKがコンテナイメージにないことが指摘されている。

ガイドラインと照らし合わせてチェックしていくと大変なので、便利そう。

参考