Ansibleのsudo ask-sudo-pass はどう実装されているか


ansible の -Kオプションによるリモートホストへのsudoのパスワード引き渡しが便利すぎるのですが、どのように実装されているか気になったので調べてみた。


https://github.com/ansible/ansible/blob/devel/lib/ansible/runner/connection_plugins/paramiko_ssh.py#L209
この辺で、sudo_passが使われている。


```python:ansible/ansible/blob/devel/lib/ansible/runner/connection_plugins/paramiko_ssh.py#L209
  if self.runner.sudo or sudoable:
      shcmd, prompt, success_key = utils.make_sudo_cmd(sudo_user, executable, cmd)
  elif self.runner.su or su:
      shcmd, prompt, success_key = utils.make_su_cmd(su_user, executable, cmd)
  vvv("EXEC %s" % shcmd, host=self.host)
  sudo_output = ''
  try:
      chan.exec_command(shcmd)
      if self.runner.sudo_pass or self.runner.su_pass:
          while not sudo_output.endswith(prompt) and success_key not in sudo_output:
              chunk = chan.recv(bufsize)
              if not chunk:
                  if 'unknown user' in sudo_output:
                      raise errors.AnsibleError(
                          'user %s does not exist' % sudo_user)
                  else:
                      raise errors.AnsibleError('ssh connection ' +
                          'closed waiting for password prompt')
              sudo_output += chunk
          if success_key not in sudo_output:
              if sudoable:
                  chan.sendall(self.runner.sudo_pass + '\n')
              elif su:
                  chan.sendall(self.runner.su_pass + '\n')

```

sendallで標準入力に流し込んでいるもよう。

make_sudo_cmdをみてみよう



https://github.com/ansible/ansible/blob/devel/lib/ansible/utils/__init__.py#L827

```python:ansible/ansible/blob/devel/lib/ansible/utils/__init__.py
def make_sudo_cmd(sudo_user, executable, cmd):
    """
    helper function for connection plugins to create sudo commands
    """
    # Rather than detect if sudo wants a password this time, -k makes
    # sudo always ask for a password if one is required.
    # Passing a quoted compound command to sudo (or sudo -s)
    # directly doesn't work, so we shellquote it with pipes.quote()
    # and pass the quoted string to the user's shell.  We loop reading
    # output until we see the randomly-generated sudo prompt set with
    # the -p option.
    randbits = ''.join(chr(random.randint(ord('a'), ord('z'))) for x in xrange(32))
    prompt = '[sudo via ansible, key=%s] password: ' % randbits
    success_key = 'SUDO-SUCCESS-%s' % randbits
    sudocmd = '%s -k && %s %s -S -p "%s" -u %s %s -c %s' % (
        C.DEFAULT_SUDO_EXE, C.DEFAULT_SUDO_EXE, C.DEFAULT_SUDO_FLAGS,
        prompt, sudo_user, executable or '$SHELL', pipes.quote('echo %s; %s' % (success_key, cmd)))
    return ('/bin/sh -c ' + pipes.quote(sudocmd), prompt, success_key)
```

sudocmd を展開するとこんな感じか

```bash:
sudo -k; sudo -S -p “[sudo via ansible, key=randbits]” -u root /bin/bash “echo SUDO-SUCCESS-randbits; cmd”
```

いろいろと使った事が無いsudoのオプションが並んでいる。

http://linuxjm.sourceforge.jp/html/sudo/man8/sudo.8.html

```
-S
-S (stdin) オプションを指定すると、 sudo はパスワードをターミナルデバイスからではなく、 標準入力から読み込む。パスワードは末尾に改行を付けなければならない。

-k [command]
-k (kill) オプションを単独で使うと、 sudo はユーザの保存された認証情報を無効にする。 次回 sudo を実行するとき、パスワードが要求されることになるわけだ。 このオプション自体はパスワードを必要としない。

-p prompt
-p (prompt) オプションを使うと、 デフォルトのパスワードプロンプトを変更して、好きな文句にすることができる。 sudoers ポリシーでは以下のパーセント (` %
') エスケープが使用できる。
```

sudo に-Sを指定すると標準入力からパスワードを受け取れるようです。