Problems with Jenkins/Docker connecting to an Ansible agent using SSH Pipeline Steps and transferring withCredentials private ssh key

Hello,

I’m relatively new to Jenkins, experimenting with operational CI/CD services for my company and have been happy with it thus far. I’d started with a single custom monolithic container that had jenkins and a bunch of other stuff installed for relative ease of use. Ran into some issues and I’ve recently shifted into a multi-container setup, using the Jenkins official container image from Docker hub with separate additional containers to act as dedicated agents for interactions with Ansible, AWS, etc.

I’m trying to make use of the SSH Pipeline Steps plugin to give myself some added granularity with my interactions between Jenkins and remote agents. I’ve got things working up to a point. When interacting with the Ansible agent container, I’m using the Jenkins Credentials vault to host both the ssh creds used by Jenkins to login to the Ansible remote agent container itself (username + ssh key) as well as the ssh service account creds (username and ssh key) used by Ansible (installed on the remote node) to login and interact with all of it’s assigned inventory.

I’m having a devil of a time getting the ansible service account user private key, stored in the Jenkins Credential vault, to be recognized and processed by the ansible-playbook binary on the remote Ansible agent container. Jenkins playbook code snippet as follows:

-----
  ANSIBLE_INVENTORY = "ansible/inventory/non-prod/hosts"

    echo "Running on non-production server. Using non-production inventory: ${ANSIBLE_INVENTORY}"

    INVENTORY_FILE = ANSIBLE_INVENTORY.split('/')[-1]

    echo "Inventory file: ${INVENTORY_FILE}"

    PLAYBOOK_FILE = ANSIBLE_PLAYBOOK.split('/')[-1]

    echo "Playbook file: ${PLAYBOOK_FILE}"

    withCredentials([sshUserPrivateKey(credentialsId: 'secopsdev-ansible-agent', keyFileVariable: 'ANS_AGENT_KEY', usernameVariable: 'ANS_AGENT_USER'),

    sshUserPrivateKey(credentialsId: 'secopsdev-ansible-svcaccount', keyFileVariable: 'ANS_SVC_KEY', usernameVariable: 'ANS_SVC_USER')]) {

        def remote = [:]

        remote.name = 'ansible'

        remote.host = AGENT_HOST

        remote.user = ANS_AGENT_USER

        remote.identityFile = ANS_AGENT_KEY

        remote.knownHosts = "${HOME}/.ssh/known_hosts"

        // Define a work directory path using Jenkins environment variables

        def workDir = "work/${JOB_NAME}/${BUILD_NUMBER}"

        // Use sshCommand to create the work directory on the remote node

        sshCommand remote: remote, command: "mkdir -p \"${workDir}\""

        // copy the playbook and inventory file to the working directory

        sshPut remote: remote, from: "${ANSIBLE_PLAYBOOK}", into: "${workDir}"

        sshPut remote: remote, from: "${ANSIBLE_INVENTORY}", into: "${workDir}"

        // show that the uploaded files are present in the workDir

        sshCommand remote: remote, command: "ls -al '${workDir}'"

        // Switch into the workDir and execute the playbook using the inventory file, playbook file and associated creds

        sshCommand remote: remote, command: """

            cd "${workDir}"

            ansible-playbook -i ./${INVENTORY_FILE} ./${PLAYBOOK_FILE} -u ${ANS_SVC_USER} --private-key ${ANS_SVC_KEY}

            """

    }
-----

From what I’ve been able to read up on thus far, the private key is supposed to be “auto-transferred” to the remote agent host automatically to a temp file and that the path to that temp file is then stored in the associated variable, in $(ANS_SVC_KEY) as referenced above. Try as I might though, I can’t seem to actually see/find that temporary key as having actually been transferred during the execution. I can see the tmp locale on the jenkins controller server and I can actually dive down into the workspace tmp path on the controller to actually see the private ssh key files in question.

Part of me is just thinking of bypassing this and just performing a fixed transfer of the private key separately and ahead of time into the service user’s ~/.ssh dir, but I’d like to see if I can get withCredentials working if possible to dynamically process the key at runtime and not leave it on the agent permanently.

From what I can tell, the transfer seems to attempt to use the pipeline job name as part of the tmp path to the creation of the temporary file. Not sure if this helps, but my pipeline jobs names tend to have spaces in them. Not sure if that’s relevant.

Any suggestions/recommendations on how I can get this working?

jenkins config:

Jenkins: 2.557
OS: Linux - 6.8.0-106-generic
Java: 21.0.10 - Eclipse Adoptium (OpenJDK 64-Bit Server VM)
---
ant:520.vd082ecfb_16a_9
antisamy-markup-formatter:173.v680e3a_b_69ff3
apache-httpcomponents-client-4-api:4.5.14-269.vfa_2321039a_83
asm-api:9.9.1-189.vb_5ef2964da_91
bootstrap5-api:5.3.8-895.v4d0d8e47fea_d
bouncycastle-api:2.30.1.83-289.v8426fcd19371
branch-api:2.1280.v0d4e5b_b_460ef
build-timeout:1.40
caffeine-api:3.2.3-194.v31a_b_f7a_b_5a_81
checks-api:402.vca_263b_f200e3
cloudbees-folder:6.1079.vc0975c2de294
commons-lang3-api:3.20.0-109.ve43756e2d2b_4
commons-text-api:1.15.0-218.va_61573470393
credentials:1499.va_f0e811253a_6
credentials-binding:719.v80e905ef14eb_
dark-theme:652.vea_da_dfea_e769
display-url-api:2.217.va_6b_de84cc74b_
durable-task:664.v2b_e7a_dfff66c
echarts-api:6.0.0-1247.vf3e35a_c1813f
eddsa-api:0.3.0.1-29.v67e9a_1c969b_b_
email-ext:1933.v45cec755423f
font-awesome-api:7.2.0-965.ve3840b_696418
git:5.10.1
git-client:6.6.0
github:1.46.0
github-api:1.330-492.v3941a_032db_2a_
github-branch-source:1967.vdea_d580c1a_b_a_
gradle:2.19.1244.v1f9866817fec
gson-api:2.13.2-173.va_a_092315913c
instance-identity:203.v15e81a_1b_7a_38
ionicons-api:94.vcc3065403257
jackson2-api:2.21.2-433.v6d50b_92cfe52
jackson3-api:3.1.1-68.v2a_4b_025ea_657
jakarta-activation-api:2.1.4-1
jakarta-mail-api:2.1.5-1
jakarta-xml-bind-api:4.0.6-12.vb_1833c1231d3
javax-activation-api:1.2.0-8
jaxb:2.3.9-143.v5979df3304e6
jjwt-api:0.11.5-120.v0268cf544b_89
job-import-plugin:143.v044a_2e819b_27
joda-time-api:2.14.1-187.vdf2def02b_8a_1
jquery3-api:3.7.1-619.vdb_10e002501a_
jsch:0.2.16-95.v3eecb_55fa_b_78
json-api:20251224-185.v0cc18490c62c
json-path-api:3.0.0-218.vcd4dd1355de2
jsoup:1.22.1-76.v9cdb_2456c0e3
junit:1403.vd9d1413fd205
ldap:807.v7d7de30930cf
mailer:534.v1b_36f5864073
matrix-auth:3.2.9
matrix-project:870.v9db_fcfc2f45b_
metrics:4.2.37-494.v06f9a_939d33a_
mina-sshd-api-common:2.16.0-167.va_269f38cc024
mina-sshd-api-core:2.16.0-167.va_269f38cc024
oic-auth:4.668.v653c6b_c6cb_f5
okhttp-api:5.3.2-200.vedb_720a_cf1f8
pipeline-build-step:584.vdb_a_2cc3a_d07a_
pipeline-github-lib:65.v203688e7727e
pipeline-graph-view:819.ved496c19e082
pipeline-groovy-lib:787.ve2fef0efdca_6
pipeline-input-step:540.v14b_100d754dd
pipeline-milestone-step:138.v78ca_76831a_43
pipeline-model-api:2.2277.v00573e73ddf1
pipeline-model-definition:2.2277.v00573e73ddf1
pipeline-model-extensions:2.2277.v00573e73ddf1
pipeline-stage-step:322.vecffa_99f371c
pipeline-stage-tags-metadata:2.2277.v00573e73ddf1
plain-credentials:199.v9f8e1f741799
plugin-util-api:6.1192.v30fe6e2837ff
prism-api:1.30.0-703.v116fb_3b_5b_b_a_a_
resource-disposer:0.25
scm-api:728.vc30dcf7a_0df5
script-security:1399.ve6a_66547f6e1
snakeyaml-api:2.5-149.v72471e9c6371
snakeyaml-engine-api:3.0.1-5.vd98ea_ff3b_92e
ssh-credentials:361.vb_f6760818e8c
ssh-slaves:3.1097.v868116049892
ssh-steps:2.0.92.vb_a_0583935f9b_
structs:362.va_b_695ef4fdf9
theme-manager:344.vd7b_e20e046dc
timestamper:1.30
token-macro:477.vd4f0dc3cb_cf1
trilead-api:2.284.v1974ea_324382
variant:70.va_d9f17f859e0
woodstox-core-api:7.1.1-1.v4d297985f397
workflow-aggregator:608.v67378e9d3db_1
workflow-api:1398.v67030756d3fb_
workflow-basic-steps:1098.v808b_fd7f8cf4
workflow-cps:4275.vb_0565eb_a_3d36
workflow-durable-task-step:1464.v2d3f5c68f84c
workflow-job:1573.v1465f6f78810
workflow-multibranch:821.vc3b_4ea_780798
workflow-scm-step:466.va_d69e602552b_
workflow-step-api:710.v3e456cc85233
workflow-support:1015.v785e5a_b_b_8b_22
ws-cleanup:0.49

The withCredentials is executed on the agent where the pipeline is running (I assume you run the pipeline on the controller directly as you say you see the file there). There is stores the ssh key in a temp folder and sets env variables with the user, keyfile and keypass. From your snippet I assume that this is not the machine used in the ssh commands (remote.host = AGENT_HOST used by sshPut and sshCommand).
So you can neither assume that the env variables are set on the machine (though you use double quotes which means that the interpolation happens before when groovy evaluates the script and is not done by the shell when the sshCommand is executed remotely) nor can you assume that the files are available or accessible.
You could attach the ansible container directly as an agent in Jenkins, then you wouldn’t need to do ssh calls at all or you need to transfer the ssh key file to the ansible machine before executing the ansible commands and take care to cleanup once the ansible commands finished in a safe way so that is also handles the case then the step fails.

When you connect the ansible container as an agent in Jenkins you can then probably also use the Ansible plugin