Docker容器明文密码问题解决之道
Docker 带着 “Dockerize Everything” 的口号,以“软件标准”的姿态展现于世人面前,不断影响大家对于软件的理解。然而现实是否就如想象中的那么饱满,新的科技诞生之际,是摧枯拉朽之势,还是 循序渐进,皆有个过程,面对异军突起的 Docker,软件传统的精髓又是何去何从?这些无一不是值得深思的话题。
在《存储类 Docker 容器的明文密码问题》一文中,我们初步领略了存储类软件与 Docker 结合时,存在的些许安全隐患,比如明文密码问题。
过去数十年间,MySQL 数据库的创建都在人机交互过程中完成,流程大致可以分为以下三个步骤:
-
运维人员创建机器,安装并配置 MySQL 服务器;
-
DBA 负责管理 MySQL 数据库,如 MySQL 数据库的创建、删除与权限更改等;
-
开发人员使用 DBA 交付的数据库,对数据库进行增删改查等操作。
MySQL 历经了许多年,形成了以上这种交付方式,在 Docker 诞生之前,没有任何风吹草动。然而,在 Docker 诞生之后,MySQL 的 Docker 化路程似乎并不平坦。
常言道,Docker 横空出世,极大地推动了DevOps
的发展。虽然看似 MySQL 与 Docker 的结合并没有对开发造成直接的影响,但是 Docker 化的 MySQL 的确加速了运维进度,原本冗长的人机交互,以及多方协调,如今一条简易的docker run
命令即可全部完成。无可否认,自动化的程度有了质的飞跃,然而当我们审视自动化流程时,我们也可以从中找到一些隐患——MySQL 容器的明文密码问题
。
MySQL 容器的明文密码问题,指的是:Docker 创建 MySQL 容器时,通过环境变量的方式传递 MySQL 存储引擎的密码,纵使 MySQL 会对密码进行加密,然而环境变量的存在却会泄漏密码信息,因此存在安全隐患。
明文密码解决方案
Docker 容器的明文密码问题,在于控制流程的过于自动化,并且环境变量的方式又无可避免地使用明文记录了密码。在一个完整的 MySQL 容器创建过程中,环境变量和 MySQL 引擎密码始终保持一致,假设我们可以做到用户为 MySQL 设定的密码最终可以落实到 MySQL 引擎处,而不存在于任何环境变量中
,那就可以说明明文密码可以解决。换言之,用户为 MySQL 容器设定的密码时,可以绕过环境变量。众所周知,环境变量在 Docker 的世界中是配置环境最常用的方式,连完成容器间通信的docker link
命令最终也是通过环境变量来完成。
绕过环境变量又是从何说起,首先让我们分析下图。
上图中,我们通过 Docker Daemon 创建了两个 MySQL 容器,容器名分别为 MySQL1 和 MySQL2,并且两个容器中的 MySQL 引擎的密码分别为 mysql1 和 mysql2。创建容器时使用的命令分别为:
docker run -d -e MYSQL_ROOT_PASSWORD=daocloud --name MySQL1 mysql docker run -d -e MYSQL_ROOT_PASSWORD=docker --name MySQL2 mysql
假设一位用户希望创建一个密码不会泄漏的 MySQL 容器,密码为 daocloud。为了弥合明文密码问题,绕过环境变量,我们可以按照以下三个步骤来完成。
1. 创建两个 MySQL 容器 MySQL1 与 MySQL2,MySQL 的 root 密码分别为 daocloud 与 docker;
2. 待 MySQL1 启动完毕,使用docker stop
命令停止 MySQL1 容器,并将 MySQL1 容器的 volume1 全部拷贝出来,最终使用docker rm
命令删除 MySQL1 容器;
3. 待 MySQL2 启动完毕,使用docker stop
命令停止 MySQL2 容器,并将 MySQL2 容器 volume2 内的文件全部删除,接着将 volume1 的内容拷贝至 volume2 下,最终启动 MySQL2。
通过以上三个步骤,我们直接交付 MySQL2 容器,此时 MySQL2 容器中 MySQL 的 root 密码为 daocloud,即目标达成。虽然 MySQL2 容器的环境变量 MYSQLROOTPASSWORD 依旧是 docker,但是 MySQL 引擎使用的密文密码已经转变为 daocloud,交付完毕的 MySQL2 容器中不存在任何有关字符串 daocloud 的明文
信息,同时无需再使用的 MySQL1 容器也被我们删除。
上述流程的执行,可以很巧妙的通过替换 volume
的方式,完成密文的转移,同时使得明文环境变量的失效。
Docker 层与应用层
通过实践,可以验证解决方案的可行性。不过很多读者看到这里,不禁会对上述方案产生一些质疑,是否替换 volume 就是一个合理的解决方案。
以下的观点,相信很多人会认为同样是合理的解决方案。
明文密码固然是一个大问题,然而当 MySQL 容器创建完毕之后,用户完全有权限通过 mysql-client 等工具登陆 MySQL 引擎,实现 MySQL 引擎 root 密码的修改,最终的结果是:密码修改同样会作用到 volume 中的密文,使得充当明文密码的 Docker 容器环境变量失效。
不可否认,上述观点同样具有可行性。仔细分析和对比两种解决方案,可以看到两者之间存在一些明显的差异。
最大的差异性,当属通用性。替换 volume
的方式,虽然在容器创建流程中加入了部分额外的操作(比如创建两个容 器、启动容器、替换 volume 等),但是在通用性方面,优势十分明显。通用性的体现何在?本文举例的是 MySQL 容器,其实其他存储类 Docker 容器如 MongoDB、Redis 等,均可以采用这种方式。
换言之,对于存储类 Docker 容器而言,Docker Daemon 的管理员无需获知容器内部运行的是何种服务,机械化操作替换 volume
即 可导致明文密码失效。通过 mysql-client 修改密码的方式,只能由容器的用户来完成,而现实情况中, Docker Daemon 管理员与容器用户很有可能并非同一个人,尤其是在公有云服务上。因此,Docker Daemon 交付出的容器,必须由用户进行二次加工,才能真正满足用户需求,无疑在便捷性方面,无法尽如人意。
更为细致的比较,我们就能发现:其实两者的实现的立足点不同。替换 volume
则是从 Docker 层出发;而修改密码
则是站在应用层出发。
何为 Docker 层?
Docker 是一款软件,Docker 的世界中 Docker 镜像、Docker 容器等,对于容器的管理(比如启动停止、环境变量的设置等),笔者都认为是 Docker 层的概念。
何为应用层?
此处的应用层,指的是与用户镜像内或者容器内与应用直接相关的内容。
依然以 MySQL 为例,通过 MySQL 镜像启动 MySQL 容器时,会使用MYSQL_ROOT_PASSWORD
这个环境变量。环境变量是一个 Docker 层的概念,原因很简单,Docker Daemon 会机械化地将所有用户设置的环境变量作用到容器进程,而不会去关心具体哪个环境变量在容器中充当什么样的角色。同样的道理,名为MYSQL_ROOT_PASSWORD
的环境变量就是一个应用层的概念,这个具体的环境变量,有可能会被容器内部的应用进程来使用,最终影响容器内部的应用。
同样的道理,volume 是一个 Docker 层的概念,volume 内部的具体内容则是应用层的概念。因此,通过 volume 来操作容器,属于 Docker 层的操作,不会涉及任何应用层的内容。而通过事先获知容器内部应用的详细情况,再针对应用进程做出相应的行为,则属于应用层的操作。
总结
存储类 Docker 容器的明文密码问题,实则是:为应用层准备的密码,必须通过 Docker 层的环境变量来传递,而最终 Docker 层的环境变量将一直以明文的形式遗留于多处。通过替换 volume
的方式可以很好地从 Docker 层解决存储类 Docker 容器的明文密码问题。(责编/魏伟)