CI/CDの盲点:その「信頼」は偽物かもしれない。A08:2021からシステムを守る極意
エンジニア諸君、日々お疲れ様。本日は「Software and Data Integrity Failures(ソフトウェアおよびデータの整合性の欠如)」について、現場の泥臭い視点から切り込んでいこうと思う。
OWASP Top 10の常連となったこの項目、一言で言えば「お前が読み込んでいるそのファイル、本当に信頼できるのか?」という問いだ。CI/CDパイプラインを自動化している君たちなら、一度は「ビルド済みのライブラリをどこから落としてくるか」を考えたことがあるはずだ。もし、そのリポジトリが汚染されていたら?あるいは、デプロイ先で設定ファイルが書き換えられていたら?
今日は、暗号学の基本である「署名」を武器に、この攻撃を物理的に遮断する方法を伝授する。
—
1. なぜ「ハッシュ値」だけでは不十分なのか
多くの開発者は、ファイルをダウンロードする際に「MD5やSHA256のハッシュ値」を照合して満足する。しかし、考えてみてほしい。攻撃者が攻撃用バイナリを置き換えるような状況で、そのハッシュ値リスト自体を改ざんすることは不可能だろうか? 答えは「容易」だ。
ここに必要なのが「公開鍵暗号(RSA/ECC)」を用いたデジタル署名だ。
- 共通鍵(AES等): データの秘匿には最強だが、鍵の配布・管理が地獄。
- 公開鍵(RSA/ECDSA等): データの「真正性」を証明するのに最適。
私たちがやるべきは、ビルドされた成果物に「開発者の秘密鍵で署名」を付与し、デプロイ先で「公開鍵を使って検証」すること。これだけで、途中で何者かがバイナリにバックドアを仕込んでも、検証フェーズで即座に検知(デプロイ停止)できる。
—
2. 実装:Pythonによる署名検証の自動化
CI/CDパイプラインのデプロイ直前に、このスクリプトを走らせるルールを徹底してほしい。cryptographyライブラリを使用した、実務でそのまま使えるコードだ。
# 必要なライブラリ: pip install cryptography
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization
def verify_artifact(artifact_path, signature_path, public_key_path):
# 公開鍵を読み込む
with open(public_key_path, "rb") as key_file:
public_key = serialization.load_pem_public_key(key_file.read())
# 署名とバイナリを読み込む
with open(signature_path, "rb") as f:
signature = f.read()
with open(artifact_path, "rb") as f:
data = f.read()
# 検証:改ざんがあればここで例外が発生する
try:
public_key.verify(
signature,
data,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
print("【成功】署名検証OK。デプロイを続行します。")
except Exception as e:
print(f"【危険】改ざんの疑いあり!デプロイを直ちに停止してください: {e}")
exit(1)
# 実行例
# verify_artifact("app.tar.gz", "app.tar.gz.sig", "public_key.pem")
—
3. Webアプリ側の対策:設定ファイルの整合性
Webアプリ側で「外部から読み込む設定ファイルやJSONデータ」を扱う際も同様だ。特に、eval()やincludeで外部ファイルを読み込むコードを書くのは言語道断。
もしどうしても外部JSONを読み込む必要があるなら、必ず署名を含めたペイロードを送信させ、受け取り側で検証する。
PHPでの検証例:
<?php
// 信頼された公開鍵
$publicKey = openssl_pkey_get_public(file_get_contents('public_key.pem'));
// データと署名(JSONの特定のフィールドに署名を載せるのが一般的)
$data = '{"config": "value"}';
$signature = base64_decode($_POST['signature']);
// verify: 1なら正常、0なら改ざん
$result = openssl_verify($data, $signature, $publicKey, OPENSSL_ALGO_SHA256);
if ($result === 1) {
// 処理実行
$config = json_decode($data);
} else {
// セキュリティログに出力し、管理者にアラートを飛ばす
error_log("Security Alert: Data integrity failure!");
die("不正なデータです。");
}
?>
—
4. 現場のチーフエンジニアからのアドバイス
実務で私が最も重視するのは、「鍵の管理」だ。コードをどれだけセキュアに書いても、秘密鍵がgitリポジトリにコミットされていたら全てが台無しになる。
1. 秘密鍵は環境変数やKey Management Service (KMS) に閉じ込める:絶対にソースコードと一緒に保存しないこと。
2. CI/CDの権限分離: デプロイ権限を持つアカウントと、署名を行うアカウントは分離する。
3. 「信頼できない」を前提にする: 外部からの設定ファイルは、必ずスキーマ検証(JSON Schema等)と署名検証の二段構えでガードする。
エンジニアの仕事は、コードを書くことだけではない。「何が攻撃者にとっての利益になるか」を常に想像し、その道を塞ぐことだ。この署名検証の実装は、一見手間かもしれない。だが、インシデント発生時の被害額を想像すれば、これほど安上がりな保険はないはずだ。
さあ、今日から君たちのパイプラインに「検証」のプロセスを組み込んでくれ。健闘を祈る。
コメント