写点什么

Dockerfile 安全最佳实践

  • 2020-11-19
  • 本文字数:2376 字

    阅读完需:约 8 分钟

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:422573
用户头像

发布了 165 篇内容, 共 83.5 次阅读, 收获喜欢 343 次。

关注

评论

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

消费大众网民的审丑心理,如何拯救扭曲化的自媒体行业

石头IT视角

在线XML转TSV工具

入门小站

工具

spark实战之:分析维基百科网站统计数据(java版)

程序员欣宸

Java spark 8月月更

Spring进阶(一):SpringMVC常用注解标签详解

No Silver Bullet

springmvc 注解 8月月更

头脑风暴:最长连续递增序列

HelloWorld杰少

算法 LeetCode 数据结构, 8月月更

IPv4向IPv6的过渡技术

穿过生命散发芬芳

ipv6 8月月更

架构训练营毕业总结

joak

开源一夏 | 阿里云ECS之Linux 文本操作命令

六月的雨在InfoQ

vim Linux 开源 8月月更 more

开源一夏|三步注册gitee

坚果

开源 8月月更

LabVIEW LINX Toolkit控制Arduino设备(拓展篇—1)

不脱发的程序猿

嵌入式 单片机 LabVIEW Arduino LINX Toolkit

详解CAN总线:什么是CAN总线?

不脱发的程序猿

嵌入式 汽车电子 CAN总线协议

leetcode 304. Range Sum Query 2D - Immutable 二维区域和检索 - 矩阵不可变(中等)

okokabcd

LeetCode 数据结构与算法

C++多态的基本概念与原理刨析

CtrlX

c c++ 面向对象 代码 8月月更

开源一夏 | 阿里云ECS之Linux 系统工作命令

六月的雨在InfoQ

Linux 开源 8月月更 系统命令

开源一夏 | 实战Node.js之GET/POST请求在Web 应用架构在客户端的使用

恒山其若陋兮

开源 8月月更

flstudio21版本有什么新功能?

茶色酒

FL Studio

加密市场由阴转晴,Zebec或成2022后半段黑马

鳄鱼视界

聚焦2022全球边缘计算大会·深圳站,揭秘火山引擎新一代边缘云解决方案

火山引擎边缘云

分布式 CDN 边缘计算 渲染 边缘云

三个线程交替打印ABC100次问题思考

越长大越悲伤

面试 线程 后端 JUC

SAFe Day 2022 中国规模化敏捷大会即将开启

爱吃鱼的小雨

敏捷 safe 规模化敏捷 大规模敏捷 SAFeDay

文件管理-Linux系统文件属性

Albert Edison

Linux centos 运维 文件管理 8月月更

6.18秒杀系统架构设计

joak

MySQL数据库之数据查询

乌龟哥哥

8月月更

坚叔:让科幻片的概念变成产品丨编程挑战赛 x 嘉宾分享

声网

人工智能 编程‘

RocketMQ高可用设计之同步刷盘

急需上岸的小谢

8月月更

开源一夏 | 阿里云ECS之Linux 文件管理命令

六月的雨在InfoQ

Linux 开源 8月月更 文件管理命令 磁盘命令

详解CAN总线:常用CAN连接器的使用方法

不脱发的程序猿

汽车电子 嵌入式开发 CAN连接器

在线诺基亚短信图片生成器工具

入门小站

工具

《Dubbo3.0.8源码解析》15-Dubbo的三大中心之元数据中心源码解析

宋小生

dubbo Dubbo3

智能化运维场景分析

阿泽🧸

智能运维 8月月更

SpringMvc如何同时支持Jsp和Json接口?

xiaoxi666

Dockerfile安全最佳实践_文化 & 方法_Gianluca Brindisi_InfoQ精选文章