Docker: How to prevent exposing proxy credential when building container images with dockerfile

Recently, I started using dockerfile to create container images that will trigger the shell commands to download packages behind a web proxy server. Obviously, my container image will require proxy credential such as username and password for proxy authentication purposes.

I started of scripting the dockerfile with proxy credential and the magic just happened without any issue until I checked the image history. To be honest, I’m horrified that the plain-text username and password are stored in the image history. And I decided that I need to find a better way to overcome this so that I can share the dockerfile or images without compromising the credential security.

This topic is intended for anyone who is a container image maintainer who builds container images using dockerfile.

Why I do not recommend defining ENV or ARG for credential inputs?

ENV is a quick way of hardcoding the value to be stored as environment variables so that you can use it on every script stage using RUN syntax but ENV will display the variable in plain-text during execution and stored the plain-text in docker history. An example of this scenario below.

DockerFile-Image-History-contain-Username-and-Password-when-using-ENV

ARG is a nice way of allowing maintainer to define their own arguments to allow user passing the value as an argument value during the execution that reads the argument value on every script stage using RUN. This enhanced the user experience by allowing flexibility without making changes to the scripted content in the dockerfile. Although the argument value will not be displayed in plain-text during execution, custom argument defined by the dockerfile’s maintainer will still be constantly passed to every script stage in plain-text stored in docker history. An example of this scenario below.

DockerFile-Image-History-contain-Username-and-Password-when-using-ARG

Since ENV and ARG has their purposes in the dockerfile syntax, I still need to find a solution to pass on that sensitive credential authentication information for the proxy server setting during image building to automatically communicate through proxy and download the latest package online.

After working for awhile resolve/workaround with ARG till I found the Predefined ARGs which excludes variables from the output of docker history by default and decided to do an experiment by passing proxy credential through the HTTP_PROXY predefined argument like this below:

 


docker build -t nanoserver:sac2016-dev --build-arg HTTP_PROXY=http://'myusername@mydomainname.co.nz':mypassword@myproxyserver:8080 -f "NanoServer.dockerfile" .

 

While I had first RUN syntax in the dockerfile that behaves like a preparation stage where it will handles the input from the HTTP_PROXY/HTTPS_PROXY predefined args to filter out the username and password from those predefined args. Store them securely as environmental variables for use with Invoke-WebRequest or Invoke-RestMethod PowerShell cmdlets on every RUN syntax.

 


SHELL ["powershell", "-Command"]

# Validate HTTP_PROXY/HTTPS_PROXY argument
RUN \
if(Test-Path -Path ENV:\HTTP_PROXY) \
{ \
if($ENV:HTTP_PROXY.Split(':').Length -gt 3) \
{ \
New-ItemProperty \
-Path 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment' \
-Name 'PROXY_URI' \
-Value ([String]::Format('{0}://{1}', \
$ENV:HTTP_PROXY.Split(':')[0], \
$( \
if($ENV:HTTP_PROXY.Split('@')[2] -eq $null) \
{ \
$ENV:HTTP_PROXY.Split('@')[1] \
} \
else \
{ \
$ENV:HTTP_PROXY.Split('@')[2] \
} \
) \
)) \
-PropertyType String \
-Verbose | Out-Null ; \
\
New-ItemProperty \
-Path 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment' \
-Name 'PROXY_USERNAME' \
-Value $ENV:HTTP_PROXY.Split(':')[1].Replace('//','') \
-PropertyType String \
-Verbose | Out-Null ; \
\
New-ItemProperty \
-Path 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment' \
-Name 'PROXY_PASSWORD' \
-Value ((ConvertTo-SecureString \
-String $ENV:HTTP_PROXY.Split(':')[2].Split('@')[0] \
-AsPlainText \
-Force) | ConvertFrom-SecureString) \
-PropertyType String \
-Verbose | Out-Null ; \
} \
else \
{ \
New-ItemProperty \
-Path 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment' \
-Name 'PROXY_URI' \
-Value $ENV:HTTP_PROXY \
-PropertyType String \
-Verbose | Out-Null ; \
} \
} \
elseif(Test-Path -Path ENV:\HTTPS_PROXY) \
{ \
if($ENV:HTTPS_PROXY.Split(':').Length -gt 3) \
{ \
New-ItemProperty \
-Path 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment' \
-Name 'PROXY_URI' \
-Value ([String]::Format('{0}://{1}', \
$ENV:HTTPS_PROXY.Split(':')[0], \
$( \
if($ENV:HTTPS_PROXY.Split('@')[2] -eq $null) \
{ \
$ENV:HTTPS_PROXY.Split('@')[1] \
} \
else \
{ \
$ENV:HTTPS_PROXY.Split('@')[2] \
} \
) \
)) \
-PropertyType String \
-Verbose | Out-Null ; \
\
New-ItemProperty \
-Path 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment' \
-Name 'PROXY_USERNAME' \
-Value $ENV:HTTPS_PROXY.Split(':')[1].Replace('//','') \
-PropertyType String \
-Verbose | Out-Null ; \
\
New-ItemProperty \
-Path 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment' \
-Name 'PROXY_PASSWORD' \
-Value ((ConvertTo-SecureString \
-String $ENV:HTTPS_PROXY.Split(':')[2].Split('@')[0] \
-AsPlainText \
-Force) | ConvertFrom-SecureString) \
-PropertyType String \
-Verbose | Out-Null ; \
} \
else \
{ \
New-ItemProperty \
-Path 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment' \
-Name 'PROXY_URI' \
-Value $ENV:HTTPS_PROXY \
-PropertyType String \
-Verbose | Out-Null ; \
} \
} ;

 

As the screenshot below demonstrates how those sensitive authentication information are stored to $ENV:PROXY_URI, $ENV:PROXY_USERNAME and $ENV:PROXY_PASSWORD (as SecureString). You can add any other RUN(s) syntax after this initial RUN syntax to execute SHELL scripts to configure, setup or install using those environmental variables.

DockerFile-HowTo-hide-Username-and-Password-for-proxy-authentication

And finally, add the finalizing RUN syntax below that cleans up those environmental variables before sealing it as a container image.

WARNING: DO NOT EXCLUDE THIS FINAL RUN THAT REMOVE THOSE ENVIRONMENTAL VARIABLES FROM THE OPERATING SYSTEM BEFORE SEALING INTO AN IMAGE


# Remove environment variables from the build image
RUN \
if( \
($ENV:HTTP_PROXY.Split(':').Length -gt 3) \
-or \
($ENV:HTTPS_PROXY.Split(':').Length -gt 3)) \
{ \
Remove-ItemProperty \
-Path 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment' \
-Name PROXY_URI, PROXY_USERNAME, PROXY_PASSWORD \
-Verbose ; \
} \
else \
{ \
Remove-ItemProperty \
-Path 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment' \
-Name PROXY_URI \
-Verbose ; \
}

 

Save that dockerfile and execute it with those sensitive authentication information with the HTTP_PROXY/HTTPS_PROXY predefined args. Use docker history to validate them and see if you can view those information in plain-text.

DockerFile-Image-History-do-not-contain-Username-and-Password-with-HTTP_PROXY

That’s all about preventing sensitive authentication information leaked on your container or image. You will now have no sensitive information display during run-time execution, hard coded in the dockerfile, stored docker history or stored in the operating system.

Reference