Question

I would like a way to update my password on a remote Ubuntu 10.4 box with fabric.

I would expect my fabfile.py would look something like this:

def update_password(old_pw, new_pw):
    # Connects over ssh with a public key authentication
    run("some_passwd_cmd --old %s --new %s" % (old_pw, new_pd))

Unfortunately the only command I know of that lets one change the password is passwd, and on Ubuntu 10.4 there doesn't seem to be any way to pass in the new (or old) password as an argument to passwd.

What command could one use to change a user's password on Ubuntu 10.4 via fabric?

EDIT: I've had a look at usermod -p, and that may work but it isn't recommended by the man page.

EDIT: For some reason usermod -p wasn't working either over fabric.

As well, I've tried a (somewhat insecure) variation on mikej's answer that did solve the problem:

# connecting & running as root.
from fabric.api import *
from fabric.contrib import files

files.append("%s\n%s" % (passwd, passwd), '.pw.tmp')
# .pw.tmp:
# PASSWD
# PASSWD

run("passwd %s < .pw.tmp" % user)

run("rm .pw.tmp")

It's not a very elegant solution, but it works.

Thank you for reading.

Brian

Was it helpful?

Solution

You could feed the new and old passwords into passwd using echo e.g.

echo -e "oldpass\\nnewpass\\nnewpass" | passwd

(the -e option for echo enables interpretation of backslash escapes so the newlines are interpreted as such)

OTHER TIPS

The trick is to use a combination of usermod and Python’s crypt to change your password:

from crypt import crypt
from getpass import getpass
from fabric.api import *

def change_password(user):
    password = getpass('Enter a new password for user %s:' % user)
    crypted_password = crypt(password, 'salt')
    sudo('usermod --password %s %s' % (crypted_password, user), pty=False)

I use chpasswd on Ubuntu 11.04

fabric.api.sudo('echo %s:%s | chpasswd' % (user, pass))

Note: Normally this pattern doesn't work:

$ sudo echo bla | restricted_command

because only the 'echo' gets elevated privileges, not the 'restricted_command'.

However, here it works because when fabric.api.sudo is caled with shell=True (the default), fabric assembles the command like this:

$ sudo -S -p <sudo_prompt> /bin/bash -l -c "<command>"

sudo spawns a new shell (/bin/bash), running with root privileges, and then that escalated shell runs the command.

Another way to pipe with sudo is to use sudo tee:

Out of interest, I have to do a similar task on a collection of Solaris boxes (add a whole lot of users, set their password). Solaris usermod doesn't have a --password option, so in the past I've used Expect to do this, but writing Expect scripts can be painful.

So this time I'm going to use Python's crypt.crypt, edit /etc/shadow directly (with backups, of course). http://docs.python.org/release/2.6.1/library/crypt.html

Commenters have suggested using various echo incantations piped to passwd. AFAIK this will never work, as passwd is programmed to ignore input from stdin and only accept input from an interactive tty. See http://en.wikipedia.org/wiki/Expect

I had no luck with the other methods. Thought I would share my method that I used for a once-off throwaway script.

It uses auto-responder to type in passwords at the prompts. I then immediately expire all the passwords so that users have a chance to choose their own.

This is not the most secure method, but depending on your use case it may be useful.

from collections import namedtuple
from getpass import getpass
import hashlib
from invoke import Responder
import uuid

from fabric import Connection, Config


User = namedtuple('UserRecord', ('name', 'password'))


def set_passwords(conn, user):
    print(f'Setting password for user, {user.name}')
    responder = Responder(
        pattern=r'(?:Enter|Retype) new UNIX password:',
        response=f'{user.password}\n',
    )
    result = conn.sudo(f'passwd {user.name}', warn=True, hide='both',
                       user='root', pty=True, watchers = [responder])
    if result.exited is not 0:
        print(f'Error, could not set password for user, "{user.name}". command: '
              f'{result.command}; exit code: {result.exited}; stderr: '
              f'{result.stderr}')
    else:
        print(f'Successfully set password for {user.name}')


def expire_passwords(conn, user):
    print(f'Expiring password for user, {user.name}')
    cmd = f'passwd --expire {user.name}'
    result = conn.sudo(cmd, warn=True, user='root')
    if result.exited is not 0:
        print(f'Error, could not expire password for user, "{user.name}". '
              f'command: {result.command}; exit code: {result.exited}; stderr: '
              f'{result.stderr}')
    else:
        print(f'Successfully expired password for {user.name}')


def gen_password(seed_string):
    # Don't roll your own crypto. This is for demonstration only and it is
    # expected to only create a temporary password that requires changing upon
    # initial login. I am no cryptography expert, hence this alternative
    # simplified answer to the one that uses crypt, salt, etc - 
    # https://stackoverflow.com/a/5137688/1782641.
    seed_str_enc = seed_string.encode(encoding='UTF-8')
    uuid_obj = uuid.UUID(int=int(hashlib.md5(seed_str_enc).hexdigest(), 16))
    return str(uuid_obj)[:8]


def some_function_that_returns_something_secret(conn):
    return f'dummy-seed-{conn}'

sudo_pass = getpass('Enter your sudo password:')
config = Config(overrides={'sudo': {'password': sudo_pass}})
with Connection('vm', config=config) as vm_conn:
    print(f'Making a new connection to {vm_conn.host}.')
    # I usually use the sudo connection here to run a command that returns a
    # reproducible string that only the sudo user could get access to be used 
    # for user_record.password bellow. Proceed with caution, this is not a 
    # recommended approach
    seed = some_function_that_returns_something_secret(vm_conn)
    user_record = User(name='linux_user', password=gen_password(seed))
    set_passwords(vm_conn, user_record)
    expire_passwords(vm_conn, user_record)
    print(f'Done! Disconnecting from {vm_conn.host}.')

# So that you know the temporary password, print user_record or save to file
# `ssh linux_user@vm` and it should insist that you change password
print(user_record)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top