英文:
DockerFile : RUN Pass arguments
问题
我们需要更改位于我们的 Docker 镜像内的密钥库(Keystore)的 JKS 密码。
在 DockerFile 中,以下命令是有效的:
RUN [\
"/usr/lib/jvm/java-11-openjdk-amd64/bin/keytool",\
"-storepasswd",\
"-storepass",\
"changeit",\
"-new",\
"NEW_JKS_PASSWORD",\
"-cacerts"\
]
然而,修改后的密码(第7行)仍然是一个硬编码的字符串。
我们希望能够从环境变量中读取它并在命令中进行设置。
我应该如何使用环境变量来实现相同的功能?
英文:
We need to change the JKS password for the Keystore inside our docker image.
The following command in DockerFile works
1. RUN [\
2. "/usr/lib/jvm/java-11-openjdk-amd64/bin/keytool",\
3. "-storepasswd",\
4. "-storepass",\
5. "changeit",\
6. "-new",\
7. "NEW_JKS_PASSWORD",\
8. "-cacerts"\
9. ]
However, the changed password (line number 7) is still a hard-coded string.
We would like to read it from the environment and set in the command.
How can I use environment variables to achieve the same?
答案1
得分: 2
以下是您要翻译的内容:
将 Dockerfile 视为应用程序的 Java 源代码,构建后的镜像类似于一个 jar 文件。在 RUN
命令中执行的任何操作都将在运行容器时固定,就像 jar 文件一样,如果您知道如何熟练使用主机工具,从镜像中提取内容(在这种情况下是相应的秘密)会非常容易。
在这里最简单的做法是删除这个 RUN
指令。取而代之,在主机系统上运行 keytool
命令,然后在运行容器时使用 docker run -v
绑定挂载选项将文件注入容器。
# 从镜像中提取现有的 cacerts 文件
docker run --rm yourimage \
cat /usr/lib/jvm/java-11-openjdk-amd64/lib/security/cacerts \
> cacerts
# 更改其密码
keytool \
-keystore cacerts \
-storepass changeit \
-storepasswd -new NEW_JKS_PASSWD
# 运行容器,用本地的文件替换 cacerts 文件
docker run \
-v $PWD/cacerts:/usr/lib/jvm/java-11-openjdk-amd64/lib/security/cacerts \
...
yourimage
如果您真的想在容器内执行此操作,您需要使用一个入口点脚本来更改密码。使用 ENTRYPOINT
和 CMD
结合的典型模式是将 ENTRYPOINT
设为一个包装脚本,该脚本进行一些必要的首次设置,然后运行 shell 的 exec "$@"
命令以作为主要容器进程运行 CMD
(其余的命令行参数)。
在这种情况下,请记住,任何具有 Docker 访问权限的人都可以使用 docker inspect
命令查找容器的环境变量和其他启动选项,而任何具有系统 root 访问权限的人都可以找到任何进程的环境变量,因此以这种方式传递密码存在相当大的安全风险。
入口点脚本可能如下所示:
#!/bin/sh
# 如果提供了密码,则设置密码
if [ -n "$NEW_JKS_PASSWORD" ]; then
/usr/lib/jvm/java-11-openjdk-amd64/bin/keytool \
-storepasswd \
-storepass changeit \
-new "$NEW_JKS_PASSWORD" \
-cacerts
# 防止密码泄漏到主进程中
# (`docker inspect` 等仍将显示它)
unset NEW_JKS_PASSWORD
fi
# 运行主要容器进程
exec "$@"
在您的 Dockerfile 中,您可以将此脚本设为入口点,将运行应用程序的任何 java
命令设为命令:
FROM openjdk:11
COPY target/app.jar entrypoint.sh /
# 必须使用 JSON 数组形式
ENTRYPOINT ["/entrypoint.sh"]
# 可以是 shell 形式或 JSON 数组形式
CMD ["java", "-jar", "/app.jar"]
英文:
Think of the Dockerfile like the Java source code to your application, and the built image like a jar file. Anything you do in a RUN
command will be fixed when you run container, and like a jar file, if you know how to use the host tools well it is very easy to extract the contents of the image (and in this case the corresponding secret).
The easiest thing to do here is to remove this RUN
directive. Instead, run the keytool
command on the host system, and then use a docker run -v
bind mount option to inject the file into the container when you run it.
<!-- language: lang-sh -->
# Get the existing cacerts file out of the image
docker run --rm yourimage \
cat /usr/lib/jvm/java-11-openjdk-amd64/lib/security/cacerts \
> cacerts
# Change its password
keytool \
-keystore cacerts \
-storepass changeit \
-storepasswd -new NEW_JKS_PASSWD
# Run the container, replacing the cacerts file with the local one
docker run \
-v $PWD/cacerts:/usr/lib/jvm/java-11-openjdk-amd64/lib/security/cacerts \
...
yourimage
If you really want to do this in the container, you need to use an entrypoint script to change the password. A typical pattern for using ENTRYPOINT
and CMD
together is to have the ENTRYPOINT
be a wrapper script that does some required first-time setup, then runs the shell exec "$@"
command to run the CMD
(the rest of the command-line arguments) as the main container process.
In this context, remember that anyone who has Docker access can docker inspect
the container to find the environment variables and other options it was started with, and anyone who has root access to the system can find the environment variables of any process, so there is a not insignificant security risk of passing a password this way.
The entrypoint script would look something like:
<!-- language: lang-sh -->
#!/bin/sh
# Set the password if it's provided
if [ -n "$NEW_JKS_PASSWORD" ]; then
/usr/lib/jvm/java-11-openjdk-amd64/bin/keytool \
-storepasswd \
-storepass changeit \
-new "$NEW_JKS_PASSWORD" \
-cacerts
# Prevent the password from leaking into the main process
# (`docker inspect` _etc._ will still show it)
unset NEW_JKS_PASSWORD
fi
# Run the main container process
exec "$@"
In your Dockerfile, you'd make this script be the entrypoint, and whatever java
command runs the application be the command:
FROM openjdk:11
COPY target/app.jar entrypoint.sh /
# MUST be JSON-array form
ENTRYPOINT ["/entrypoint.sh"]
# Can be either shell or JSON-array form
CMD ["java", "-jar", "/app.jar"]
答案2
得分: 1
你可以使用 ARG
来配合 RUN
命令的 shell 形式:
ARG jks_password
RUN "/usr/lib/jvm/java-11-openjdk-amd64/bin/keytool -storepasswd -storepass …"
然后,在构建时使用 docker build --build-arg jks_password=XYZ .
但是,这种方法有一个注意事项,请参阅 Docker 文档中的警告:
警告:
不建议使用构建时变量传递诸如 GitHub 密钥、用户凭据等敏感信息。构建时变量的值对于使用 docker history 命令查看镜像的任何用户都是可见的。
请参阅“使用 BuildKit 构建镜像”部分,了解在构建镜像时安全使用密钥的方法。
相反,应该使用 --secret
标志:
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret
以及相应的 Docker 构建参数:--secret id=mysecret,src=mysecret.txt .
英文:
You can use ARG
and then the shell form of the RUN
command:
ARG jks_password
RUN "/usr/lib/jvm/java-11-openjdk-amd64/bin/keytool -storepasswd -storepass …"
then, when building use docker build --build-arg jks_password=XYZ .
BUT this comes with a caveat, see the warning in the Docker docs:
> Warning:
> It is not recommended to use build-time variables for passing secrets like github keys, user credentials etc. Build-time variable values are visible to any user of the image with the docker history command.
> Refer to the “build images with BuildKit” section to learn about secure ways to use secrets when building images.
Instead, the --secret
flag should be used:
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret
And the corresponding docker build arguments: --secret id=mysecret,src=mysecret.txt .
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论