Repository: ansible/ansible

Issue:
SSH publickey and password combination not allowed
If you use `--ask-pass` with ansible-playbook, Ansible appears to try and be clever by assuming that you do not want to use publickey authentication, and adds `-o PubkeyAuthentication=no` to the ssh command line.

At best, this is misleading, at worst it is wrong and prevents specific edge use cases.

It is perfectly valid for an SSH server to REQUIRE that the client provide a publickey AND a password, or more commonly, that the client provide a publickey AND a 2-factor OTP via keyboard-interactive.

Consider the sshd_config configuration:
`AuthenticationMethods publickey,password`

And ssh_config:
`PreferredAuthentications=publickey,password`

At present, the above SSH configuration is not supported by Ansible.

```
<test1> ESTABLISH CONNECTION FOR USER: testuser
<test1> REMOTE_MODULE setup
<test1> EXEC ['sshpass', '-d4', 'ssh', '-C', '-tt', '-vvv', '-o', 'PreferredAuthentications=publickey,password', '-o', 'Port=22', '-o', 'GSSAPIAuthentication=no', '-o', 'PubkeyAuthentication=no', '-o', 'ConnectTimeout=10', 'test1', "/bin/sh -c 'mkdir -p $HOME/.ansible/tmp/ansible-tmp-1454369482.89-90417182374711 && chmod a+rx $HOME/.ansible/tmp/ansible-tmp-1454369482.89-90417182374711 && echo $HOME/.ansible/tmp/ansible-tmp-1454369482.89-90417182374711'"]
```



Discussion:
TBH I don't think the sshd_config and ssh_config options mean there is an AND relationship if you specify more than one option. (e.g. the default has 5 methods listed)

```
     PreferredAuthentications
             Specifies the order in which the client should try protocol 2
             authentication methods.  This allows a client to prefer one method
             (e.g. keyboard-interactive) over another method (e.g. password) The
             default for this option is: “gssapi-with-mic, hostbased, publickey,
             keyboard-interactive, password”.
```

It only specifies what methods are allowed (and in what order), and any one will do for a given system. (Except of course if PAM is being used and configured differently)

That said, I can see that if you have multiple systems and some require a password, while others require key authentication. In that case `--ask-pass` may be intended for some of your systems only. But you could also make a case that each system requires a different password which Ansible does not allow today either. (A different key for each individual system is allowed however)

So it is safe to assume that Ansible expects one authentication method for all your targets, by design.

The `sshd_config` option allows for requiring multiple auth types.

My mention of `ssh_config` probably muddied the water a little; it was merely intended to illustrate that I was explicitly ensuring that I was asking the ssh client used by Ansible to explicitly support the authentication methods required by the SSH server.

publickey and password auth are not mutually exclusive, so setting `PubkeyAuthentication=no` when `--ask-pass` seems wrong.

`RequiredAuthentications2`, `RequiredAuthentications1` and `AuthenticationMethods` (depending on OpenSSH server version) allow for more than one authentication method to be satisfied as a login requirement.

```
     AuthenticationMethods
             Specifies the authentication methods that must be successfully completed for a user
             to be granted access.  This option must be followed by one or more comma-separated
             lists of authentication method names.  Successful authentication requires completion
             of every method in at least one of these lists.

             For example, an argument of ``publickey,password publickey,keyboard-interactive''
             would require the user to complete public key authentication, followed by either
             password or keyboard interactive authentication.  Only methods that are next in one
             or more lists are offered at each stage, so for this example, it would not be possi-
             ble to attempt password or keyboard-interactive authentication before public key.
```

So what exactly is not working and what are we trying to solve for?

What specific configuration do you currently have that this does not satisfy?

I need to check this, but IIRC this was done because of waiting for prompts, if the ssh keys open the connection anisble could  just wait for the login prompt indefinitely. 

I want to be able to use publickey authentication in combination with password authentication with ssh. 

To enable password authentication with ansible-playbook I am using `--ask-pass`, but it appears that using the `--ask-pass` option causes Ansible to inject `-o PubkeyAuthentication=no` in to the ssh client command line.

If there is another way that I can get Ansible to support ssh password authentication that doesn't inject `-o PubkeyAuthentication=no` in to the ssh client command line, then I will happily do that. Please educate me if there is another way.

I may just not be following, so bare with me.  Do you have a system that allows SSH key auth, but that also prompts for an SSH password?  Is this a form of 2fa?  I don't know that I have ever seen a system which allows for publickey but also performs SSH password prompting.

Or are you trying to achieve a goal where you aren't sure if a host is password or publickey?

Just trying to understand the scenario so that I am sure what is being solved for.

Can you describe the log in process for said system?  And potentially what pieces of software causes this behavior or what system/ssh configuration is responsible for this?

@neechbear I think I understand what you are looking for (although I am not convinced that it's the best use-case ;-)), but I don't see why we deliberately would disable key-authentication when using `--ask-pass`.

And in fact what you ask for looks rather trivial to implement. However looking at the Ansible v2.0.1 source code, I don't see this happening. The only thing I see happen is an explicit disable of password authentication when no password is provided (which is there to avoid password prompting).

https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/connection/ssh.py#L187

``` python
        if not self._play_context.password:
            self._add_args(
                "ansible_password/ansible_ssh_pass not set", (
                    "-o", "KbdInteractiveAuthentication=no",
                    "-o", "PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey",
                    "-o", "PasswordAuthentication=no"                                                                                                       
                )
            )
```

Did you try this with Ansible v2 ?

I can confirm that the behaviour you mentioned is part of Ansible v1.9 codebase (and likely prior versions):

https://github.com/ansible/ansible/blob/stable-1.9/lib/ansible/runner/connection_plugins/ssh.py#L94

``` python
        if self.password:
            self.common_args += ["-o", "GSSAPIAuthentication=no",
                                 "-o", "PubkeyAuthentication=no"]
        else:
            self.common_args += ["-o", "KbdInteractiveAuthentication=no",
                                 "-o", "PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey",
                                 "-o", "PasswordAuthentication=no"]
```

I assume that this can be closed ?


Resolving patch:
diff --git a/lib/ansible/runner/connection_plugins/ssh.py b/lib/ansible/runner/connection_plugins/ssh.py
--- a/lib/ansible/runner/connection_plugins/ssh.py
+++ b/lib/ansible/runner/connection_plugins/ssh.py
@@ -91,10 +91,7 @@ def connect(self):
             self.common_args += ["-o", "IdentityFile=\"%s\"" % os.path.expanduser(self.private_key_file)]
         elif self.runner.private_key_file is not None:
             self.common_args += ["-o", "IdentityFile=\"%s\"" % os.path.expanduser(self.runner.private_key_file)]
-        if self.password:
-            self.common_args += ["-o", "GSSAPIAuthentication=no",
-                                 "-o", "PubkeyAuthentication=no"]
-        else:
+        if not self.password:
             self.common_args += ["-o", "KbdInteractiveAuthentication=no",
                                  "-o", "PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey",
                                  "-o", "PasswordAuthentication=no"]


Test patch: