Repository: ansible/ansible

Issue:
.ssh/config User is not overrides user on ansible hosts file
##### Issue Type:

Bug Report
##### Ansible Version:

ansible 1.8 (devel c610783f90)
##### Environment:

Ubuntu 12.04 
##### Summary:

.ssh/config User overrides user on ansible hosts file
##### Steps To Reproduce:

```
echo "User invalid_user" > ~/.ssh/config
echo "vm ansible_ssh_host=vm ansible_ssh_user=mtoledo" > hosts_file
ansible -i hosts_file -m shell -a whoami 
```
##### Expected Results:

Ansible should ssh into the host as 'mtoledo' and print 'mtoledo'
##### Actual Results:

If the user on ssh hosts doesn't exist or the user's password isn't identical to the hosts file user's password:

```
ansible all -m ping -i hosts
vm | FAILED => Authentication failure.
```

If the user does exist on the other host:

```
ansible all  -i hosts -m shell -a whoami
vm | success | rc=0 >>
invalid_user
```



Discussion:
So the problem is on https://github.com/ansible/ansible/blob/devel/lib/ansible/runner/connection_plugins/ssh.py#L97 

It seems that ansible only add ansible_ssh_user when it is different from the current user. In turn this is linked to #8216 and #7170 so while thi s is easy to fix, I think this requires to decide what behavior we want. 

@mscherer Thanks for linking to the gremlin. This caused a bit of pain for my because I have something like:

```
Host *.mycompany
    User testuser
```

in my `~/.ssh/config` so that I SSH to work machines as `testuser` by default. However, I really want my playbooks to run as `me`, not `testuser`. The existence of that line means I can't explicitly say "Ansible, please run this playbook as me on this set of machines".

I wish there were enough state to make that test look like:

```
if explicitly_set_user() or self.user != pwd.getpwuid(os.geteuid())[0]: ...
```

This is set that way because other users had the opposite need, that when using their own user ansible should read and use the .ssh/config settings.

I can understand that for the default, that is, if no `--user` is given. But I was very surprised that it was ignoring my explicit `--user $(whoami)` argument.

it is not ignoring it, it just happens to match your current user and then
load that .ssh/config, which overrides it.

## 

Brian Coca

Hrm please help me understand as I think there's some confusion here. From what I read from #8216 and #7170, the problem was ansible wasn't reading .ssh/config when the playbook didn't setup a default user. In those cases, I agree .ssh/config should be used and whichever user is setup on it.

On @bcoca 's #8216 description, he says "it reverts an old patch of mine that enables .ssh/config when not setting remote user in ansible". I think our current problem is it's using .ssh/config even when setting the remote user in ansible.

In short, no user in ansible should use .ssh/config, but if the playbook is explicit about a user, then using .ssh/config feels really counterintuitive for me.

Oh, I suddenly understand what I'm running into.  I've been fighting this off and on for weeks, I think.

Rule of Least Surprise:  If I specify a user, it should use that user.

I fully appreciate the role of .ssh/config for user-level overrides, but I can't see that it should dominate a specific request in a playbook to use a specific user.  Since Ansible's chief benefit is "keeping things consistent across all of the machines", it's a bad idea to dilute that value by making random configs in random places override the consistency driven by Ansible.

What @mtoledo and @jvantuyl said. I absolutely see the value in using .ssh/config by default, but need explicit declarations in a playbook or via `--user` to be respected.

So the solution seems to be (as per Kirk):

if explicitly_set_user() ...

except we currently have no way to distinguish between explicit and
implicit settings, that would have to be added.

## 

Brian Coca

I apologize for asking a newbie question without looking first, so please forgive; my intent is to make the answer searchable by future seekers.

Is there any kind of a global context object that gets passed all the way down through modules so that an interested hacker could add something like `context_obj['configuration']['explicit_user'] = True` and have plugins and modules read that flag?

Ah, this explains the wall I've been head-butting for the last hour or so :-)

I've got my .ssh/config file being used for general ssh connectivity but have been using a user called "ansible" for actual ansible configuration and management.

Basically, I've set:
remote_user = ansible in ansible.cfg
and: user = bob in .ssh/config
IE:
Host example
        Hostname example.com
        User bob

And the user that all ansible operations are done from is called ansible.

Because the user I'm doing it from is also ansible, it's just ignoring that and using whatever happens to be in .ssh/config - if I set it to remote_user = ansiblee it'll use that, but if I set it to ansible, it uses bob.

Yeah, I'm going to go with if a username is set in ansible.cfg or a playbook, use what's set there, otherwise use whatever's in .ssh/config. Don't check to see if there's a match against the currently logged-in user, and if there is ignore it. I'd been using this for a few weeks with pre-existing machines, but when I tried to setup a new machine completely from scratch it was more than a touch confusing :-)

Edit: I apologise if this is a touch incoherent at times (It looks all right, but at present I'm no judge) and if this reiterates what's been said. It's 7:20 and I'm a touch out of it.

Has there been any movement on this in the context of Ansible 2.0?

I just started testing Ansible and I ran into this issue. It seems there's no way to "force" ansible to set the SSH user if it equals the current username?

(A little hack I'm using at the moment is to add a space to the username, e.g. `ansible_ssh_user=\ me` or `-u ' me'`. Then it gets set.)


Resolving patch:
diff --git a/lib/ansible/constants.py b/lib/ansible/constants.py
--- a/lib/ansible/constants.py
+++ b/lib/ansible/constants.py
@@ -20,7 +20,6 @@
 __metaclass__ = type
 
 import os
-import pwd
 from string import ascii_letters, digits
 
 from ansible.compat.six import string_types
@@ -108,8 +107,6 @@ def load_config_file():
 
 p, CONFIG_FILE = load_config_file()
 
-active_user   = pwd.getpwuid(os.geteuid())[0]
-
 # check all of these extensions when looking for yaml files for things like
 # group variables -- really anything we can load
 YAML_FILENAME_EXTENSIONS = [ "", ".yml", ".yaml", ".json" ]
@@ -138,7 +135,7 @@ def load_config_file():
 DEFAULT_MODULE_LANG       = get_config(p, DEFAULTS, 'module_lang',      'ANSIBLE_MODULE_LANG',      'en_US.UTF-8')
 DEFAULT_TIMEOUT           = get_config(p, DEFAULTS, 'timeout',          'ANSIBLE_TIMEOUT',          10, integer=True)
 DEFAULT_POLL_INTERVAL     = get_config(p, DEFAULTS, 'poll_interval',    'ANSIBLE_POLL_INTERVAL',    15, integer=True)
-DEFAULT_REMOTE_USER       = get_config(p, DEFAULTS, 'remote_user',      'ANSIBLE_REMOTE_USER',      active_user)
+DEFAULT_REMOTE_USER       = get_config(p, DEFAULTS, 'remote_user',      'ANSIBLE_REMOTE_USER',      None)
 DEFAULT_ASK_PASS          = get_config(p, DEFAULTS, 'ask_pass',  'ANSIBLE_ASK_PASS',    False, boolean=True)
 DEFAULT_PRIVATE_KEY_FILE  = get_config(p, DEFAULTS, 'private_key_file', 'ANSIBLE_PRIVATE_KEY_FILE', None, ispath=True)
 DEFAULT_REMOTE_PORT       = get_config(p, DEFAULTS, 'remote_port',      'ANSIBLE_REMOTE_PORT',      None, integer=True)
diff --git a/lib/ansible/plugins/connection/ssh.py b/lib/ansible/plugins/connection/ssh.py
--- a/lib/ansible/plugins/connection/ssh.py
+++ b/lib/ansible/plugins/connection/ssh.py
@@ -23,7 +23,6 @@
 import os
 import pipes
 import pty
-import pwd
 import select
 import shlex
 import subprocess
@@ -188,7 +187,7 @@ def _build_command(self, binary, *other_args):
             )
 
         user = self._play_context.remote_user
-        if user and user != pwd.getpwuid(os.geteuid())[0]:
+        if user:
             self._add_args(
                 "ANSIBLE_REMOTE_USER/remote_user/ansible_user/user/-u set",
                 ("-o", "User={0}".format(self._play_context.remote_user))


Test patch:
diff --git a/test/units/playbook/test_play_context.py b/test/units/playbook/test_play_context.py
--- a/test/units/playbook/test_play_context.py
+++ b/test/units/playbook/test_play_context.py
@@ -19,7 +19,6 @@
 from __future__ import (absolute_import, division, print_function)
 __metaclass__ = type
 
-import pwd
 import os
 
 from ansible.compat.tests import unittest
@@ -55,7 +54,7 @@ def test_play_context(self):
         play_context = PlayContext(options=options)
         self.assertEqual(play_context.connection, 'smart')
         self.assertEqual(play_context.remote_addr, None)
-        self.assertEqual(play_context.remote_user, pwd.getpwuid(os.geteuid())[0])
+        self.assertEqual(play_context.remote_user, None)
         self.assertEqual(play_context.password, '')
         self.assertEqual(play_context.port, None)
         self.assertEqual(play_context.private_key_file, C.DEFAULT_PRIVATE_KEY_FILE)