Dockerfile 安全性最佳实践

Posted king哥Java架构

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Dockerfile 安全性最佳实践相关的知识,希望对你有一定的参考价值。

容器安全是一个广阔的问题空间,这里有许多轻松实现的方法来减轻风险。一个很好的开端就是在编写 Dockerfile 时遵循一些规则。

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

密钥分散存在是一个毛骨悚然的问题,而且很容易出错。对于容器化应用,可以通过安装卷从文件系统,也可以通过环境变量更方便地显示它们。

使用 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])
} 

只使用可信的基础镜像

容器化应用的供应链攻击也会来自与构建容器本身的层次结构。

罪魁祸首显然是所使用的基础镜像。不可信的基础镜像具有很高的风险,应尽可能避免使用。

如果你觉得自己学习效率低,缺乏正确的指导,可以加入资源丰富,学习氛围浓厚的技术圈一起学习交流吧!
[Java架构群]
群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的JAVA交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

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 标签

固定基础镜像的版本,可以让你在构建容器的可预测性方面放心。

如果你使用最新镜像,则可能默默集成更新的程序包,最坏的情况下可能会影响应用程序的可靠性,而且可能会引入漏洞。

如何检测:

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,它将在构建时获取内容:

ADD https://cloudberry.engineering/absolutely-trust-me.tar.gz 

讽刺的是,官方文档建议改为使用 curl 脚本替代。

从安全的角度来看,这里给出同样的建议:不使用(ADD)。无论想要获取什么内容,要先进行验证,然后使用 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

即使您以用户身份运行,也请确保该用户不在 sudo 用户列表中。

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])
} 

最后

给大家分享一篇一线开发大牛整理的java高并发核心编程神仙文档,里面主要包含的知识点有:多线程、线程池、内置锁、JMM、CAS、JUC、高并发设计模式、Java异步回调、CompletableFuture类等。

文档地址:一篇神文就把java多线程,锁,JMM,JUC和高并发设计模式讲明白了

码字不易,如果觉得本篇文章对你有用的话,请给我一键三连!关注作者,后续会有更多的干货分享,请持续关注!

以上是关于Dockerfile 安全性最佳实践的主要内容,如果未能解决你的问题,请参考以下文章

Dockerfile 最佳实践

Dockerfile 最佳实践

Dockerfile 最佳实践

容器安全之 Dockerfile 安全扫描

Dockerfile 的最佳实践 | Dockerfile 你写的都对么?

Dockerfile最佳实践