mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-13 03:59:12 +00:00
206 lines
7.7 KiB
YAML
206 lines
7.7 KiB
YAML
name: Image Scan
|
|
|
|
on:
|
|
pull_request:
|
|
branches:
|
|
- main
|
|
- litellm_oss_branch
|
|
- "litellm_**"
|
|
paths:
|
|
- Dockerfile
|
|
- .dockerignore
|
|
- docker-entrypoint.sh
|
|
- migrations/run.py
|
|
- gateway/main.py
|
|
- gateway/launch.py
|
|
- gateway/routes/allowlist.py
|
|
- backend/main.py
|
|
- backend/routes/allowlist.py
|
|
- litellm-rust/**
|
|
- litellm/proxy/lens/**
|
|
- litellm/proxy/prisma_migration.py
|
|
- litellm/proxy/admin_mcp.py
|
|
- litellm/proxy/proxy_server.py
|
|
- pyproject.toml
|
|
- litellm-proxy-extras/**
|
|
- tests/integration/database/image/**
|
|
- uv.lock
|
|
- ui/litellm-dashboard/package-lock.json
|
|
- ui/nginx.conf
|
|
- .github/workflows/image-scan.yml
|
|
- .grype.yaml
|
|
schedule:
|
|
- cron: "41 6 * * *"
|
|
workflow_dispatch:
|
|
|
|
permissions: {}
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
IMAGE: litellm-image-scan:${{ github.sha }}
|
|
ADMIN_MCP_SCHEMA_IMAGE: litellm-admin-mcp-schema:${{ github.sha }}
|
|
|
|
jobs:
|
|
build:
|
|
name: build
|
|
runs-on: ubuntu-latest
|
|
if: >-
|
|
github.event_name != 'pull_request' ||
|
|
github.event.pull_request.head.repo.full_name == github.repository
|
|
timeout-minutes: 30
|
|
permissions:
|
|
contents: read
|
|
steps:
|
|
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Build the image
|
|
run: docker build --build-arg LITELLM_RELEASE_TAG=v0.0.0-lens-ci -t "$IMAGE" .
|
|
|
|
- name: Save the image for the verify matrix
|
|
run: docker save "$IMAGE" | zstd -T0 -3 -o "$RUNNER_TEMP/litellm-image.tar.zst"
|
|
|
|
- uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
|
with:
|
|
name: litellm-image
|
|
path: ${{ runner.temp }}/litellm-image.tar.zst
|
|
retention-days: 1
|
|
compression-level: 0
|
|
|
|
- name: Build the Admin MCP schema image
|
|
run: docker build --build-arg LITELLM_RELEASE_TAG=v0.0.0-lens-ci --target builder -t "$ADMIN_MCP_SCHEMA_IMAGE" .
|
|
|
|
- name: Save the Admin MCP schema image for the verify matrix
|
|
run: docker save "$ADMIN_MCP_SCHEMA_IMAGE" | zstd -T0 -3 -o "$RUNNER_TEMP/litellm-admin-mcp-schema.tar.zst"
|
|
|
|
- uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
|
with:
|
|
name: litellm-admin-mcp-schema
|
|
path: ${{ runner.temp }}/litellm-admin-mcp-schema.tar.zst
|
|
retention-days: 1
|
|
compression-level: 0
|
|
|
|
verify:
|
|
name: verify (${{ matrix.check }})
|
|
needs: build
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 45
|
|
permissions:
|
|
contents: read
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
check: [scan, migrations, gateway, backend, ui, collector]
|
|
steps:
|
|
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
|
with:
|
|
name: litellm-image
|
|
path: ${{ runner.temp }}
|
|
|
|
- name: Load the image
|
|
run: zstd -d --stdout "$RUNNER_TEMP/litellm-image.tar.zst" | docker load
|
|
|
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
|
if: matrix.check == 'migrations' || matrix.check == 'backend'
|
|
with:
|
|
name: litellm-admin-mcp-schema
|
|
path: ${{ runner.temp }}
|
|
|
|
- name: Load the Admin MCP schema image
|
|
if: matrix.check == 'migrations' || matrix.check == 'backend'
|
|
run: zstd -d --stdout "$RUNNER_TEMP/litellm-admin-mcp-schema.tar.zst" | docker load
|
|
|
|
- name: Set up Python
|
|
if: matrix.check != 'scan'
|
|
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
|
with:
|
|
python-version: "3.12"
|
|
|
|
- name: Install pytest
|
|
if: matrix.check != 'scan'
|
|
run: python -m pip install "pytest==9.0.3"
|
|
|
|
# The prisma bake must migrate a fresh DB with no egress as an arbitrary
|
|
# non-root uid (OpenShift restricted-v2 / air-gapped / readOnlyFilesystem).
|
|
# `docker run` as the default uid with network hides a broken bake because
|
|
# the migration entrypoint exits 0 even when it applied nothing; asserting
|
|
# the schema was created is what catches it.
|
|
- name: Verify offline migration as a non-root uid
|
|
if: matrix.check == 'migrations'
|
|
env:
|
|
LITELLM_IMAGE: ${{ env.IMAGE }}
|
|
LITELLM_ADMIN_MCP_SCHEMA_IMAGE: ${{ env.ADMIN_MCP_SCHEMA_IMAGE }}
|
|
run: |
|
|
python -m pytest --noconftest -v \
|
|
tests/integration/database/image/test_offline_image_migration.py \
|
|
tests/integration/database/image/test_image_bedrock_realtime_extra.py \
|
|
tests/integration/database/image/test_image_admin_mcp.py
|
|
|
|
- name: Verify the gateway component serves offline as a non-root uid
|
|
if: matrix.check == 'gateway'
|
|
env:
|
|
LITELLM_IMAGE: ${{ env.IMAGE }}
|
|
LITELLM_COMPONENT: gateway
|
|
LITELLM_IMAGE_COMPONENT: gateway
|
|
run: |
|
|
python -m pytest --noconftest -v \
|
|
tests/integration/database/image/test_component_image_serves_offline.py \
|
|
tests/integration/database/image/test_image_admin_mcp.py
|
|
|
|
- name: Verify the backend component serves offline as a non-root uid
|
|
if: matrix.check == 'backend'
|
|
env:
|
|
LITELLM_IMAGE: ${{ env.IMAGE }}
|
|
LITELLM_COMPONENT: backend
|
|
LITELLM_IMAGE_COMPONENT: backend
|
|
LITELLM_ADMIN_MCP_SCHEMA_IMAGE: ${{ env.ADMIN_MCP_SCHEMA_IMAGE }}
|
|
run: |
|
|
python -m pytest --noconftest -v \
|
|
tests/integration/database/image/test_component_image_serves_offline.py \
|
|
tests/integration/database/image/test_image_admin_mcp.py
|
|
|
|
- name: Verify the ui component serves as an arbitrary uid with a read-only root fs
|
|
if: matrix.check == 'ui'
|
|
env:
|
|
LITELLM_IMAGE: ${{ env.IMAGE }}
|
|
run: python -m pytest --noconftest -v tests/integration/database/image/test_ui_image_serves_offline.py
|
|
|
|
- name: Verify the collector creates its socket as the image user
|
|
if: matrix.check == 'collector'
|
|
env:
|
|
LITELLM_IMAGE: ${{ env.IMAGE }}
|
|
run: python -m pytest --noconftest -v tests/integration/database/image/test_collector_image_serves_socket.py
|
|
|
|
- name: Download Grype v0.114.0
|
|
if: matrix.check == 'scan'
|
|
run: |
|
|
curl -fsSL --retry 3 -o "$RUNNER_TEMP/grype.tar.gz" \
|
|
https://github.com/anchore/grype/releases/download/v0.114.0/grype_0.114.0_linux_amd64.tar.gz
|
|
echo "edda0968d8827daab01d32b3cd7de192ae0915005e7bbfcfef9e68e79bc43343 $RUNNER_TEMP/grype.tar.gz" | sha256sum -c -
|
|
tar xzf "$RUNNER_TEMP/grype.tar.gz" -C "$RUNNER_TEMP" grype
|
|
chmod +x "$RUNNER_TEMP/grype"
|
|
|
|
# Scans the whole shipped artifact: OS/apk plus every language package
|
|
# baked into the image, including ones no lockfile declares (e.g. prisma's
|
|
# vendored node engine) that osv-scan cannot see. osv-scan stays the fast
|
|
# source-level gate; this is the customer's-eye-view backstop. Credential-
|
|
# free OSS, run as a pinned, checksum-verified binary; no GitHub Action
|
|
# dependency and no vendor SaaS callout.
|
|
- name: Scan image for fixable HIGH/CRITICAL CVEs
|
|
if: matrix.check == 'scan'
|
|
env:
|
|
GRYPE_MATCH_PYTHON_USING_CPES: "true"
|
|
run: |
|
|
"$RUNNER_TEMP/grype" "$IMAGE" \
|
|
--config .grype.yaml \
|
|
--only-fixed \
|
|
--fail-on high \
|
|
--output table
|