Dockerfile安全最佳实践

2020 年 11 月 19 日

Dockerfile安全最佳实践

容器安全涉及问题很多,有许多“唾手可得”的方案能用来降低风险。不过,一个好的开始是编写 Dockerfile 文件时遵循一些规则。


在本文,我列出了一些常见的安全问题和如何规避它们。对于每一个问题,我还写了一个开放策略代理(Open Policy Agent,OPA)规则来使用conftest静态分析你的 Dockerfile 文件。


你可以在这个库找到.rego规则集。

不要在环境变量中存放密钥


密钥部署是一个很棘手的问题,而且很容易出错。对于容器化的应用程序,可以通过挂载卷从文件系统中显示它们,也可以更方便地通过环境变量显示。


使用ENV来存储密钥通常是不好的,因为 Dockerfile 文件通常与应用程序一起部署,因此这与在代码中硬编码密钥没有什么差别。


如何检测这一点:

secrets_env = [    "passwd",    "password",    "pass", #  "pwd", can't use this one       "secret",    "key",    "access",    "api_key",    "apikey",    "token",    "tkn"]
deny[msg] { input[i].Cmd == "env" val := input[i].Value contains(lower(val[_]), secrets_env[_]) msg = sprintf("Line %d: Potential secret in ENV key found: %s", [i, val])}
复制代码

只使用信任的根镜像


针对容器化应用程序的攻击链也来自构建容器本身所使用的层次结构。其中,主要的罪魁祸首明显是使用的根镜像。不受信的根镜像是一个高风险,任何时候都应该避免使用。


Docker 为大多数使用的操作系统和应用程序提供了一组官方根镜像。使用这些镜像,我们通过 Docker 自身分担的一些责任降低了协议风险。


如何检测这一点:

deny[msg] {    input[i].Cmd == "from"    val := split(input[i].Value[0], "/")    count(val) > 1    msg = sprintf("Line %d: use a trusted base image", [i])}
复制代码

这条规则针对的是 DockerHub 的官方镜像。由于我只检测到了 namespace 的缺失,这是非常愚蠢的。


信任的定义取决于你的上下文:可以相应地更改这条规则。


不要对根镜像使用'latest'标签


固定基础镜像的版本将使你对正在构建的容器的预期比较安心。


如果你依赖最新的(latest)版本,你可能会不知不觉地继承更新包,这在最好的坏情况下可能会影响你应用程序的可靠性,在最差的坏情况下可能会引入一个漏洞。


如何检测这一点:

deny[msg] {    input[i].Cmd == "from"    val := split(input[i].Value[0], ":")    contains(lower(val[1]), "latest"])    msg = sprintf("Line %d: do not use 'latest' tag for base images", [i])}
复制代码

避免 curl 命令


从互联网上拉取东西,并通过管道将它放到一个 shell 脚本中是非常糟糕的。不幸的是,这是一个比较广泛应用的方案来流式安装软件。


wget https://cloudberry.engineering/absolutely-trustworthy.sh | sh
复制代码


供应链攻击的风险与此相同,归根结底就是信任。如果你不得不使用 curl 命令,就请正确使用:


  • 使用可信来源

  • 使用安全连接

  • 验证下载内容的真实性和完整性


如何检测这一点:

deny[msg] {    input[i].Cmd == "run"    val := concat(" ", input[i].Value)    matches := regex.find_n("(curl|wget)[^|^>]*[|>]", lower(val), -1)    count(matches) > 0    msg = sprintf("Line %d: Avoid curl bashing", [i])}
复制代码

不要更新你的系统包


这可能有点儿牵强,但理由如下:你想要固定你的软件依赖的版本,如果你运行apt-get upgrade,你会将它们都更新到最新的版本。


如果你做了更新,而且你对根镜像使用latest标签,那么你就放大了你的依赖树的不确定性。


你要做的是固定根镜像的版本,并且只运行apt/apk update


如何检测这一点:

upgrade_commands = [    "apk upgrade",    "apt-get upgrade",    "dist-upgrade",]
deny[msg] { input[i].Cmd == "run" val := concat(" ", input[i].Value) contains(val, upgrade_commands[_]) msg = sprintf(“Line: %d: Do not upgrade your system packages", [i])}
复制代码

尽可能不要使用 ADD 命令


ADD命令的一个小功能是,将它指向一个远程 url,然后它会在构建时获取 url 的内容:

ADD https://cloudberry.engineering/absolutely-trust-me.tar.gz
复制代码

比较讽刺的是,官方文档建议使用 curl 命令来代替它。


从安全角度来看,不要这么做。事先获取你需要的内容,对其进行验证,然后COPY。但是,如果你真的需要,在安全连接上使用可靠信源


注意:如果你有一个奇特的构建系统,动态生成 Dockerfile 文件,那么ADD肯定会被使用到。


如何检测这一点:

deny[msg] {    input[i].Cmd == "add"    msg = sprintf("Line %d: Use COPY instead of ADD", [i])}
复制代码

不要使用 root 用户


容器中的 root 和主机上的 root 相同,但会受到 docker 守护程序配置的限制。无论有什么限制,如果一个人突破了容器,他也能够找到一种方法来获取访问主机的完整权限。


当然,这是不理想的,你的威胁模型不能忽视作为 root 用户运行所带来的风险。


因此,最好始终指定一个用户:

USER hopefullynotroot
复制代码


需要注意的是,在 dockerfile 中明确设定一个用户只是一层防线,不会解决所有以root用户运行所带来的问题


相反,我们可以——也应该采用层次防御方案并在整个堆栈中一步步缓解:严格配置 docker 守护进程或者使用一个非 root 容器方案,限制运行时配置(如果可能的话,禁止--privileged)等等。


如果检测这一点:

any_user {    input[i].Cmd == "user" }
deny[msg] { not any_user msg = "Do not run as root, use USER instead"}
复制代码

不要使用 sudo 命令


既然不能使用root用户,那你自然也不能使用 sudo 命令。


即使你作为一个普通用户运行,也要确保这个用户不属于sudoers

deny[msg] {    input[i].Cmd == "run"    val := concat(" ", input[i].Value)    contains(lower(val), "sudo")    msg = sprintf("Line %d: Do not use 'sudo' command", [i])}
复制代码

原文链接:


https://cloudberry.engineering/article/dockerfile-security-best-practices/


2020 年 11 月 19 日 12:421093
用户头像

发布了 95 篇内容, 共 32.4 次阅读, 收获喜欢 110 次。

关注

评论

发布
暂无评论
发现更多内容

目光聚集之处,金钱必将追随

Tom

学习 个人成长 思考 读书

Java 为什么需要包装类

Rayjun

Java

Linux学习-2020.05.11

蜗牛前进

DDD 实践手册(6. Bounded Context - 限界上下文)

Joshua

企业架构 设计模式 领域驱动设计 DDD 架构模式

Python程序性能分析和火焰图

ElvinYang

对话 CTO | 喜茶也有 CTO?听陈霈霖讲讲茶饮中的技术甜度

ONES 王颖奇

研发管理 CTO 零售

错过了初恋,别错过WebFlux

稻草鸟人

stream Spring5 WebFlux Reactive

Try-Catch包裹的代码异常后,竟然导致了产线事务回滚!

码大叔

Java spring 事务

追光逐影:读《我们这一代》

北风

认识数据产品经理(二 数据产品经理的稀缺性)

马踏飞机747

大数据 互联网 数据分析 数据产品经理

接口限流算法有哪些,看完这篇又能和面试官互扯了~

不才陈某

分布式 后端 Java、

ShedLock:一个轻量级的定时任务协调组件

SunYk

定时任务 shedlock

NIO 看破也说破(三)—— 不同的IO模型

小眼睛聊技术

Java 深度思考 学习方法 程序员 架构

从技术层面理解对于区块链技术的10.24集体学习讲话

MaxHu

区块链 智能合约 以太坊 加密货币 去中心化网络

你的团队属于部落的哪个阶段?

Yanel 说敏捷产品

敏捷 敏捷开发 敏捷精髓

带你吃透原型设计

Yanel 说敏捷产品

产品 产品经理 产品设计 产品开发 产品推荐

如何让团队产生“多米诺骨牌”效应?

Yanel 说敏捷产品

项目管理 敏捷 敏捷开发 敏捷精髓

对话 CTO | 听快看漫画 CTO 李润超讲重塑漫画产业的技术推动力

ONES 王颖奇

研发管理 CTO 动画 文化

Python网络编程socket 简易聊天窗

蜗牛前进

良好的工作习惯——及时存档、备份

Sicolas Flamel

工作效率

C语言常量、变量和关键字

C语言技术网-码农有道

C语言 常量 变量 关键字

也谈程序员的核心竞争力

我心依然

程序员 竞争力 独立思考 清晰表达 持续学习

你真的懂"看板文化"么?

Yanel 说敏捷产品

敏捷 敏捷开发 敏捷精髓

危机过后,「表格文档协同」需要具备什么能力?

Geek_Willie

前端开发 开发者工具 Excel

工具集系列|值得收藏的几个免费在线学习国外网站

一尘观世界

学习 工具 网站 在线学习 提升

每个人都应该知道的性能参数

ElvinYang

ITerm2 + Oh my ZSH + Powerlevel10k

JDoe

配置

【解析+示例】2种方法,通过SpreadJS在前端实现甘特图

Geek_Willie

前端开发 甘特图 SpreadJS 表格控件

如何高效阅读

ElvinYang

前端有未来吗?

欧雷

前端 前端开发

医院陪护5天的四点感受

赵新龙

身心健康 医院

Dockerfile安全最佳实践-InfoQ