Jenkins sudo: a terminal is required to read the password
Question:
I have a FastAPI app for which I have configured Jenkins pipeline. When I execute unit tests with the code coverage enabled they are failing with the following error :
Started by user gold
Obtained Jenkinsfile from git https://github.com/edtshuma/devsecops-labs.git
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /var/lib/jenkins/workspace/Python-DevSecOps
....
....
[Pipeline] sh
+ pip install -r requirements.txt
....
Requirement already satisfied: uvicorn==0.20.0 in ./.pyenv-usr-bin-python3.8/lib/python3.8/site-packages (from -r requirements.txt (line 41)) (0.20.0)
Requirement already satisfied: watchfiles==0.18.1 in ./.pyenv-usr-bin-python3.8/lib/python3.8/site-packages (from -r requirements.txt (line 42)) (0.18.1)
Requirement already satisfied: websockets==10.4 in ./.pyenv-usr-bin-python3.8/lib/python3.8/site-packages (from -r requirements.txt (line 43)) (10.4)
+ sudo chown -R jenkins:jenkins ./docs/unit-tests/htmlcoverage
sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper
[Pipeline] }
[Pipeline] // withPythonEnv
Jenkinsfile
pipeline {
agent any
triggers {
githubPush()
}
stages {
stage('Setup'){
steps{
withPythonEnv('/usr/bin/python3.8') {
sh 'echo "Job is starting" '
}
}
}
stage('Unit Tests'){
steps{
withPythonEnv('/usr/bin/python3.8') {
sh '''pip install -r requirements.txt
sudo chown -R jenkins:jenkins ./docs/unit-tests/htmlcoverage
pytest -v --junitxml=docs/unit-tests/htmlcoverage/coverage.xml --cov-report xml --cov app.main
'''
}
}
}
stage('Publish Test Report'){
steps{
cobertura autoUpdateHealth: false, autoUpdateStability: false, coberturaReportFile: 'coverage*.xml', conditionalCoverageTargets: '70, 0, 0', failUnhealthy: false, failUnstable: false, lineCoverageTargets: '80, 0, 0', maxNumberOfBuilds: 0, methodCoverageTargets: '80, 0, 0', onlyStable: false, sourceEncoding: 'ASCII', zoomCoverageChart: false
archiveArtifacts artifacts: 'docs/unit-tests/htmlcoverage/*.*'
}
}
}
}
I have added the line sudo chown -R jenkins:jenkins ./docs/unit-tests/htmlcoverage because I was facing a permissions error to the coverage file :
INTERNALERROR> PermissionError: [Errno 13] Permission denied: 'coverage.xml'
I have also verified that coverage.xml is under root user and not the regular jenkins user (What even causes this?) :
What I have tried :
echo “jenkins ALL=(ALL) NOPASSWD: ALL” >> /etc/sudoers
This results in the same error sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper
echo “jenkins ALL= NOPASSWD: ALL” >> /etc/sudoers
This also results in the same error sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper
In both cases I have done a restart of the jenkins service. The jenkins user is also already added to sudo group.
What exactly am I missing ?
Answers:
As the docs
folder owned by root, you have to clean up it by root, you can add a temporary stage to clean it as following, once you ensure your pipeline won’t use root, you can remove the temporary stage
stage('Clean Test Report') {
steps {
sh '''
docker run -t
-u root
-v "$(pwd)":"$(pwd)"
-w "$(pwd)
--entrypoint=''
<what ever a docker image>
rm -rf docs
'''
}
}
stage('Unit Tests'){
steps{
withPythonEnv('/usr/bin/python3.8') {
sh '''
ls -al
pip install -r requirements.txt
pytest -v
--junitxml=docs/unit-tests/htmlcoverage/coverage.xml
--cov-report xml
--cov app.main
'''
}
}
}
To prevent pipeline run container as root, you can do as following
# run container by sh step
sh '''
docker run
-u $(id -u):$(id -g)
-v /etc/passwd:/etc/passwd
...
'''
# run container by docker global variable
container = docker.image(<image>).run(
'-u $(id -u):$(id -g) -v /etc/passwd:/etc/passwd -t',
'cat'
)
docker.image(<image>).withRun(
'-u $(id -u):$(id -g) -v /etc/passwd:/etc/passwd -t', 'cat') { container ->
sh """
docker exec -it ${container.id} -c
"pip install ..
pytest ..
"
"""
}
docker.image(<image>).inside('-v /etc/passwd:/etc/passwd') {
# inside() run container as current host user by default
sh '''
pip install ..
pytest ...
'''
}
# specify container as agent
# which runs container as current host user by default
agent {
docker {
args: '-v /etc/passwd:/etc/passwd'
}
}
agent {
dockerfile {
args: '-v /etc/passwd:/etc/passwd'
}
}
For anyone struggling with this – the problem was with rules precedence inside /etc/sudoers
.
By default if there are multiple entries for a user inside of the /etc/sudoers
file, sudo uses the last rule that applies
The change to jenkins
user was being overridden because of the rule’s position in /etc/sudoers
:
It is wrong because jenkins user is already a member of sudo group, and thus the line jenkins ALL=(ALL) NOPASSWD: ALL
will be overriden by the line
sudo ALL=(ALL:ALL) ALL
CORRECT
I have a FastAPI app for which I have configured Jenkins pipeline. When I execute unit tests with the code coverage enabled they are failing with the following error :
Started by user gold
Obtained Jenkinsfile from git https://github.com/edtshuma/devsecops-labs.git
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /var/lib/jenkins/workspace/Python-DevSecOps
....
....
[Pipeline] sh
+ pip install -r requirements.txt
....
Requirement already satisfied: uvicorn==0.20.0 in ./.pyenv-usr-bin-python3.8/lib/python3.8/site-packages (from -r requirements.txt (line 41)) (0.20.0)
Requirement already satisfied: watchfiles==0.18.1 in ./.pyenv-usr-bin-python3.8/lib/python3.8/site-packages (from -r requirements.txt (line 42)) (0.18.1)
Requirement already satisfied: websockets==10.4 in ./.pyenv-usr-bin-python3.8/lib/python3.8/site-packages (from -r requirements.txt (line 43)) (10.4)
+ sudo chown -R jenkins:jenkins ./docs/unit-tests/htmlcoverage
sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper
[Pipeline] }
[Pipeline] // withPythonEnv
Jenkinsfile
pipeline {
agent any
triggers {
githubPush()
}
stages {
stage('Setup'){
steps{
withPythonEnv('/usr/bin/python3.8') {
sh 'echo "Job is starting" '
}
}
}
stage('Unit Tests'){
steps{
withPythonEnv('/usr/bin/python3.8') {
sh '''pip install -r requirements.txt
sudo chown -R jenkins:jenkins ./docs/unit-tests/htmlcoverage
pytest -v --junitxml=docs/unit-tests/htmlcoverage/coverage.xml --cov-report xml --cov app.main
'''
}
}
}
stage('Publish Test Report'){
steps{
cobertura autoUpdateHealth: false, autoUpdateStability: false, coberturaReportFile: 'coverage*.xml', conditionalCoverageTargets: '70, 0, 0', failUnhealthy: false, failUnstable: false, lineCoverageTargets: '80, 0, 0', maxNumberOfBuilds: 0, methodCoverageTargets: '80, 0, 0', onlyStable: false, sourceEncoding: 'ASCII', zoomCoverageChart: false
archiveArtifacts artifacts: 'docs/unit-tests/htmlcoverage/*.*'
}
}
}
}
I have added the line sudo chown -R jenkins:jenkins ./docs/unit-tests/htmlcoverage because I was facing a permissions error to the coverage file :
INTERNALERROR> PermissionError: [Errno 13] Permission denied: 'coverage.xml'
I have also verified that coverage.xml is under root user and not the regular jenkins user (What even causes this?) :
What I have tried :
echo “jenkins ALL=(ALL) NOPASSWD: ALL” >> /etc/sudoers
This results in the same error sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper
echo “jenkins ALL= NOPASSWD: ALL” >> /etc/sudoers
This also results in the same error sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper
In both cases I have done a restart of the jenkins service. The jenkins user is also already added to sudo group.
What exactly am I missing ?
As the docs
folder owned by root, you have to clean up it by root, you can add a temporary stage to clean it as following, once you ensure your pipeline won’t use root, you can remove the temporary stage
stage('Clean Test Report') {
steps {
sh '''
docker run -t
-u root
-v "$(pwd)":"$(pwd)"
-w "$(pwd)
--entrypoint=''
<what ever a docker image>
rm -rf docs
'''
}
}
stage('Unit Tests'){
steps{
withPythonEnv('/usr/bin/python3.8') {
sh '''
ls -al
pip install -r requirements.txt
pytest -v
--junitxml=docs/unit-tests/htmlcoverage/coverage.xml
--cov-report xml
--cov app.main
'''
}
}
}
To prevent pipeline run container as root, you can do as following
# run container by sh step
sh '''
docker run
-u $(id -u):$(id -g)
-v /etc/passwd:/etc/passwd
...
'''
# run container by docker global variable
container = docker.image(<image>).run(
'-u $(id -u):$(id -g) -v /etc/passwd:/etc/passwd -t',
'cat'
)
docker.image(<image>).withRun(
'-u $(id -u):$(id -g) -v /etc/passwd:/etc/passwd -t', 'cat') { container ->
sh """
docker exec -it ${container.id} -c
"pip install ..
pytest ..
"
"""
}
docker.image(<image>).inside('-v /etc/passwd:/etc/passwd') {
# inside() run container as current host user by default
sh '''
pip install ..
pytest ...
'''
}
# specify container as agent
# which runs container as current host user by default
agent {
docker {
args: '-v /etc/passwd:/etc/passwd'
}
}
agent {
dockerfile {
args: '-v /etc/passwd:/etc/passwd'
}
}
For anyone struggling with this – the problem was with rules precedence inside /etc/sudoers
.
By default if there are multiple entries for a user inside of the /etc/sudoers
file, sudo uses the last rule that applies
The change to jenkins
user was being overridden because of the rule’s position in /etc/sudoers
:
It is wrong because jenkins user is already a member of sudo group, and thus the line jenkins ALL=(ALL) NOPASSWD: ALL
will be overriden by the line
sudo ALL=(ALL:ALL) ALL
CORRECT