設定ファイルを丸ごと表示させたら、暗号化キーが作業ログに平文で出た。 交換したが、その前にそのキーが実際に何を守っていたかを数えたのが良かった。
慌てて交換すると、そのキーで暗号化されていたデータが読めなくなる。 先に「何が入っているか」を確認する。
$ sqlite3 db.sqlite "select count(*) from integration;" 0
$ sqlite3 db.sqlite "select count(*) from integrationSecret;" 0
$ sqlite3 db.sqlite "select count(*) from apiKey;" 0
$ sqlite3 db.sqlite "select count(*) from widget_secret;" 0
全部 0 だった。そのキーは空の金庫の鍵だったので、 交換によるデータ消失は起きない、と判断できた。
キーを替えるとログインできなくなるのでは、と心配になる。形式を見れば分かる。
$ sqlite3 db.sqlite "select substr(password,1,4), length(password) from user;"
$2b$|60
$2b$ で長さ60なら bcrypt ハッシュ。
暗号化ではなくハッシュなので、暗号化キーとは無関係。
交換しても同じパスワードでログインできると確信できる。
生成した鍵をシェルに出すと、今度はシェル履歴に残る。 コンテナの中で生成して、そのまま書き込むと外に出ない。
docker run --rm -v portainer_data:/d alpine sh -c '
F=/d/compose/4/docker-compose.yml
K=$(od -An -tx1 -N32 /dev/urandom | tr -d " \n") # 64桁の16進
[ ${#K} -eq 64 ] || { echo "生成失敗"; exit 1; }
cp "$F" "$F.pre-rotate"
sed -i -E "s|(SECRET_ENCRYPTION_KEY=).*|\1$K|" "$F"
'
openssl が無くても /dev/urandom と od で作れる。
追加パッケージが要らないぶん確実。
バックアップファイルにも残っている。交換後の旧キーは無効なので、 残しておく理由がない。
for f in "$F".bak-* "$F".pre-rotate-*; do
sed -i -E "s|(SECRET_ENCRYPTION_KEY=).*|\1<rotated-out>|" "$f"
done
消し残しの確認まで含めて手順にする。
for f in /d/compose/4/*; do
printf "%-45s " "$(basename $f)"
grep -qE "SECRET_ENCRYPTION_KEY=[0-9a-f]{64}" "$f" && echo "鍵あり" || echo "鍵なし"
done
根本的にはこれ。設定ファイルを表示するときは伏せ字を通す。
$ cat docker-compose.yml | sed -E 's/(KEY=|TOKEN=|PASSWORD=).*/\1<伏字>/'
コマンドの出力も同じ。docker inspect の Env は
環境変数を全部出すので、そのまま貼ると事故る。
$ docker inspect <name> --format '{{range .Config.Env}}{{println .}}{{end}}' \
| sed -E 's/(SECRET[A-Z_]*=).*/\1<伏字>/'
別の話だが同じ種類の事故。VPN の認証キーを引数で渡すと
シェル履歴と ps の出力に残る。ファイルに置いて読ませる。
install -m 600 /dev/null config/authkey
cat > config/authkey # ここに貼り付けて Ctrl-D
なお、鍵を失効させても、既に登録済みのデバイスは動き続ける。 失効は「これ以上新しいデバイスを作れなくする」だけ。安心して失効させてよい。