その自動更新、全部は見ていません ── unattended-upgrades と第三者リポジトリの盲点
株式会社グルーヴサイン代表取締役の米沢です。
前回の記事で、PHPの緊急脆弱性への対応を書きました。その点検の流れでサーバ全体を見直したところ、自動更新が効いていると思っていた範囲に、明確な穴があったことが分かりました。
結論を先に書きます。unattended-upgrades は、既定ではディストリビューション公式のリポジトリしか見ていません。 第三者が提供するリポジトリから入れたパッケージは、自動更新が正常に動いていても、何年でも取り残されます。
そして厄介なことに、この状態はログにもエラーにも現れません。 毎晩きちんと動作し、「更新対象なし」として静かに終わります。
なぜ漏れるのか
unattended-upgrades は、パッケージのバージョンを見て更新するのではありません。そのパッケージがどのリポジトリ(origin)から来たかを見て、許可されたoriginのものだけを適用します。
既定の設定はこうなっています。
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}";
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
};
${distro_id} は Ubuntu なら Ubuntu に展開されます。つまり**「Ubuntu が配っているもの」だけ**が対象です。
ここに、たとえば Node.js を NodeSource から入れたとします。このパッケージの origin は Ubuntu ではありません。どのパターンにも一致しないため、対象外として黙って飛ばされます。 設定ミスでも故障でもなく、仕様どおりの動作です。
同じことが、Docker、各種クラウドのエージェント、データベースのベンダ提供版、監視エージェント、そのほかベンダが独自リポジトリで配っているものすべてに起きます。現代のサーバで、公式リポジトリだけで構成されているものはむしろ少数派でしょう。
自分の環境を確認する
難しくありません。3つ実行すれば分かります。
① 公式以外のリポジトリを洗い出す
apt-cache policy | grep -E "^ 500 https?://" \
| grep -viE "archive.ubuntu.com|security.ubuntu.com|esm.ubuntu.com" \
| awk '{print $1}' | sort -u
ここに出てきたものが、自動更新の対象外である可能性が高いリポジトリです。
② 自動更新が実際に見ているoriginを確認する
apt-config dump | grep -E "Unattended-Upgrade::(Allowed-Origins|Origins-Pattern)::"
設定ファイルを直接読むのではなく apt-config dump を使うのがポイントです。/etc/apt/apt.conf.d/ の全ファイルを読み込んだ結果が出るため、「別ファイルで上書きされていた」「書いたつもりが効いていない」といった取り違えが起きません。
③ 対象外のまま更新待ちになっているものを見る
apt list --upgradable 2>/dev/null | grep -viE "ubuntu|esm"
①で出たリポジトリ由来のパッケージがここに並んでいれば、それが取り残されているものです。
見つかったら、どうするか
選択肢は3つあります。すべてを自動化するのが正解とは限りません。
A. 自動更新の対象に加える
sudo tee /etc/apt/apt.conf.d/53unattended-upgrades-local > /dev/null <<'EOF'
Unattended-Upgrade::Origins-Pattern {
"origin=<リポジトリのorigin名>";
};
EOF
origin名は apt-cache policy の release o=... で確認できます。
ここで知っておくとよいのが、Origins-Pattern は Allowed-Origins を上書きせず、追加として扱われることです。別ファイルに書けば、公式・ESMの既存設定はそのまま残ります。元の 50unattended-upgrades を書き換える必要はありません(書き換えると、パッケージ更新時に上書きされて設定が消えることがあります)。
B. 手動で定期更新する
sudo apt update && sudo apt install --only-upgrade <パッケージ名>
ビルドツールや言語処理系は、こちらを選ぶ判断もあります。 勝手にバージョンが上がってビルドが壊れると、気づくのが翌朝のデプロイ時になりかねません。自動化の利点より、変更タイミングを制御できる利点が上回る場合です。
C. そもそも外す
使っていないなら消す。これが最も確実です。インストールされているだけで使われていないパッケージは、更新の手間を生むだけで何の役にも立ちません。
判断の基準
私は2つの軸で分けています。
- 外部に面しているか ── インターネットからのトラフィックを直接受けるもの(リバースプロキシ、トンネル、Webサーバ、VPN)は、迷わず自動更新に含めます。 攻撃を受ける側なので、更新の遅れがそのまま危険です
- 壊れたときに何が止まるか ── ビルドツールや開発用の処理系は、自動で上がると気づかないうちに壊れます。手動にして、更新後に動作確認までを1セットにします
結果として「全部自動」でも「全部手動」でもなく、役割ごとに分ける形に落ち着きました。
もう一つの盲点:EOLに気づけない
第三者リポジトリには、もう一段の落とし穴があります。多くがメジャーバージョンごとに別のリポジトリに分かれていることです。
たとえば Node.js の NodeSource は node_20.x、node_22.x のように分かれています。node_20.x を指定している限り、届くのは20系の更新だけです。そして20系がサポート終了(EOL)を迎えると、更新自体が静かに来なくなります。
このとき、
apt list --upgradableには何も出ない- 自動更新のログにもエラーは出ない
- 「更新するものがない」状態と、「もう更新が提供されない」状態が、見た目で区別できない
これが一番怖いところです。パッケージ管理の仕組みは「上流がもう直してくれない」ことを教えてくれません。
確認するには、自分でサポート期限を見に行くしかありません。
# 使っている処理系のバージョンを控える
node -v; python3 -V; php -v | head -1; psql --version 2>/dev/null
あとは endoflife.date や各プロジェクトのリリース一覧と突き合わせます。年に一度でいいので、この照合をやる日を決めておくのが現実的です。
点検のチェックリスト
今回やったことを、手順として残しておきます。
- 公式以外のリポジトリを洗い出す(
apt-cache policy) - 自動更新が見ているoriginを確認する(
apt-config dump。設定ファイルを直接読まない) - 対象外で更新待ちのものを確認する(
apt list --upgradable) - 役割ごとに、自動・手動・削除を決める(外部に面しているか/壊れたとき何が止まるか)
- 使っている処理系のEOLを照合する(更新が来ないことは、エラーにならない)
- 再起動保留を確認する(カーネルやライブラリは、入れただけでは有効にならない)
自動化したものほど、点検が要る
今回いちばん堪えたのは、自動更新が「正常に動いていた」という事実でした。毎晩動き、ログを残し、対象を適切に処理していた。壊れていたわけではありません。
それでも穴は空いていました。見ている範囲が、こちらの思い込みより狭かっただけです。
自動化は、作業をなくす代わりに関心もなくします。 動いていることが当たり前になると、何を見ていないのかを考えなくなる。だから定期的に、自動化した仕組みそのものを点検する必要がある。ありふれた教訓ですが、自分の環境で実際に掘り当てると、重みが違いました。
ご相談について
「自動更新は入れてあるはずだが、本当に効いているか分からない」「何が入っているのか把握しきれていない」。そうした状態のサーバは、珍しくありません。
上に挙げたコマンドは、どれも状態を変えずに読み取るだけのものです。まずはご自身の環境で実行してみてください。それで何も出てこなければ、安心材料になります。
結果を見ても判断がつかない、あるいは古い処理系が載っていて手を入れるのが怖い、という場合はご相談ください。動いているものを止めずに、どこから手をつけるかを考えるのが、保守という仕事です。
レガシーPHPシステムの保守・引き継ぎのご相談を受け付けています。
初回のご相談・ヒアリングは無料です。