I have created a windows container that includes a Git client and I am able to clone and checkout from our Bitbucket server using HTTP. This means I need to provide username and password for every transaction with Git. I am now wanting to rather use SSH. So I have generated an SSH key using ssh-keygen and have added the public key to my user account in Bitbucket and on my local PC I am able to transact with Bitbucket successfully as I loaded my key using Pageant.
Within the container I am struggling to get this to work.
I found some info here which I included in my Dockerfile - specifically the commands Set-Service, Start-Service and Get-Service.
This works up to a point, this line RUN printf "${SSH_KEY_PASSPHRASE}" | ssh-add C:\Users\Jenkins\.ssh\id_rsa
fails to build - it doesn't recognize printf
. I have tried to replace it with echo
but also fails.
If I remove this line from my Dockerfile I can build and run this container; I then tried go into it with a powershell session and tried to run ssh-add C:\Users\Jenkins\.ssh\id_rsa
manually but I get response Could not open a connection to your authentication agent.
If I run Get-Service ssh-agent
it reports that ssh-agent is running.
So my questions are as follows:
- Why is the
ssh-add
command not working? - How to I add my ssh key within this container?
- When I get the
ssh-add
command to work, how do I specify the passphrase in the Dockerfile?
Here is my Dockerfile:
# escape=`
FROM mcr.microsoft.com/windows/servercore
ARG SSH_KEY_PASSPHRASE
COPY .\files\Git-2.18.0-64-bit.exe C:\installers\git-installer.exe
COPY .\files\Git-2.18.0-64-bit.inf C:\installers\git-installer-script.inf
RUN powershell -Command `
# Install Git ; `
Start-Process -filepath C:\installers\git-installer.exe -passthru -wait -argumentlist "/LOADINF=C:\installers\git-installer-script.inf","/VERYSILENT","/NORESTART" ; `
Remove-Item C:\installers\ -Force -Recurse;
RUN powershell -Command `
net user /add Jenkins; `
net localgroup Administrators Jenkins /add;
USER Jenkins
RUN powershell -Command `
mkdir ~\.ssh;
COPY .\id_rsa C:\Users\Jenkins\.ssh\id_rsa
RUN powershell -Command `
# Make sure you're running as an Administrator `
Set-Service ssh-agent -StartupType Automatic; `
Start-Service ssh-agent; `
Get-Service ssh-agent;
# This doesn't work and I don't know how to do this
RUN printf "${SSH_KEY_PASSPHRASE}" | ssh-add C:\Users\Jenkins\.ssh\id_rsa
Edit 1
I have now discovered that if run this container (without the ssh-add
command in the dockerfile) and then run a git clone using the ssh URL, it states that the authenticity of the bitbucket host can't be established, do you want to continue connecting? I enter "yes" and it then "permanently adds my bit bucket host to the list of known hosts" before asking for the passphrase for key 'c/Users/Jenkins/.ssh/id_rsa'.
Once I enter the passphrase it successfully clones the repo. This result surprises me and results in a new question:
- How does it know about the ssh key in c/Users/Jenkins/.ssh if I haven't added it yet? I'm clearly missing some understanding somewhere.
Edit 2
I have played around with my Dockerfile and found that the line
RUN powershell -Command mkdir ~\.ssh
seems to be important in some way.
If I remove this line and run the container, I can see that my key is present in C:\Users\Jenkins\.ssh\
but if I run ssh-add .\Users\Jenkins\.ssh\id_rsa
it fails with Permissions for id_rsa are too open
.
If I include the mkdir
line there is no obvious difference to me but the ssh-add
command seems to work and reports Identity added: .\Users\Jenkins\.ssh\id_rsa (<my company domain>+<my windows username@PC name>)
.
If I don't manually create the .ssh folder it still seems to be implicitly created during the COPY
command but the ssh-add
fails. What is going on here?
Next I included the ssh-add
command into my Dockerfile and rebuilt it and could see that the key was added - it reports Identity added: .\Users\Jenkins\.ssh\id_rsa
.
At this stage I am not able to interact with Git as the path was not setup, so I modified my Dockerfile to setup the path by adding the following lines to the command where I install Git;
${env:PATH} = 'C:\Git\bin;C:\Git\usr\bin;C:\Git\mingw64\bin;' + $env:PATH;
[Environment]::SetEnvironmentVariable('PATH', ${env:PATH}, [EnvironmentVariableTarget]::Machine);
Now when I build the image the ssh-add command fails with "Could not open a connection to your authentication agent."
I ran Get-Service ssh-agent
which reports that it is running.
So somehow the inclusion of Git into the path has resulted in a difference regarding the ssh-add
command.
What is happening here?
How do I resolve this?
I also recreated the key without a passphrase so have removed the need to enter a passphrase during the docker build and I specified the version of windows.
Here is my updated Dockerfile:
# escape=`
FROM mcr.microsoft.com/windows/servercore:1903
# Install Git
COPY .\files\Git-2.18.0-64-bit.exe C:\installers\git-installer.exe
COPY .\files\Git-2.18.0-64-bit.inf C:\installers\git-installer-script.inf
RUN powershell -Command `
Start-Process -filepath C:\installers\git-installer.exe -passthru -wait -argumentlist "/LOADINF=C:\installers\git-installer-script.inf","/VERYSILENT","/NORESTART" ; `
Remove-Item C:\installers\ -Force -Recurse; `
# Set the path to include Git folders - not listed in my original post `
${env:PATH} = 'C:\Git\bin;C:\Git\usr\bin;C:\Git\mingw64\bin;' + $env:PATH; `
[Environment]::SetEnvironmentVariable('PATH', ${env:PATH}, [EnvironmentVariableTarget]::Machine);
# Create a Jenkins user with admin permissions
RUN powershell -Command `
net user /add Jenkins; `
net localgroup Administrators Jenkins /add;
USER Jenkins
# The presence or absence of this mkdir command affects ssh-add for some reason
RUN powershell -Command `
mkdir ~\.ssh;
# This key was created without a passphrase
COPY .\id_rsa C:\Users\Jenkins\.ssh\id_rsa
RUN powershell -Command `
# Make sure you're running as an Administrator `
Set-Service ssh-agent -StartupType Automatic; `
Start-Service ssh-agent; `
Get-Service ssh-agent; `
ssh-add .\Users\Jenkins\.ssh\id_rsa;
Edit 3
Thanks to information I found here I it seems that the inability to run ssh-add
successfully was due to the SSH_AUTH_SOCK
environment variable not being set.
Running ssh-agent
provides the data needed to set this environment variable and in Linux you could run eval $(ssh-agent)
but powershell in windows doesn't recognize the eval
command. At this stage I tried commenting out the ssh-add
command from my last dockerfile and then inside the container I manually ran the ssh-agent
command and set the environment variable using $Env:SSH_AUTH_SOCK = "<value returned from ssh-agent>"
which succeeded and if I run Get-ChildItem Env:
I see this new environment variable listed.
I then tried to run a git clone ssh://git@mybitbucketurl/myrepo.git
which reported that "the authenticity of host mybitbucketurl (xx.yy.zz.aa:port) can't be established. Are you sure you want to continue (yes/no)?"
I entered "yes" and it "Permanently added mybitbucketurl:port,[xx.yy.zz.aa]:port (RSA) to the list of known hosts" and then cloned my repository.
I thought I had this cracked now and thought I just needed to work out how to do the eval
command equivalent in powershell. While I was searching for clues on how to do this I found this link with an answer that listed this command:
ssh-agent ssh-add C:\Users\<username>\.ssh\id_rsa
.
I tried running this manually inside my container and it reported that the identity was added, however when I tried to run a git clone
it first add the URL to the list of known hosts and then failed with "fatal: could not read from remote repository".
Running Get-ChildItem Env:
showed that the SSH_AUTH_SOCK
environment variable is not set so I guess that running it like this is an isolated session of sorts although I don't really know what is happening with this command.
So from here it looks like I need to work out how to run the ssh-agent
command and use its result in powershell to setup the environment variable permanently.
Once I have got that working I need to work out how to add my Git host URL to the list of known hosts.
Edit 4
The crux of my issue seems to be the difference between the build-time and run-time of my docker image and specifically of what I am hoping to achieve with ssh-agent
and ssh-add
.
If I setup my image to include the ssh key and then run it, I am able to manually do the following (note that I've switched from powershell to bash):
$ echo $SSH_AUTH_SOCK
$ eval $(ssh-agent)
Agent pid 2036
$ sh-add Users/Jenkins/.ssh/id_rsa
Identity added: Users/Jenkins/.ssh/id_rsa
$ echo $SSH_AUTH_SOCK
/tmp/ssh-qCM264wFqpqq/agent.1748
So from this I can see that at run-time in the container it does what I want it to.
However if I try and run these commands at build-time from the Dockerfile things do not work as I would hope.
After some thought I suspect that the eval $(ssh-agent)
command probably runs but the SSH_AUTH_SOCK
environment variable is not retained within the image's layer and when the container is run ssh-agent will not be running, something probably needs to launch it.
What is the right way to do this:
- Do I try to get these commands to run during startup of the container via a startup script?
- Is there a way to still do this at build time (i.e. from the Dockerfile) - to achieve this I would manually need to set the
SSH_AUTH_SOCK
environment variable but this seems to be different every time you runssh-agent
- is this worth pursuing?