25

I'm trying to execute an SSH command from inside a Docker container in a Jenkins pipeline. I'm using the CloudBees Docker Pipeline Plugin to spin up the container and execute commands, and the SSH Agent Plugin to manage my SSH keys. Here's a basic version of my Jenkinsfile:

node {
  step([$class: 'WsCleanup'])
  docker.image('node').inside {
    stage('SSH') {
      sshagent (credentials: [ 'MY_KEY_UUID' ]) {
        sh "ssh -vvv -o StrictHostKeyChecking=no ubuntu@example.org uname -a"
      }
    }
  }
}

When the SSH command runs, I get this error:

+ ssh -vvv -o StrictHostKeyChecking=no ubuntu@example.org uname -a
No user exists for uid 1005
Nathan Thompson
  • 2,354
  • 1
  • 23
  • 28

5 Answers5

31

I combed through the logs and realized the Docker Pipeline Plugin is automatically telling the container to run with the same user that is logged in on the host by passing a UID as a command line argument:

$ docker run -t -d -u 1005:1005 [...]

I decided to check what users existed in the host and the container by running cat /etc/passwd in each environment. Sure enough, the list of users was different in each. 1005 was the jenkins user on the host machine, but that UID didn't exist in the container. To solve the issue, I mounted /etc/passwd from the host to the container when spinning it up:

node {
  step([$class: 'WsCleanup'])
  docker.image('node').inside('-v /etc/passwd:/etc/passwd') {
    stage('SSH') {
      sshagent (credentials: [ 'MY_KEY_UUID' ]) {
        sh "ssh -vvv -o StrictHostKeyChecking=no ubuntu@example.org uname -a"
      }
    }
  }
}
Nathan Thompson
  • 2,354
  • 1
  • 23
  • 28
  • 3
    You are my hero. Finally I could make a git push from a pipeline. Thanks a lot. Probably it would be a good idea to comment your solution in relevant jenkins bugs. – Gábor Lipták Aug 14 '18 at 14:19
  • Maybe, but I imagine that replacing the password file in the container could cause some real permissions or ownership issues. I'm not opposed to the solution, just reticent of it. – ingyhere Oct 21 '20 at 06:10
  • I would also suggest you to mount `/etc/passwd` as `ro` (read-only) if you need to mount host's `/etc/passwd` into the Docker container. – yaobin May 20 '21 at 06:42
  • 1
    I guess exposing a more or less sensitive host file like `/etc/passwd` to the container is some kind of bad practice. I guess setting `args '-u user:user'` to a `user` which exists in that image is the better alternative. Even if it's `root`. – asbachb Aug 14 '21 at 03:45
9

The solution provided by @nathan-thompson is awesome, but in my case I was unable to find the user even in the /etc/passwd of the host machine! It means mounting the passwd file did not fix the problem. This question https://superuser.com/questions/580148/users-not-found-in-etc-passwd suggested some users are logged in the host using an identity provider like LDAP.

The solution was finding a way to add the proper line to the passwd file on the container. Calling getent passwd $USER on the host will provide the passwd line for the Jenkins user running the container.

I added a step running on the node (and not the docker agent) to get the line and save it in a file. Then in the next step I mounted the generated passwd to the container:

stages {
    stage('Create passwd') {
        steps {
            sh """echo \$(getent passwd \$USER) > /tmp/tmp_passwd
            """
        }
    }
    stage('Test') {
        agent {
            docker {
                image '*******'
                args '***** -v /tmp/tmp_passwd:/etc/passwd'
                reuseNode true
                registryUrl '*****'
                registryCredentialsId '*****'
            }
        }
        steps {
            sh """ssh -i ********
            """
        }
    }
}
hpaknia
  • 2,769
  • 4
  • 34
  • 63
8

I just found another solution to this problem, that I want to share. It differentiates from the existing solutions in that it allows to run the complete pipeline in one agent, instead of per stage.

The trick is to, instead of directly using an image, refer to a Dockerfile (which may be build FROM the original) and then add the user:

# Dockerfile
FROM node

ARG jenkinsUserId=
RUN if ! id $jenkinsUserId; then \
    usermod -u ${jenkinsUserId} jenkins; \
    groupmod -g ${nodeId} jenkins; \
  fi
// Jenkinsfile
pipeline {
  agent {
    dockerfile {
      additionalBuildArgs "--build-arg jenkinsUserId=\$(id -u jenkins)"
    }
  }
}
Florian Bachmann
  • 532
  • 6
  • 14
0
    agent {
        docker {
            image 'node:14.10.1-buster-slim'
            args '-u root:root'
        }

    }

    environment {
        SSH_deploy = credentials('e99988ea-6bdc-45fc-b9e1-536b875bcac7')
    }

stage('build') {
            steps {
                sh '''#!/bin/bash
                    eval $(ssh-agent -s)
                    cat $SSH_deploy | tr -d '\r' | ssh-add -
                    touch .env
                    echo 'REACT_APP_BASE_API = "//172.22.132.115:8080"' >> .env
                    echo 'REACT_APP_ADMIN_PANEL_URL = "//172.22.132.115"' >> .env
                    yarn install
                    CI=false npm run build
                    ssh -t -o StrictHostKeyChecking=no root@172.22.132.115 'rm -rf /usr/local/src/build'
                    scp -r -o StrictHostKeyChecking=no build root@172.22.132.115:/usr/local/src/
                    ssh -t -o StrictHostKeyChecking=no root@172.22.132.115 'systemctl restart nginx'
                 '''
            }

  • Sadly this solution has no further explanation. The important part is `args '-u root:root'` as it overrides the jenkins default to set to jenkins uid. – asbachb Aug 14 '21 at 03:31
0

From the solution provided by Nathan Thompson, I modified it this way for Jenkins DOCKER build container which runs inside a Jenkins DOCKER-slave. #docker in docker

if (validated_parameters.custom_gradle_image){
                docker.image(validated_parameters.custom_gradle_image).inside(" -v /etc/passwd:/etc/passwd -v /var/lib/jenkins/.ssh/:/var/lib/jenkins/.ssh/ "){
                 sshagent(['jenkins-git-io']){
                    sh "${gradleCommand}"
                 }

Ranesh
  • 1
  • 1