通过Python / Fabric从命令行更改Unix密码

时间:2010-06-20 19:11:10

标签: python unix passwords fabric passwd

我希望能够使用fabric在远程Ubuntu 10.4框中更新我的密码。

我希望我的fabfile.py看起来像这样:

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))

不幸的是,我所知道的唯一命令是允许更改密码passwd,而在Ubuntu 10.4上似乎没有任何方法可以将新(或旧)密码作为参数传递给passwd

可以使用什么命令通过fabric在Ubuntu 10.4上更改用户密码?

修改: 我已经看了usermod -p,这可能会有效,但手册页不建议这样做。

编辑:出于某种原因,usermod -p无法通过结构进行操作。

同样,我在mikej的答案中尝试了一种(有些不安全的)变体来解决问题:

# 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")

这不是一个非常优雅的解决方案,但它确实有效。

感谢您的阅读。

布赖恩

5 个答案:

答案 0 :(得分:14)

您可以使用passwd例如

将新密码和旧密码提供给echo
echo -e "oldpass\\nnewpass\\nnewpass" | passwd

-e的{​​{1}}选项启用了反斜杠转义的解释,因此换行符被解释为这样)

答案 1 :(得分:10)

诀窍是使用usermod和Python crypt的组合来更改密码:

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)

答案 2 :(得分:5)

我在Ubuntu 11.04上使用chpasswd

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

注意: 通常这种模式不起作用:

$ sudo echo bla | restricted_command

因为只有'echo'获得了提升权限,而不是'restricted_command'。

但是,这里有效,因为当fabric.api.sudo被篡改时 使用shell = True(默认值),结构组装命令如下:

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

sudo生成一个新的shell(/ bin / bash),以root权限运行,和 那个升级的shell运行命令。

使用sudo管道的另一种方法是使用sudo tee

答案 3 :(得分:3)

出于兴趣,我必须在一组Solaris盒子上做一个类似的任务(添加很多用户,设置他们的密码)。 Solaris usermod没有--password选项,所以在过去我使用Expect来做这件事,但编写Expect脚本可能很痛苦。

所以这次我将使用Python的crypt.crypt,直接编辑/ etc / shadow(当然还有备份)。 http://docs.python.org/release/2.6.1/library/crypt.html

评论者建议使用管道传输的各种回声咒语。 AFAIK这将永远不会工作,因为passwd被编程为忽略来自stdin的输入并且仅接受来自交互式tty的输入。见http://en.wikipedia.org/wiki/Expect

答案 4 :(得分:1)

我没有其他方法的运气。以为我会分享我曾经使用过的一次性脚本的方法。

它使用自动响应器在提示符下键入密码。然后,我立即使所有密码失效,以便用户有机会选择自己的密码。

这不是最安全的方法,但是根据您的用例,它可能会有用。

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)