📦

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を使用します。

https://github.com/qodo-ai/pr-agent

PR-Agentはコードに対してLLMを使ってGitHub上などでAIコードレビューを行えるツールです。実行はGitHub Actions上かLambda上で行えます。今回はLambda上で起動する方を取り上げます。詳細なセットアップ手順などは以下をご確認ください。

https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-app

以降は脆弱性の確認をしていきます。

InspectorでCVEを確認

まずはベースイメージにどのぐらいCVEが含まれているか確認します。以降の手順を行う前にInspectorV2を有効化して、コンテナイメージのスキャンを有効化しておきます。またECRのpr-agentというリポジトリを作成しておいてください。

イメージ単体の脆弱性を確認

次にベースイメージとして使用する最小限のDockerfileを用意します。今回はPR-Agentのものを流用するのでdocker/Dockerfile.lambdaを修正します。

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側で脆弱性が検知されます。

BaseImageの検知結果

現状はCVEは合計14件検知されています。

アプリの脆弱性を確認

次にアプリを加えた状態でInspectorによるスキャン結果を見てみます。Dockerfileの記述は以下のリンク先のものです。

https://github.com/qodo-ai/pr-agent/blob/main/docker/Dockerfile.lambda

docker/Dockerfile.lambda
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で生成できるコンテナイメージをプッシュすると以下のような結果になります。

BaseImage+Appの検知結果

現状はCVEは合計45件検知されています。数日前に検証した段階よりも増えていました。

イメージ内の脆弱性をもつ機能を減らす

ここからはタイトル通り、可能な限り脆弱性を減らしていきます。今回は元のイメージを改善していく方法でトライします。

アプリのrequirements.txtを更新しパッケージを最新化

Inspectorなどのイメージスキャンする機能は、ライブラリやパッケージなどに含まれるCVEがないかを検知します。ですので、単純にパッケージを脆弱性を含まないバージョンに更新して取り除きます。脆弱性の確認にはpip-auditを使います。

https://github.com/pypa/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をビルド時に使っています。

https://github.com/qodo-ai/pr-agent/blob/cbea0380ece7a4434c037f8d2fac7eefa8082720/docker/Dockerfile.lambda#L3-L5

gitは、実行環境でも使用するので上記2つはビルド用のステージでだけ追加します。

docker/Dockerfile.lambda
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などの工程をステージ分離することで、ビルド時間を短縮して開発効率を上げます。最終的には、以下のようになります。

docker/Dockerfile.lambda
# ビルドステージ - 依存関係のインストールとコンパイル
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の内容です。

Inspector dnf updateの推奨

ただこのイメージではdnfが追加するパッケージのバージョンが固定されています。常に管理者側もイメージのパッケージを最新化しようとしていますが、間に合わない場合もあるのでその際はバージョン固定を解除することで対処できるコメントが何回かあります。これを参考に設定してみます。

https://github.com/aws/aws-lambda-base-images/issues/140
https://github.com/aws/aws-lambda-base-images/issues/224

Dockerfile.lambda
...
RUN rm /etc/dnf/vars/releasever && \
    dnf -y --refresh update --releasever=latest
...

上記コマンドを入れると脆弱性を4件まで減らせます。

dnf updateの検知結果

※注意:上記のdnf updateを実行すると、privateなAmazon環境想定の設定になりdnf installがうまく動かなくなる場合もあるようなので、必要なパッケージはこのステージの前に追加してください。

https://github.com/aws/aws-lambda-base-images/issues/163

脆弱性をもち、Lambdaの起動に不要な機能を削除

残り4件の脆弱性を見ていきます。以下はCVE-2025-22874 - go/stdlibの内容です。

aws-lambda-rieの脆弱性

検知されている対象がファイルパスで表示されることもあります。これをみるとaws-lambda-rieが関連することがわかります。これは以下のリポジトリの内容です。

https://github.com/aws/aws-lambda-runtime-interface-emulator

機能としては以下のように書かれている通り、Lambdaのランタイム API をローカル環境でエミュレートするプロキシとして機能するものです。

https://aws.amazon.com/jp/builders-flash/202104/new-lambda-container-development-2/

本番での実行には不要なものなので今回は本番用のビルドステージで単純に削除します。

Dockerfile.lambda
...
RUN rm /usr/local/bin/aws-lambda-rie
...

こちらのステージを追加して最後にビルド、プッシュすると以下のように脆弱性1件まで減らせることが確認できました🎉
(先に作っていたので時間の時系列は変な感じですが気にしないでください)

aws-lambda-rieを削除した後の検知結果

最後にpyパッケージの脆弱性だけが残りますが、これは前述の通りSubversionに関連するものです。Lambdaからアクセスする可能性はないため、今回は受容する判断としました。

最終的なDockerfileは以下のようになります。

Dockerfile.lambda
# ビルドステージ - 依存関係のインストールとコンパイル
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上での実行に最適化されており起動も早いので公式のイメージを活かす方式で考えます。

https://aws.amazon.com/jp/blogs/news/optimizing-lambda-functions-packaged-as-container-images/

※正直distrolessやslimで動かすの面倒くさそうだなという気もあったので、うまく動かしている方いればブログ化してもらえると嬉しいです!

所感

理屈では把握していたものの、実際にコンテナLambdaを使う機会が少なかったため、CVEへの対応を今回あえて記事としてまとめてみました。もっとスマートな方法があればぜひコメントやブログで共有していただけると嬉しいです!

具体例を使って脆弱性を削減していく手順がそこまで見当たらなかったので、同じことをやりたい場合は参考になると思います。少しでもどなたかの参考になれば幸いです。

Discussion