Why this post?
I hit an issue at one of our client recently – beginning from the latest version of the application, the remote server will prompt for a new password in the first login attempt, the password that comes out of the box will take you to the set new password prompt where you need to set a new password, re-type the new password & then login again with the new password. While this change is a really good security feature, it led to our automation tests failing.
I was able to fix this, but while solving it, I realized that a solution using Paramiko was not easily available, I had to google for a few issues with my proposed solutions. I am sharing it to help another tester who might face this same problem.
My solution:
I created a method to invoke an interactive shell with the Paramiko SSH client to:
-
1. check for existing password prompt(to confirm new password prompt)
-
2. verify new password prompt
-
3. enter a new password
-
4. re-enter the new password again
-
5. get the confirmation message to assist debug
NOTE: I am assuming you are working with a partially working code already. I am sharing the function alone hence. If you are new to Paramiko and wish to understand it better before solving this issue, refer our blog on Paramiko
def handle_new_password_prompt(): """ Check if new password is prompted and set it :param: :return client: SSH client object """ client = None host = your_host user_name = your_user_name port = your_port old_pwd = your_old_password new_pwd = your_new_password try: # create an ssh client object client = paramiko.SSHClient() logging.getLogger("paramiko").setLevel(logging.ERROR) client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # connect to the remote server with the old password client.connect(host, username=user_name, password=old_pwd, port=port, timeout=30) # invoke an shell shell_obj = ssh_client.invoke_shell() # get the shell output, get the complete output with the while loop out = '' # sleep is essential, recv_ready returns False without sleep time.sleep(1) while shell_obj.recv_ready(): out += shell_obj.recv(2048) # get the last line of the output out = out.split('\n')[-1] ########### step #1 ############ # verify if the last line of the output prompts existing password if out.strip() == '(current) UNIX password:': shell_obj.send(old_pwd+'\n') print("Old password when prompted was entered successfully") ########### step #2 ############ # check prompt for new password out = '' time.sleep(1) while shell_obj.recv_ready(): out += shell_obj.recv(2048) ########### step #2 ############ # if new password prompted, Enter new password if out.strip() == 'New password:': ########### step #3 ############ shell_obj.send(new_pwd+'\n') print("New password when prompted was entered successfully") ########### step #4 ############ # check re-enter new password prompt out = '' time.sleep(1) while shell_obj.recv_ready(): out += shell_obj.recv(2048) # if re-enter new password prompted, Enter the new password again if out.strip() == 'Retype new password:': shell_obj.send(new_pwd+'\n') print("Re-enter new password when prompted was entered successfully") ########### step #5 ############ # get the output after setting password - useful debug statement out = '' time.sleep(1) while shell_obj.recv_ready(): out += shell_obj.recv(2048) if out: print("Confirmation after setting new password is - {}".format(out)) shell_obj.close() except Exception as e: print("Unable to set new password due to - {}".format(str(e))) else: client.connect(host, username=user_name, password=new_pwd, port=port, timeout=30) finally: return client |
I have posted the whole snippet as one big function to explain the flow of actions, I would suggest you break it down into smaller methods.
This is what I learned while working on this fix :
1. recv_ready()
method requires the while
loop to read the complete data buffer
2. time.sleep(1)
although is mundane is required(reference – paramiko issue) for the shell_obj
to be ready to receive data
3. using recv_ready()
in the end to read the output after setting the new password is very useful.
This solution worked for me, I was able to run my automation tests with this fix. I hope this information is as useful to you as it was to me.
If you have tried a different solution and it worked for you, great! let us know your solution in a comment.
My expertise lies in engineering high-quality software. I began my career as a manual tester at Cognizant Technology Solutions, where I worked on a healthcare project. However, due to personal reasons, I eventually left CTS and tried my hand at freelancing as a trainer. During this time, I mentored aspiring engineers on employability skills. As a hobby, I enjoyed exploring various applications and always sought out testing jobs that offered a good balance of exploratory, scripted, and automated testing.
In 2015, I joined Qxf2 and was introduced to Python, my first programming language. Over the years, I have also had the opportunity to learn other languages like JavaScript, Shell scripting (if it can be called a language at all), and more recently, Rust. Despite this exposure, Python remains my favorite language due to its simplicity and the extensive support it offers for libraries.
In my free time, I like to watch football (I support Arsenal Football Club), play football myself, and read books.
Alas, the solution doesn’t work for me on ZPE devices because it fails on the following line:
client.connect(host, username=user_name, password=old_pwd, port=port, timeout=30)
The problem is that Paramiko can’t establish connection until new password is provided, so it throws the following error without going further with the script.
paramiko.ssh_exception.AuthenticationException: Authentication failed.
Hi,
We would like to know more about the error.
Does your device not prompt for old password before asking you to set a new password?
If that’s the case, you can ignore that step & start setting the new password.
Worked very well for me! Trying to set up automatic server creation and configuration for Hetzner servers with ansible and this was the one step hindering me from accessing the new server. New to python as well, so this was not just a good solution but its also very readable! +1