pyinfra-dev / pyinfra

pyinfra turns Python code into shell commands and runs them on your servers. Execute ad-hoc commands and write declarative operations. Target SSH servers, local machine and Docker containers. Fast and scales from one server to thousands.
https://pyinfra.com
MIT License
3.85k stars 374 forks source link

Need a better way to provide sudo password for remote operations over many nodes #965

Open wookayin opened 1 year ago

wookayin commented 1 year ago

Related to #305 but this feature is still lacking so I open a new issue.

Is your feature request related to a problem? Please describe

We would need a better way to enter or prompt sudo password for many remote servers.

It seems that the documentation still lacks an easy guide about how to require SUDO password when deploying to remote machines. #305 suggests use of config.SUDO or config.USE_SUDO_PASSWORD (or --use-sudo-password) which would prompt sudo password for one remote machine, but if you are working on N number of machines to run some tasks it will prompt N times:

$ pyinfra node1,node2,...,node100 t/task.py

--> Preparing Operations...
    Loading: t/task.py
[node1] sudo password: 
[node2] sudo password: 
[node3] sudo password: 
[node4] sudo password: 
..
[node100] sudo password: 

Of course it has to be entered 100 times. Ideally one would want to enter (remote) sudo password only once, assuming the same password would work for all the machines.

305 and #481 discuss the use of SUDO_ASKPASS, but I was unable to make it work. I would not want to write down secret information somewhere into file, it should be prompted and forgotten every single time when an operation is taking place. Hence, pyinfra should take care of passing SUDO passwords more in a built-in way.

Describe the solution you'd like

Not concrete idea yet. Or any other workaround/suggestions would be appreciated.

wookayin commented 1 year ago

@Fizzadar any advices?

Fizzadar commented 1 year ago

@wookayin apologies for the delay, I have been away the last few weeks! In this case is the sudo password the same for each host?

So essentially want to have prompt input but once and then use that for all servers, should be possible. In fact - now that pyinfra automatically prompts the user for any passwords, use_sudo_password is somewhat redundant anyway as a flag, perhaps it should now be used to prompt for a global sudo password.

wookayin commented 1 year ago

@Fizzadar thanks for your reply! Yes in my use cases and in many other practical use cases sudo password for each host is the same. We should also consider a case where passwords might be different, in which then pyinfra may want to have some flag to distinguish such scenarios.

ebreton commented 1 year ago

Hi both,

jumping in with my use case where each device has a different password: it would be nice to be able to provide the appropriate one for each of them through the data.

I am currently using this inventory:

servers = [
    ('2809', {'ssh_user': "nvidia", 'ssh_password': "password1"}, ),
    ('2810', {'ssh_user': "nvidia", 'ssh_password': "password2"}, ),
]

It would be great if the ssh_password here could be automatically grabbed as sudo password when necessary :)

Fizzadar commented 1 year ago

@ebreton this should be possible today with the following:

servers = [
    ('2809', {'ssh_user': "nvidia", 'ssh_password': "password1", "_use_sudo_password": "password1"}, ),
    ('2810', {'ssh_user': "nvidia", 'ssh_password': "password2", "_use_sudo_password": "password2"}, ),
]

The _use_sudo_password global already accepts a string value as well as boolean (but is confusing).

ebreton commented 1 year ago

Excellent! Works like a charm. Thanks a lot

wookayin commented 10 months ago

Here is how I am doing:

Make a inventory file, say nodes/all-sudo

#!/usr/bin/env python3

import getpass
_SUDO_PASSWORD = getpass.getpass(prompt="Sudo password: ")

def node(node_name: str, **opts):
    # See https://github.com/Fizzadar/pyinfra/issues/965
    opts['_use_sudo_password'] = _SUDO_PASSWORD
    return (node_name, opts)

nodes = [
    node("server1", some_additaionl_options="foo"),
    node("server2"),
    node("server3"),
]

if __name__ == '__main__':
    print(" ".join(name for (name, _) in nodes))

and then run pyinfra nodes/all-sudo operation.py. BEAWARE that password will be printed in a plain text.