meles 是一个用于容器镜像的恶意软件扫描器:只需对镜像引用执行一条命令,无需 Docker 守护进程,提供适合 CI 的退出码。在 Kubernetes 中,它还会扫描 Pod 运行的每个镜像,并且如果你愿意,可以暂缓新 Pod 的启动,直到其镜像完成扫描。
meles 在镜像运行之前检查其内容。它补充漏洞扫描器和运行时检测(如 Falco);它并不取代它们。
这个名字来自 Meles meles,即欧洲獾——它会挖掘穿透各层。
meles image <ref> 在发现恶意软件时使流水线失败。输出为表格、JSON 或 SARIF,并可通过忽略规则处理误报。MalwareReport,可选提供 Prometheus 告警。在你的机器上试用(Linux amd64、Docker、bash):
curl -fsSL https://github.com/movray/meles/releases/download/v0.3.3/meles_0.3.3_linux_amd64.tar.gz | tar -xz meles
docker run -d --name clamd -p 127.0.0.1:3310:3310 clamav/clamav:1.4
export MELES_CLAMD=tcp://127.0.0.1:3310
until (exec 3<>/dev/tcp/127.0.0.1/3310 && printf 'zPING\0' >&3 && head -c4 <&3 | grep -q PONG) 2>/dev/null; do sleep 5; done
./meles selftest
./meles image alpine:3.20
或在集群中(集群管理员、默认 StorageClass、节点上约 2 GB 可用内存):
kubectl create namespace meles
kubectl label namespace meles pod-security.kubernetes.io/enforce=baseline
helm install meles oci://ghcr.io/movray/charts/meles --version 0.3.3 -n meles --set operator.enabled=true
kubectl -n meles rollout status deploy/meles-clamd --timeout=15m
kubectl get malwarereports -A # every image running in your cluster
kubectl -n meles port-forward svc/meles-server 8080:80 # http://localhost:8080/
操作员现在会拉取并扫描每个 Pod 的镜像,这会占用你镜像仓库的拉取限额;它只报告,不会触碰你的 Pod。报告会在几分钟内出现。非常大的二进制文件可能最终显示为 Incomplete,这绝不算干净:参见
读取结果。
端口转发只是查看结果的最快方式。若要长期使用,请通过 server.ingress 或 server.httpRoute 暴露 UI,并通过 server.oauth2Proxy 在其前面加上登录,这些都在 chart 的 values 中配置:
Web UI 的登录。meles 自身没有登录功能,因此不要在没有登录的情况下暴露 UI。
从被后续层隐藏的文件到 Kubernetes 中的扫描门禁,逐步导览:docs/getting-started.md。
clamd 通过其 socket 协议完成。meles 不链接 libclamav,并保持 Apache-2.0 许可。默认情况下 meles 只报告,且从不删除任何内容。它不能替代镜像签名、SBOM 或运行时检测。
每个 release 都附带适用于 Linux 和 macOS 的二进制文件(amd64、arm64)、多架构镜像和 Helm chart:
VERSION=0.3.3
# Binary
curl -fsSLO https://github.com/movray/meles/releases/download/v$VERSION/meles_${VERSION}_linux_amd64.tar.gz
tar -xzf meles_${VERSION}_linux_amd64.tar.gz meles
# Image (linux/amd64, linux/arm64; no shell, runs as non-root)
docker run --rm ghcr.io/movray/meles:$VERSION version
# Helm chart (see Kubernetes below)
helm install meles oci://ghcr.io/movray/charts/meles --version $VERSION -n meles -f my-values.yaml
# From source
go install github.com/movray/meles/cmd/meles@v$VERSION
二进制文件、镜像和图表由 release workflow 构建,并带有签名的构建 来源信息。镜像在发布前会使用 meles 和 Trivy 进行扫描。 使用 GitHub CLI 检查来源信息:
gh attestation verify meles_${VERSION}_linux_amd64.tar.gz -R movray/meles
gh attestation verify oci://ghcr.io/movray/meles:$VERSION -R movray/meles
gh attestation verify oci://ghcr.io/movray/charts/meles:$VERSION -R movray/meles
镜像和图表也使用 cosign(无密钥)进行签名,因此 Kyverno 或 Sigstore policy-controller 等准入策略可以对其进行检查。签名是以 OCI 引用形式存储的 Sigstore 捆绑包:使用 cosign 3 进行验证(cosign 2 需要 --new-bundle-format):
cosign verify ghcr.io/movray/meles:$VERSION \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp '^https://github.com/movray/meles/\.github/workflows/release\.yml@refs/tags/v'
你需要一个正在运行的 clamd。使用 apt install clamav-daemon 时,meles 会自动找到它的
socket(/run/clamav/clamd.ctl);clamav/clamav 镜像同样可用
(参见 TL;DR;加载签名需要一两分钟)。
meles selftest # checks the whole chain with EICAR test images
# (--yara: also the default YARA rules, if loaded)
meles image alpine:3.20
meles image --format json --platform linux/amd64 ghcr.io/org/app:1.2
alpine:3.20 (d9e853e87e55)
engine: ClamAV 1.4.3, signatures 28159, scanned in 2.6s
PLATFORM LAYERS FILES FINDINGS SKIPPED
linux/amd64 1 87 0 0
linux/arm64/v8 1 87 0 0
No malware found.
| 代码 | 含义 |
|---|---|
0 | 干净(或在 --exit-code 0 下发现结果,仅报告模式) |
1 | 发现恶意软件,或文件未被扫描(可通过 --exit-code 和 --skipped-exit-code 更改) |
2 | 错误:拉取、引擎或配置。绝不报告为干净。 |
meles 默认在发现结果时使流水线失败,因此无需额外标志即可作为门禁使用。
未被扫描的文件(超过 --max-file-size,或超过 clamd 的某个限制)会被列为已跳过,并且报告被标记为不完整:表格显示“Scan INCOMPLETE”而不是“No malware found.”,JSON 报告包含 "complete": false,并且 meles 像发现结果时一样以 1 退出。任何大小限制都可以通过使文件大于该限制来规避,因此未扫描的文件绝不能通过安全门禁。使用 --skipped-exit-code 0 仅报告跳过的文件,或使用 2 将其视为错误。
--max-file-size 默认为 768M,最高可提升至 ClamAV 自身的限制 2047M。大于 4 MiB 的文件会以流式传输到 clamd,因此较高的限制不会在 meles 中消耗内存;clamd 在扫描每个流时会将其保存在临时文件中。clamd 的 StreamMaxLength、MaxFileSize 和 MaxScanSize 必须至少同样大(Helm chart 将它们设置为至少 scan.maxFileSize,参见 clamd 中的限制;clamd 自身的默认 StreamMaxLength 仅为 100M)。clamd 因文件过大而拒绝的文件会被报告为已跳过。更大的文件,例如大型 ML 模型,根本无法被 ClamAV 扫描。
每个标志也可以设置为环境变量 MELES_<FLAG>,例如 MELES_CLAMD=tcp://clamd:3310 或 MELES_PLATFORM=host。标志优先于环境变量,环境变量优先于默认值。