【问题标题】:Change Unix password from command line over Python/Fabric通过 Python/Fabric 从命令行更改 Unix 密码
【发布时间】:2026-01-04 11:05:01
【问题描述】:

我想用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 上似乎没有任何方法可以将新(或旧)密码作为参数传递给 @987654326 @。

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

编辑: 我查看了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")

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

感谢您的阅读。

布赖恩

【问题讨论】:

  • 请注意,在 Lucid 上,usermod -p 的参数是“加密密码,由 crypt(3) 返回”,使用 SHA-512 而不是明文。 usermod 页面中的警告相当于说“您将在短时间内将 /etc/shadow 的(通常隐藏的)散列内容放入进程表中”,这取决于您的安全要求,可能并非全部揭示。

标签: python unix passwords fabric passwd


【解决方案1】:

您可以使用echo 将新旧密码输入passwd,例如

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

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

【讨论】:

  • @Mikej - 感谢您的回复。我一直在尝试这个,但我遇到了我认为逃避的麻烦。特别是run("echo -e \"%s\\n%s\\n%s\" | /usr/bin/passwd" % (old_pw, new_pw, new_pw)) 不起作用(即返回“UNIX 密码:passwd:身份验证令牌操作错误”)
  • 您可能需要对 \ 进行两次转义(一次用于 Python,一次用于回显)例如\\\\n 每个换行符
  • @Mikej:当我从命令行运行它时,它工作正常。但是,当我在 fabric 上运行它时,我得到以下信息:UNIX password: Enter new UNIX password: Retype new UNIX password: passwd: Authentication token manipulation error
  • 我已经尝试过run("echo -e \"%s\\\\n%s\" | passwd %s" % (passwd, passwd, user), shell=False) 导致命令echo -e "PASSWD\\nPASSWD" | passwd USER(对于passwd=PASSWDuser=USER)。唉,这会导致“UNIX 密码:passwd:身份验证令牌操作错误”。当我从 shell 运行 echo 命令时,它按预期工作。
  • 新密码或旧密码中是否有任何字符可能还需要转义(尝试暂时将密码更改为简单的字母数字密码。)另外,我认为您正在包括新密码确认,如您的第一条评论,即使您没有将其包含在最近的评论中?
【解决方案2】:

诀窍是使用 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)

【讨论】:

  • 不错!我想补充一点,在询问密码时使用 getpass 而不是 prompt 可能是个好主意。
  • salt for crypt 只能是 [a–zA–Z0–9./] 中的两个字符,因此 'salt' 与 'sa' 相同,参见docs。跨度>
  • 好电话。谢谢你。
【解决方案3】:

出于兴趣,我必须在一组 Solaris 机器上执行类似的任务(添加大量用户,设置他们的密码)。 Solaris usermod 没有 --password 选项,所以过去我使用 Expect 来执行此操作,但编写 Expect 脚本可能会很痛苦。

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

评论者建议使用通过管道传送到 passwd 的各种回声咒语。 AFAIK 这永远不会起作用,因为 passwd 被编程为忽略来自标准输入的输入,只接受来自交互式 tty 的输入。见http://en.wikipedia.org/wiki/Expect

【讨论】:

    【解决方案4】:

    我在 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 (默认),fabric 组装命令如下:

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

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

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

    【讨论】:

      【解决方案5】:

      其他方法我没有运气。想我会分享我用于一次性脚本的方法。

      它使用自动回复在提示符处输入密码。然后我立即使所有密码过期,以便用户有机会选择自己的密码。

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

      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://*.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)
      
      

      【讨论】: