Automatically answer 'yes' on Python's 'click' prompts (unattended command run) - python

I have built a command line interface using Python's click library (version 7.1.2) that can run a command that asks the (human) user to answer certain confirmations on the way (The well known "Are you sure you want to continue? Y/n")
Now, I'm trying to run that command automatically on a Kubernetes CronJob and I need a way of "pretending" to input Y to each of the prompts.
I have seen the click.confirmation_option, but if I understand correctly, that seems more like a confirmation to run the whole command, right? (maybe I'm wrong?)
I'm more looking for something like some kind of assume_yes in the example below (could be passed either to the invocation or when creating full context ctx = cli.make_context...):
from my_command import cli
ctx = cli.make_context(
'main_command_group', ["action", "--num_times", 3]
)
with ctx:
result = cli.invoke(ctx, assume_yes=True)
Is that possible?
I can always add some kind of environment variable, or pass an extra boolean Option (a flag) to my Click command and edit my code so before even trying to show the confirmation prompt, it'd check whether the environment variable (or the flag) is True, and if so, then assume yes (without even showing the prompt), but it feels like Click must have something for unattended runs.
EDIT: (As per the comments)
The prompts look like:
customer = CustomerManager.get_by_name(customer_name)
if (
not customer and
click.confirm(f"Create customer {customer_name}", default=True)
):
customer = CustomerManger.create(name=customer_name)
# more code
if not customer.address:
if click.confirm(
f"No address for customer {customer.name}. Leave blank?",
default=True):
# ...
As to why doing it this way: Because the exact same code is going to be run first by humans (as a script... on the command line) and once the human user "ok"s it, it will be pushed to a pod where it'll be run by a CronJob.
Running the script on the command line will populate a local database, run some data checks, some verifications... So once everything is confirmed to be "good" locally by a human, a signal will be sent to a Kubernetes Cronjob that will do exactly the same as the humans. The only difference is that at that point it can assume the answer to all the prompts is "yes".
So yes: the code could be refactored, but it would be nice if it
didn't have to be (because when the CLI is run by the cronjob, we
already know that the answer to every confirmation prompt is going to
be a "yes")

How do you expect from Click_ to handle this out of the box? The system needs to know that you are a robot, and not a human. How could you achieve this? One way is to have some environment variable, another way is to use special API for your cron job. Nevertheless – you will end up with a special option for this case.
You are totally right – the confirmation_option doesn't cover your case, but it shows you how are you supposed to handle such cases.
So, what I would do is something like this:
#click.option('--yes', is_flag=True)
def my_method(customer_name, yes):
if not yes:
click.confirm(f"Create customer {customer_name}", default=False, abort=True)
customer = CustomerManger.create(name=customer_name)
You could use environment variables, but I don't see any reason to do it. You can force your cron job to provide the --yes option. It is easier and more obvious.
I played around with the abort=True here, because otherwise I need to repeat the create line one more time, but it may not be suitable for you, if you don't want to abort the whole command and just want to skip the creation of the customer.
And something not related to your question: I advise you to default to False (this is how Click_ works by default), instead of True in your confirmation. I could hit enter unintentionally and shoot myself in the foot. You won't have confirmation dialogue if it wasn't dangerous to confirm bad data, so better don't easy the confirmation that way :)

OK, if you want to do this globally, and not per command, you may override easily the confirm() behaviour:
def confirm(*args, **kwargs):
if not os.environ.get('yes'):
click.confirm(*args, **kwargs)
And than, you could use your local version of confirm, instead of click.confirm in your code:
#click.command()
def test():
confirm('Create customer', default=False, abort=True)
...
Now, if you export the yes environment variable, you will see the difference.

Related

Examples of user-entered code that would cause an issue

Let's say the user has installed a python interpreter on their machine/browser, for example, using something like https://github.com/iodide-project/pyodide. I understand not allowing someone to enter in arbitrary code when they don't own the resources, for example doing something like:
exec('while 1: os.fork()')
However, if the user is executing the code on their own machine, is there anything wrong with allowing them to run arbitrary evals and execs, and just telling them "Please use at your own risk"? The use case is we give the user an environment to work with a spreadsheet, and they can enter in formulas using python, and we just 'pass-through' the entered string (in the spreadsheet cell) to their python environment.
If you are OK with the user being able to run arbitrary Javascript code client side (which is true for all websites), it should be also OK for them to run arbitrary code with Pyodide. Both are sandboxed by the browser.
For instance, they won't be able to interact with their actual file system, nor generally make any system calls that don't pass through the Webassembly VM. See https://webassembly.org/docs/security/ for more details.

Disable subprocess.Popen echo to command prompt window title in Python

I'm trying to use a Python's subprocess.Popen to build up a module for Python bindings to a command line interface; pretty much there as far as the CLI-bindings go, but I really want to be able to mask some "private" arguments.
The CLI uses account information, and I want to hide the account credentials from a command prompt title. Below is a screen capture of what appears when I use the login method for my CLI-bindings class.
I know that I'm using plain text for the password here ('TAIL') but I wanted to simply show the usage of what's going on. See from the image that the full command "sent" using the subprocess.Popen is displayed in the prompt's title.
I recently found that there is a way to programmatically change the title using either os or ctypes, so I could effectively mask, or cover-up, the "private" credentials I don't want shown on the command prompt title like shown here:
but is there a better way of disabling "echo-to-title" (for lack of a better name) with subprocess.Popen?
in fact passwords should never be passed as command line arguments to an executable.
Reason:
anybody being able to look at running processes and their parameters can see the password.
One solution is, that the parent process. puts the password in an environment variable and the program to be called fetches the parameter from an environment variable.
so the caller would be something like:
import os
os.environ["ACRTAC_PASSWORD"] = "TAIL"
subprocess.Popen([...])
and the acrtac.py
import os
password = os.environ["ACRTAC_PASSWORD"]

Is it possible to restrict access to globals for a block of code in python?

I would like users of my program to be able to define custom scripts in python without breaking the program. I am looking at something like this:
def call(script):
code = "access modification code" + script
exec(code)
Where "access modification code" defines a scope such that script can only access variables it instantiates itself. Is it possible to do this or something with similar functionality, such as creating a new python environment with its own scope and then receiving output from it?
Thank you for your time :)
Clarification Edit
"I want to prevent both active attacks and accidental interaction with the program variables outside the users script (hence hiding all globals). The user scripts are intended to be small and inputted as text. The return of the user script needs to be immediate, as though it were native to the program."
There's two separate problems you want to prevent in this scenario:
Prevent an outside attacker from running arbitrary code in the context of the OS user executing your program. This means preventing arbitrary code execution and privilege escalation.
Preventing a legitimate user of your program from changing the program behavior in unintended ways.
For the first problem, you need to make sure that the source of the python code you execute is the user. You shouldn't accept input from a socket, or from a file that other users can write to. You need to make sure, somehow, that it was the user who is running the program that provided the input.
Actual solutions will depend on your OS, but you may want to consider setting up restrictive file permissions, if you're storing the code in a file.
Don't ignore or downplay this problem, or your users will fall victim to virus/malware/hackers thanks to your program.
The way you solve the second problem depends on what exactly constitutes intended behaviour in your program. If you're happy with outputting simple data structures, you can run your user-inputted code in a separate process, and pass the result over a pipe in a serialization format such as JSON or YAML.
Here's a very simple example:
#remember to set restrictive file permissions on this file. This is OS-dependent
USER_CODE_FILE="/home/user/user_code_file.py"
#absolute path to python binary (executable)
PYTHON_PATH="/usr/bin/python"
import subprocess
import json
user_code= '''
import json
my_data= {"a":[1,2,3]}
print json.dumps(my_data)
'''
with open(USER_CODE_FILE,"wb") as f:
f.write(user_code)
user_result_str= subprocess.check_output([PYTHON_PATH, USER_CODE_FILE])
user_result= json.loads( user_result_str )
print user_result
This is a fairly simple solution, and it has significant overhead. Don't use this if you need to run the user code many times in a short period of time.
Ultimately, this solution is only effective against unsophisticated attackers (users). Generally speaking, there's no way to protect any user process from the user itself - nor would it make much sense.
If you really want more assurance, and want to mitigate the first problem too, you should run the process as a separate ("guest") user. This, again, is OS-dependent.
Finally a warning: avoid exec and eval to the best of your abilities. They don't protect either your program or the user against the inputted code. There's a lot of information about this on the web, just search for "python secure eval"

Running subprocess commands as root in python

I'm writing a GUI program, that configures your systems settings. For this purpose, the whole program should not be ran as root, otherwise it would configure the system for the root user. However, there is a subprocess command that needs to be ran as root, and I'm not sure how to safely, and properly incorporate this into my GUI for the following reasons.
The user would almost have to enter it into the GUI frontend.
I'm not sure how to verify that the users password was indeed correct. How to add error proofing to alert the user that the password is incorrect, without just letting the command fail miserably.
How to run this safely, since the users password is going to be involved.
I've been reccomended to create a daemon, and pass commands to that. This seems like a bit overkill, since it's just one command that needs to be ran. And since the user can't just type this into the terminal, it needs to be handled by the frontend of the GUI.
Does anyone have any other ideas on how to incorporate this feature?
You can use pkexec.
For example:
proc = subprocess.Popen(['/usr/bin/pkexec', command])

how to ask user input a string with a timeout embeded in python on windows machine?

want to ask user to input something but not want to wait forever. There is a solution for Linux, Keyboard input with timeout in Python, but I am in windows environment. anybody can help me?
Credit to Alex Martelli
Unfortunately, on Windows,
select.select works only on sockets,
not ordinary files nor the console.
So, if you want to run on Windows, you
need a different approach. On Windows
only, the Python standard library has
a small module named msvcrt, including
functions such as msvcrt.kbhit which
tells you whether any keystroke is
waiting to be read. Here, you might
sys.stdout.write the prompt, then
enter a small loop (including a
time.sleep(0.2) or so) which waits to
see whether the user is pressing any
key -- if so then you can
sys.stdin.readline etc, but if after
your desired timeout is over no key
has been hit, then just return the
empty string from your function.
All of this assumes that if the user
has STARTED typing something then you
want to wait indefinitely (not timeout
in the middle of their entering their
answer!). Otherwise, you have more
work to do, since you must ensure that
the user has hit a Return (which means
you must peek at exactly what's in
sys.stdin, resp. use msvcrt.getch, one
character at a time). Fortunately, the
slightly simpler approach of waiting
indefinitely if the user has started
entering seems to be the preferable
one from a user interface viewpoint --
it lets you deal with unattended
consoles as you desire, yet IF the
user is around at all it gives the
user all the time they want to
COMPLETE their answer.

Categories

Resources