オレオレ証明書を発行するためだけの Docker Compose

経緯

opensslコマンド を叩くことが難しい環境にオレオレ証明書をインポートするために、オレオレ証明書を発行する為だけの Docker Compose を作りました。

結果としてはインポートこそ成功したものの、結局https通信はできなかったのであまり効果はなかったですが……。

コード

ディレクトリ階層

PROJECT_ROOT/
    ├ cert/
    │
    ├ template/
    │   └ subjectnames-template.txt // SANs 設定用の設定テンプレートファイル
    │
    ├ workspace/
    │   └ entrypoint.sh             // エントリポイント
    │
    ├ .env                          // docker-compose.yml 用の環境変数
    ├ Dockerfile
    └ docker-compose.yml

構造は比較的シンプル。

docker-compose.yml

version: '3.8'
services:
  oc:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        FILE_DIRECTORY: $FILE_DIRECTORY
        HOST_NAME: $HOST_NAME
        DNS_NAME: $DNS_NAME
        IP_ADDR: $IP_ADDR
        SSL_C: $SSL_C
        SSL_ST: $SSL_ST
        SSL_L: $SSL_L
        SSL_O: $SSL_O
        SSL_OU: $SSL_OU
    labels:
      certificate.staying.overnight: "Sign this overnight certificate"
    volumes:
      # workspace
      - ./workspace:/workspace
      # template
      - ./template:/template
      # SSL
      - ./cert:/etc/ssl/private
    tty: true
    entrypoint: bash -c "bash /workspace/entrypoint.sh $FILE_DIRECTORY $HOST_NAME $DNS_NAME $IP_ADDR $SSL_C $SSL_ST $SSL_L $SSL_O $SSL_OU && /bin/bash"

SSL証明書を出力するディレクトリをボリュームとしてマウントしているので、 docker-dompose up -d すると .env のパラメータに従って証明書が生成される、という挙動を意図しています。

処理自体は Docokerfile とエントリポイントで。

.env

FILE_DIRECTORY=self_sign
HOST_NAME=www.lvh.me
DNS_NAME=lvh.me
IP_ADDR=127.0.0.1
SSL_C=JP
SSL_ST=Hokkaido
SSL_L=SapporoCity
SSL_O=DummyTestOrganization
SSL_OU=

証明書に必要なパラメータと出力するディレクトリ名を指定しています。

subjectnames-template.txt

subjectAltName = DNS:HOST_NAME, IP:IP_ADDR

単純に SANs に指定するホスト名とIP をテンプレートとして持っているだけのファイルです。

Dockerfile

FROM almalinux:8
# args
ARG FILE_DIRECTORY
# timezone
RUN \cp -pf /usr/share/zoneinfo/Japan /etc/localtime
# dnf install
RUN dnf -y update && dnf -y install \
    epel-release \
    sudo \
    less \
    procps \
    # network ss (instaed of netstat)
    iproute \
    # SSL
    openssl \
    mod_ssl
# SSL
RUN mkdir -p /etc/pki/CA/newcerts
RUN echo 00 > /etc/pki/CA/serial
RUN touch /etc/pki/CA/index.txt
RUN mkdir -p /var/certtemp
# volume directory
RUN mkdir -p /etc/ssl/private
RUN mkdir /template
RUN mkdir /workspace

証明書発行の処理に必要なディレクトリを作っておくのが主な役割。後はパッケージのインストールですね。

entrypoint.sh

#!/bin/bash

sed -e "s/HOST_NAME/${2}/gi" -e "s/IP_ADDR/${4}/gi" /template/subjectnames-template.txt > /var/certtemp/subjectnames.txt

# prepare
## delete exists files
if [ -e /etc/ssl/private/${1} ]; then
    rm -Rf /etc/ssl/private/${1}
fi
## make cert directory
mkdir /etc/ssl/private/${1}
# gen key & certificate
## .env domain
# Generate private key for CA and Server.
openssl genrsa 2048 > /etc/ssl/private/${1}/server.key

# Generate certificate sigining requests from same key.
openssl req -new -key /etc/ssl/private/${1}/server.key -subj "/C=${5}/ST=${6}/L=${7}/O=${8}/OU=${9}/CN=${2}" > /etc/ssl/private/${1}/ca.csr
openssl req -new -key /etc/ssl/private/${1}/server.key -subj "/C=${5}/ST=${6}/L=${7}/O=${8}/OU=${9}/CN=${2}" > /etc/ssl/private/${1}/server.csr

# Generate CA certificate.
openssl ca -batch -extensions v3_ca -out /etc/ssl/private/${1}/ca.crt -in /etc/ssl/private/${1}/ca.csr -selfsign -keyfile /etc/ssl/private/${1}/server.key

# Generate server certificate with CA certificate.
openssl x509 -days 3650 -req -extfile /var/certtemp/subjectnames.txt -CA /etc/ssl/private/${1}/ca.crt -CAkey /etc/ssl/private/${1}/server.key -set_serial 1 < /etc/ssl/private/${1}/server.csr > /etc/ssl/private/${1}/server.crt

証明書発行のコマンドを全てエントリポイントに含ませています。

今回は /etc/ssl/private/下 に証明書ごとにさらにサブディレクトリを切って生成するようにしましたが、ここは docker-compose.yml で指定した通りボリュームとしてマウントされ、ホスト側と共有されます。

が、 Dockerfile 実行時はこの領域は参照できないので、最初の mkdir /etc/ssl/private/{FILE_DIRECTORY} のコマンドを Dockerfile 内に記述しておくと失敗します。そのため、このディレクトリを掘るコマンドだけはエントリポイント側に持たせました。

後は .env のパラメータに従って証明書を発行しています。

これで証明書の生成の挙動は問題ないことを確認しました。

結果としては冒頭に記した通りあまり役に立ちませんでしたが……。

余談

例によって名前の由来。証明書と言われて思い付いた単語が外泊証明書だったので。「起きてこれにサインしろ!!」

参考

オレオレ証明書の発行

証明書の内容の確認

Chrome へのインポート

シェルスクリプトのコマンド確認

Dockerfile

この記事を書いた人

アルム=バンド

フロントエンド・バックエンド・サーバエンジニア。LAMPやNodeからWP、Gulpを使ってejs,Scss,JSのコーディングまで一通り。たまにRasPiで遊んだり、趣味で開発したり。