Lambdaコンテナイメージの脆弱性を最小化する:Python3.12編
概要
今回はPython3.12用のLambdaコンテナイメージでInspectorによって脆弱性として検出されるCVE(Common Vulnerabilities and Exposures)の検知数を可能な限り減らす際にやったことをブログにします。
今後同様の手順が実施できるように記事として残します。もう少しスマートな方法もありそうなので、コメントなどで教えていただけると助かります!
前提
AWSから提供されているLambda用のコンテナイメージは、Inspectorで検知した段階でいくつかの脆弱性が検出されます。これは常に新規の脆弱性が報告され、対応前だったり、AWSチームとしては対応不要と判断されているようです。
InspectorはLambda上でアプリケーションが動作している前提を考慮せずにスキャンします。ですので、Lambda上ではAWS側の責任境界となる部分も検知されます。
ベースイメージの脆弱性は割り切って無視することも可能です。ただ、可能な限りコンテナイメージに脆弱性を含めないことで、報告されている脆弱性がLambda上で問題あるか検査する時間を削減することができます。この前提で後続の作業に取り組んでいます。
全体の流れ
基本的に以下の流れで脆弱性の確認、改善策の検討、対処というステップで進めます。
- InspectorでCVEの数を確認
- イメージ単体の脆弱性を確認
- アプリの脆弱性を確認
- イメージ内の脆弱性をもつ機能を減らす
- アプリケーションのrequirements.txtを更新しパッケージを最新化
- マルチステージビルドで実行時に使用する機能以外を削除
- イメージ内部のパッケージを強制アップデート
- 脆弱性をもち、Lambdaの起動に不要な機能を削除
アプリケーションのサンプル
今回はアプリケーションの例としてPR-Agentを使用します。
PR-Agentはコードに対してLLMを使ってGitHub上などでAIコードレビューを行えるツールです。実行はGitHub Actions上かLambda上で行えます。今回はLambda上で起動する方を取り上げます。詳細なセットアップ手順などは以下をご確認ください。
以降は脆弱性の確認をしていきます。
InspectorでCVEを確認
まずはベースイメージにどのぐらいCVEが含まれているか確認します。以降の手順を行う前にInspectorV2を有効化して、コンテナイメージのスキャンを有効化しておきます。またECRのpr-agent
というリポジトリを作成しておいてください。
イメージ単体の脆弱性を確認
次にベースイメージとして使用する最小限のDockerfileを用意します。今回はPR-Agentのものを流用するのでdocker/Dockerfile.lambda
を修正します。
FROM public.ecr.aws/lambda/python:3.12
用意したDockerfileをビルド、タグ作成、ECRへのログイン、ECRへのプッシュをしていきます。以降ECRへプッシュと書く場合は、以下のコマンドの流れを実行するという意味で書きます。
# ECRへPushする際に必要なAWSアカウントIDとリージョンを設定
export AWS_ACCOUNT=123456789012
export AWS_REGION=ap-northeast-1
# コンテナイメージ名を設定
export IMAGE_NAME=serverless
# Lambda用Dockerイメージをビルド(プラットフォーム指定、タグ付け、Dockerfile指定)
docker buildx build --platform=linux/amd64 . -t codiumai/pr-agent:$IMAGE_NAME -f docker/Dockerfile.lambda
# ECR向けにイメージをタグ付け
docker tag codiumai/pr-agent:$IMAGE_NAME $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/pr-agent:$IMAGE_NAME
# ECRにログイン
aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com
# ECRへPush
docker push $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/pr-agent:$IMAGE_NAME
ECRへPushまで完了すると2〜3分後にInspector側で脆弱性が検知されます。
現状はCVEは合計14件検知されています。
アプリの脆弱性を確認
次にアプリを加えた状態でInspectorによるスキャン結果を見てみます。Dockerfileの記述は以下のリンク先のものです。
FROM public.ecr.aws/lambda/python:3.12
RUN dnf update -y && \
dnf install -y gcc python3-devel git && \
dnf clean all
ADD pyproject.toml requirements.txt ./
RUN pip install --no-cache-dir . && rm pyproject.toml
RUN pip install --no-cache-dir mangum==0.17.0
COPY pr_agent/ ${LAMBDA_TASK_ROOT}/pr_agent/
CMD ["pr_agent.servers.serverless.serverless"]
上記のDockerfileで生成できるコンテナイメージをプッシュすると以下のような結果になります。
現状はCVEは合計45件検知されています。数日前に検証した段階よりも増えていました。
イメージ内の脆弱性をもつ機能を減らす
ここからはタイトル通り、可能な限り脆弱性を減らしていきます。今回は元のイメージを改善していく方法でトライします。
アプリのrequirements.txtを更新しパッケージを最新化
Inspectorなどのイメージスキャンする機能は、ライブラリやパッケージなどに含まれるCVEがないかを検知します。ですので、単純にパッケージを脆弱性を含まないバージョンに更新して取り除きます。脆弱性の確認にはpip-auditを使います。
pip-auditを使うと、現在のrequirements.txtファイルに脆弱性が含まれるかローカルで確認できます。後は、地道に1個ずつバージョンを更新するか、AIエージェントに頼むかでバージョンを更新してください。
更新前:
% pip-audit --requirement requirements.txt
Found 11 known vulnerabilities in 7 packages
Name Version ID Fix Versions
-------------- ------- ------------------- -------------
aiohttp 3.9.5 GHSA-8495-4g3g-x7pr 3.10.11
azure-identity 1.15.0 GHSA-m5vv-6r4h-3vj9 1.16.1
jinja2 3.1.2 GHSA-h5c8-rqwp-cp95 3.1.3
jinja2 3.1.2 GHSA-h75v-3vvj-5mfj 3.1.4
jinja2 3.1.2 GHSA-q2x7-8rv6-6q7h 3.1.5
jinja2 3.1.2 GHSA-gmj6-6f8f-6699 3.1.5
jinja2 3.1.2 GHSA-cpwx-vrp4-4pq7 3.1.6
gunicorn 22.0.0 GHSA-hc5x-x2vx-497g 23.0.0
py 1.11.0 PYSEC-2022-42969
starlette 0.37.2 GHSA-f96h-pmfr-66vw 0.40.0
urllib3 2.0.7 GHSA-34jh-p97f-mpxf 1.26.19,2.2.2
更新後:
% pip-audit --requirement requirements.txt
Found 1 known vulnerability in 1 package
Name Version ID Fix Versions
---- ------- ---------------- ------------
py 1.11.0 PYSEC-2022-42969
脆弱性が1件は残ってますが直接読んでいるパッケージでなく、パッケージが依存しているパッケージになります。内容は以下でSubversionにアクセスしない場合はまったく関係ない脆弱性なので今回は受容する方向で考えます。
The py library through 1.11.0 for Python allows remote attackers to conduct a ReDoS (Regular expression Denial of Service) attack via a Subversion repository with crafted info data, because the InfoSvnCommand argument is mishandled.
マルチステージビルドで実行時に使用する機能以外を削除
パッケージの脆弱性は11件で、全体では45件ありました。次にマルチステージビルドで脆弱性を減らしていきます。
今回のアプリでは、gcc, python3-devel, gitをビルド時に使っています。
gitは、実行環境でも使用するので上記2つはビルド用のステージでだけ追加します。
FROM public.ecr.aws/lambda/python:3.12 as builder
RUN dnf update -y && \
dnf install -y gcc python3-devel git && \
dnf clean all
...
FROM public.ecr.aws/lambda/python:3.12
RUN dnf update -y && \
dnf install -y git && \
dnf clean all
またついでにpip installなどの工程をステージ分離することで、ビルド時間を短縮して開発効率を上げます。最終的には、以下のようになります。
# ビルドステージ - 依存関係のインストールとコンパイル
FROM public.ecr.aws/lambda/python:3.12 as builder
RUN dnf update -y && \
dnf install -y gcc python3-devel git && \
dnf clean all
WORKDIR /build
COPY requirements.txt pyproject.toml ./
COPY pr_agent/ ./pr_agent/
RUN pip install --no-cache-dir . && rm pyproject.toml
RUN pip install --no-cache-dir mangum==0.17.0
# 本番ステージ - 最小限の実行環境
FROM public.ecr.aws/lambda/python:3.12
RUN dnf update -y && \
dnf install -y git && \
dnf clean all
# ビルド済み依存関係をコピー
COPY --from=builder /var/lang/lib/python3.12/site-packages/ /var/lang/lib/python3.12/site-packages/
# アプリケーションコードのみコピー
COPY --from=builder /build/pr_agent/ ${LAMBDA_TASK_ROOT}/pr_agent/
CMD ["pr_agent.servers.serverless.serverless"]
これでビルド時のみに必要なものを本番用イメージから外します。同じようにイメージをビルドしてプッシュします。すると脆弱性が全体で16件と減っていることが確認できます。
ただまだ脆弱性が残っているので、イメージを削っていきます。
イメージ内部のパッケージをアップデート
Inspectorの記述を追っていくと、どのパッケージに問題があってどんな対策をすべきかが確認できます。先程のイメージで確認してみるとdnfのアップデートを推奨されています。以下はCVE-2025-40909 - perl-AutoLoader, perl-B and 25 more
の内容です。
ただこのイメージではdnfが追加するパッケージのバージョンが固定されています。常に管理者側もイメージのパッケージを最新化しようとしていますが、間に合わない場合もあるのでその際はバージョン固定を解除することで対処できるコメントが何回かあります。これを参考に設定してみます。
...
RUN rm /etc/dnf/vars/releasever && \
dnf -y --refresh update --releasever=latest
...
上記コマンドを入れると脆弱性を4件まで減らせます。
※注意:上記のdnf updateを実行すると、privateなAmazon環境想定の設定になりdnf installがうまく動かなくなる場合もあるようなので、必要なパッケージはこのステージの前に追加してください。
脆弱性をもち、Lambdaの起動に不要な機能を削除
残り4件の脆弱性を見ていきます。以下はCVE-2025-22874 - go/stdlib
の内容です。
検知されている対象がファイルパスで表示されることもあります。これをみるとaws-lambda-rie
が関連することがわかります。これは以下のリポジトリの内容です。
機能としては以下のように書かれている通り、Lambdaのランタイム API をローカル環境でエミュレートするプロキシとして機能するものです。
本番での実行には不要なものなので今回は本番用のビルドステージで単純に削除します。
...
RUN rm /usr/local/bin/aws-lambda-rie
...
こちらのステージを追加して最後にビルド、プッシュすると以下のように脆弱性1件まで減らせることが確認できました🎉
(先に作っていたので時間の時系列は変な感じですが気にしないでください)
最後にpy
パッケージの脆弱性だけが残りますが、これは前述の通りSubversionに関連するものです。Lambdaからアクセスする可能性はないため、今回は受容する判断としました。
最終的なDockerfileは以下のようになります。
# ビルドステージ - 依存関係のインストールとコンパイル
FROM public.ecr.aws/lambda/python:3.12 as builder
RUN dnf update -y && \
dnf install -y gcc python3-devel git && \
dnf clean all
WORKDIR /build
COPY requirements.txt pyproject.toml ./
COPY pr_agent/ ./pr_agent/
RUN pip install --no-cache-dir . && rm pyproject.toml
RUN pip install --no-cache-dir mangum==0.17.0
# ===========================================
# 本番ステージ - 最小限の実行環境
FROM public.ecr.aws/lambda/python:3.12
RUN dnf update -y && \
dnf install -y git && \
dnf clean all
# releaseverを削除してリリースバージョン固定を解除し、セキュリティアップデートを適用
RUN rm /etc/dnf/vars/releasever && \
dnf -y --refresh update --releasever=latest
# Lambdaのローカルテスト用のaws-lambda-rieにGoの脆弱性が含まれるため削除
RUN rm /usr/local/bin/aws-lambda-rie
COPY --from=builder /var/lang/lib/python3.12/site-packages/ /var/lang/lib/python3.12/site-packages/
COPY --from=builder /build/pr_agent/ ${LAMBDA_TASK_ROOT}/pr_agent/
CMD ["pr_agent.servers.serverless.serverless"]
当たり前ではありますが最後にテストや動作確認をして挙動に問題ないか確認してください。今回はレビュー機能の動作が確認できたので問題なしとしました。
おまけ:distrolessやslimイメージは使わないのか?
他にもdistrolessやslimなど軽量なコンテナイメージを使う方法もありますが、公式イメージの方がLambda上での実行に最適化されており起動も早いので公式のイメージを活かす方式で考えます。
※正直distrolessやslimで動かすの面倒くさそうだなという気もあったので、うまく動かしている方いればブログ化してもらえると嬉しいです!
所感
理屈では把握していたものの、実際にコンテナLambdaを使う機会が少なかったため、CVEへの対応を今回あえて記事としてまとめてみました。もっとスマートな方法があればぜひコメントやブログで共有していただけると嬉しいです!
具体例を使って脆弱性を削減していく手順がそこまで見当たらなかったので、同じことをやりたい場合は参考になると思います。少しでもどなたかの参考になれば幸いです。
Discussion