heisenbug: pexpect и смена паролей в Mac OS X через ssh

В моей организации у нас много компьютеров под управлением OS X Yosemite. У каждой из этих машин есть учетная запись по умолчанию, которую наша ИТ-команда может использовать для доступа к машине для оказания ИТ-помощи. Мы хотим периодически менять пароль для этой учетной записи, и, по мере роста числа маков в организации, мы хотим найти способ автоматизировать эту задачу.

Я написал скрипт на Python, используя pexpect это будет SSH в каждую машину и выполнить dscl чтобы изменить пароль для входа, затем SSH на каждой машине для запуска security изменить логин брелок пароль.

Эти методы являются частью класса, в котором старые и новые пароли хранятся в old_password а также new_password атрибуты:

def _change_login_password(self, host):
    """Change the login password of a machine.

    Returns True on success, False on failure.
    """

    try:
        child = pexpect.spawn(
            "ssh default@{} dscl . passwd /Users/default".format(host))
        child.expect("Password:")
        child.sendline(self.old_password)
        child.expect("New Password: ")
        child.sendline(self.new_password)
        child.expect(
            "Permission denied. Please enter user's old password:")
        child.sendline(self.old_password)
        child.close()

        return not child.exitstatus

    except pexpect.TIMEOUT:
        return False

def _change_keychain_password(self, host, login_password):
    """Change the keychain password of a machine.

    Changes the keychain password to match the login password.

    Returns True on success, False on failure.
    """

    try:
        child = pexpect.spawn(
            "ssh default@{} security set-keychain-password"
            " login.keychain".format(host))
        child.expect("Password:")
        child.sendline(login_password)
        child.expect("Old Password: ")
        child.sendline(self.old_password)
        child.expect("New Password: ")
        child.sendline(login_password)
        child.expect("Retype New Password: ")
        child.sendline(login_password)
        child.close()

        return not child.exitstatus

    except pexpect.TIMEOUT:
        return False

Оба эти метода не проходят тестирование на моем рабочем ноутбуке OS X Yosemite. Каждая команда, выполняемая через ssh, возвращает ненулевой статус выхода, и пароли на ноутбуке не меняются.

... однако, когда я вставляю pdb точка останова в верхней части любого метода, а затем пошаговое выполнение метода в отладчике без внесения каких-либо изменений, состояние выхода становится равным 0, а пароль на ноутбуке изменяется.

Гейзенбаг.

Вставка печатных операторов вокруг pexpect вызовы иногда также приводят к успешному завершению метода.

Я уже экспериментировал с pexepect"s delaybeforesend атрибут, который добавляет задержку перед sendline отправляет свою полезную нагрузку дочернему процессу, думая, что задержка при включении в отладчик могла быть проблемой, но это не решило проблему.

Кто-нибудь знает, где я мог бы посмотреть дальше? Некоторые из моих коллег подозревают, что tty вопрос. Кто-нибудь знает как pdb может влиять на среду и вызывать успешное выполнение этих команд, или что в OS X вызывает их сбой?

1 ответ

Ааа, так вот в чем дело. При ближайшем рассмотрении оба процесса выходили с кодом состояния 130, что соответствует Ctrl+C. Мне нужно было добавить expect(pexpect.EOF) в оба метода, прежде чем close, так что у них был шанс закончить, прежде чем быть выключенным.

def _change_login_password(self, host):
    """Change the login password of a machine.

    Returns True on success, False on failure.
    """

    try:
        child = pexpect.spawn(
            "ssh -ttt default@{} dscl . passwd /Users/default".format(host))
        child.expect("Password:")
        child.sendline(self.old_password)
        child.expect("New Password: ")
        child.sendline(self.new_password)
        child.expect(
            "Permission denied. Please enter user's old password:")
        child.sendline(self.old_password)
        child.expect(pexpect.EOF)  # Wait for EOF.
        child.close()

        return not child.exitstatus

    except pexpect.TIMEOUT:
        return False

    def _change_keychain_password(self, host, login_password):
    """Change the keychain password of a machine.

    Changes the keychain password to match the login password.

    Returns True on success, False on failure.
    """

    try:
        child = pexpect.spawn(
            "ssh -ttt default@{} security set-keychain-password"
            " login.keychain".format(host))
        child.expect("Password:")
        child.sendline(login_password)
        child.expect("Old Password: ")
        child.sendline(self.old_password)
        child.expect("New Password: ")
        child.sendline(login_password)
        child.expect("Retype New Password: ")
        child.sendline(login_password)
        child.expect(pexpect.EOF)  # Wait for EOF.
        child.close()

        return not child.exitstatus

    except pexpect.TIMEOUT:
        return False

Я также, как предложил Робертклеп, использовал ssh -t (на самом деле ssh -ttt, так, что я чертовски хорошо получаю это tty). Сочетание обоих этих способов устранило проблему, и теперь я с радостью меняю пароли по ssh.

Другие вопросы по тегам