Setup
Passwords, not keys are used for ssh
The target is accessed via the bastion host (ssh -> bastion -> target)
The password is kept in an encrypted file that is accessed based on the ansible_user name
When decrypted, the password is bastion
Command
$ ansible -i ./inventory/debug -m debug -a 'var=foo' -kKu ansible all
Command Output
target | FAILED! => {
"msg": "The field 'ssh_common_args' has an invalid value, which includes an undefined variable. The error was: 'ansible_user' is undefined"
}
bastion | SUCCESS => {
"foo": "-o ProxyCommand=\"sshpass -p bastion ssh -o StrictHostKeyChecking=no -W %h:%p -q ansible#3.21.247.xxx.\""
}
Problem
The password is correctly accessed and resolved for foo for bastion, but foo cannot be resolved for the target host.
The inventory file (called "debug")
all:
vars:
env: 3.21.247.xxx
password: "{{lookup('file', inventory_dir + '/../users/' + ansible_user + '.yml')}}"
foo: "-o ProxyCommand=\"sshpass -p {{password}} ssh -o StrictHostKeyChecking=no -W %h:%p -q ansible#{{env}}.\""
children:
bastions:
hosts:
bastion:
ansible_host: "{{ env }}"
nv:
children:
targets:
hosts:
target:
ansible_host: 10.0.3.209
vars:
ansible_ssh_common_args: "{{ foo }}"
You cannot use jinja2 expansion inside jinja2 expansion. Try:
"{{lookup('file', inventory_dir + '/../users/' + ansible_user + '.yml')}}"
Related
The variable {{ansible_fqdn}} can take two different values: the server's short name (server300) or its long name (server300.prod.x.y.z).
When we use this variable in the playbook to retrieve a file {{ansible_fqdn}}.crt, depending on the value chosen, the file can be not found, and the playbook will fail.
To have a consistent value for hostnames over a list of servers, should one rather use {{ansible_hostname}} (short hostname from linux command uname -n) and {{inventory_hostname}} (long hostname from "hosts" file) instead?
Or is there a way to obtain a consistent value from {{ansible_fqdn}}?
Q: "The variable {{ ansible_fqdn }} can take two different values: the server's short name (server300) or its long name (server300.prod.x.y.z)."
A: It depends on how you configure hostname. Take a look at uname -n. Either it is long
[admin#test_23 ~]$ uname -n
test_23.example.com
shell> ansible test_23 -m setup | grep ansible_fqdn
"ansible_fqdn": "test_23.example.com",
, or it is short
[admin#test_11 ~]$ uname -n
test_11
shell> ansible test_11 -m setup | grep ansible_fqdn
"ansible_fqdn": "test_11",
Q: "Should one rather use {{ ansible_hostname }} (short hostname from linux command uname -n) and {{ inventory_hostname }} (long hostname from "hosts" file) instead?"
A: It depends on how you configure the inventory. The variable ansible_hostname is not required. If you don't run setup (or get the variables from a cache) and if you don't declare it explicitly it won't be defined, e.g.
shell> cat hosts
test_23
shell> ansible test_23 -m debug -a var=inventory_hostname
test_23 | SUCCESS => {
"inventory_hostname": "test_23"
}
shell> ansible test_23 -m debug -a var=ansible_hostname
test_23 | SUCCESS => {
"ansible_hostname": "VARIABLE IS NOT DEFINED!"
}
You can declare alias and ansible_hostname, e.g.
shell> cat hosts
alias_of_test_23 ansible_hostname=test_23
shell> ansible alias_of_test_23 -m debug -a var=inventory_hostname
alias_of_test_23 | SUCCESS => {
"inventory_hostname": "alias_of_test_23"
}
shell> ansible alias_of_test_23 -m debug -a var=ansible_hostname
alias_of_test_23 | SUCCESS => {
"ansible_hostname": "test_23"
}
If you run setup the value of ansible_hostname is the short hostname from the command uname -n, e.g.
shell> cat hosts
alias_of_test_23 ansible_host=test_23.example.com
shell> ansible alias_of_test_23 -m setup | grep ansible_hostname
"ansible_hostname": "test_23",
Q: "Or is there a way to obtain a consistent value from {{ ansible_fqdn }}?"
A: There are more options:
At remote hosts, configure hostnames to provide you with the FQDN
In the inventory, declare the FQDN form of aliases and use inventory_hostname
If the options above are not feasible you'll have to declare a custom variable.
The consistency is up to you.
I have a problem when i try to launch ansible with this command:
ansible -i /etc/ansible/hosts -m ping 10.0.0.4 --vault-password-file /etc/ansible/.lorem
outpout:
10.0.0.4 | FAILED! => {
"msg": "The field 'password' has an invalid value, which includes an undefined variable. The error was: 'super_mdp' is undefined"
}
My hosts file:
all:
hosts:
10.0.0.4:
ansible_password: "{{ super_mdp }}"
ansible_shell_type: "sh"
ansible_user: "user"
My vars file:
---
super_mdp: getrooted
I want to construct the password (ansible_ssh_pass) within the vars section of my playbook from a string passed as an input (pass_var). The problem here is that the actual password should be the first 6 characters from the variable (pass_var). I am not sure how to achieve it. Here is my playbook
- hosts: all
user: "{{username}}"
become: yes
gather_facts: False
vars:
ansible_ssh_pass: <someway to decode string (base64 -d) and get first 6 characters>
I'm running the playbook as:
ansible-playbook test.yml --extra-vars "pass_var=${pass_val} username=${user}"
I also need to do some shell manipulations. For example, my string will be base64 encoded, so I need to decode it as well. Something like: echo ${pass_var} | base64 -d | rev | cut -c1-6
You can use Python-style string slicing in Ansible, so you can just write:
vars:
ansible_ssh_pass: "{{ pass_var[:6] }}"
For example, the following command:
ansible localhost -e pass_var=1234567890 -m debug -a 'msg={{pass_var[:6]}}'
Will output:
localhost | SUCCESS => {
"msg": "123456"
}
If your initial string is base64 encoded, you can use Ansible's b64_decode filter:
ansible_pass: "{{ (pass_var|b64decode)[:6] }}"
And if for some weird reason you need to reverse it, there is a reverse filter:
ansible_pass: "{{ ((pass_var|b64decode)|reverse)[:6] }}"
If we modify my earlier example, we get:
ansible localhost -e pass_var="MTIzNDU2Nzg5MA==" -m debug -a 'msg={{((pass_var|b64decode)|reverse)[:6]}}'
Which produces:
localhost | SUCCESS => {
"msg": "098765"
}
I would like to test if a user is able to SSH using SSH password. That's all I would like to do. I tried with modules: local_action, wait_for but those didn't get me the results. The playbook result must simply tell me where a connection succeeded or failed when trying to SSH.
The requirement is to test which user account succeeds in making a SSH connection to remote servers. The user who would be running the ansible script has multiple accounts on these servers but SSH login will succeed with just the right one which the user doesn't know. The user accounts all have the same password.
The inventory file:
all:
children:
FXO-Test:
hosts:
host1.abcd.com:
host2.abcd.com:
vars:
ansible_user: user1
The Playbook:
---
- hosts: "{{ targethosts }}"
gather_facts: no
tasks:
- name: Test connection
local_action: command ssh -q -o BatchMode=yes -o ConnectTimeout=3 {{ inventory_hostname }}
register: test_user
ignore_errors: true
changed_when: false
Invoked Using Command:
ansible-playbook checkLogin.yml -i ans_inventory_test --ask-pass --extra-vars "targethosts=FXO-Test" | tee verify_user.log
Expected to see which SSH connections failed and which ones worked.
Based on Vladimir Botka response, I tweaked the playbook a bit further to pull hostnames from an inventory file.
My Updated Playbook 'verifySSHLogin.yml':
- hosts: localhost
gather_facts: no
vars:
my_users:
- user1
- user2
my_hosts: "{{ query('inventory_hostnames', 'all') }}"
tasks:
- expect:
command: "ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no {{ item.0 }}#{{ item.1 }}"
timeout: 2
responses:
(.*)password(.*):
- "password" # Fit the password
- "\x03" # Ctrl-C
(.*)\$(.*): "exit" # Fit the prompt
loop: "{{ my_users|product(my_hosts)|list }}"
register: result
ignore_errors: yes
- debug:
msg: "{{ (item.rc == 0)|ternary(item.invocation.module_args.command ~ ' [OK]',item.invocation.module_args.command ~ ' [KO]') }}"
loop: "{{ result.results }}"
Which I now invoke using below command:
ansible-playbook verifySSHLogin.yml -i ans_inventory_test --extra-vars "targethosts=FXO-Test" | tee verify_user.log
I can then do a grep against verify_user.log like this:
grep '\"msg\": \"ssh' verify_user.log
Which gives me below result which is what I was expecting:
"msg": "ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no user1#host1.abc.corp.com [OK]"
"msg": "ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no user1#host2.abc.corp.com [OK]"
"msg": "ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no user1#host3.abc.corp.com [KO]"
"msg": "ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no user2#host1.abc.corp.com [KO]"
"msg": "ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no user2#host2.abc.corp.com [KO]"
"msg": "ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no user2#host3.abc.corp.com [KO]"
Tweaked the playbook further to avoid hard-coding of SSH password. The final playbook looks like now:
- hosts: localhost
gather_facts: no
vars:
my_users:
- user1
- user2
my_hosts: "{{ query('inventory_hostnames', 'all') }}"
tasks:
- expect:
command: "ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no {{ item.0 }}#{{ item.1 }}"
timeout: 2
responses:
(.*)password(.*):
- "{{ ansible_password }}" # Fit the password
- "\x03" # Ctrl-C
(.*)\$(.*): "exit" # Fit the prompt
loop: "{{ my_users|product(my_hosts)|list }}"
register: result
ignore_errors: yes
- debug:
msg: "{{ (item.rc == 0)|ternary(item.invocation.module_args.command ~ ' [OK]',item.invocation.module_args.command ~ ' [KO]') }}"
loop: "{{ result.results }}"
The SSH password can be passed to ansible-playbook command like this:
ansible-playbook verifySSHLogin.yml -i ans_inventory_test -k --extra-vars "targethosts=FXO-Test" | tee verify_user.log
expect module shall do the job. Given the
user1#test_01 is able to log in, the play below
- hosts: localhost
vars:
my_users:
- user1
- user2
my_hosts:
- test_01
- test_02
tasks:
- expect:
command: "ssh {{ item.0 }}#{{ item.1 }}"
timeout: 2
responses:
(.*)password(.*):
- "password" # Fit the password
- "\x03" # Ctrl-C
(.*)\$(.*): "exit" # Fit the prompt
with_nested:
- "{{ my_users }}"
- "{{ my_hosts }}"
register: result
ignore_errors: yes
- debug:
msg: "{{ (item.rc == 0)|ternary(item.invocation.module_args.command ~ ' [OK]',
item.invocation.module_args.command ~ ' [KO]') }}"
loop: "{{ result.results }}"
gives (grep msg):
"msg": "ssh user1#test_01 [OK]"
"msg": "ssh user1#test_02 [KO]"
"msg": "ssh user2#test_01 [KO]"
"msg": "ssh user2#test_02 [KO]"
trying to find and add hosts dynamically like so
---
- hosts: localhost
gather_facts: no
tasks:
- name: Gather EC2 remote facts.
ec2_remote_facts:
region: 'us-east-1'
register: ec2_remote_facts
- name: Debug.
debug:
msg: "{{ ec2_remote_facts }}"
- name: get instances for tags
add_host:
name: "{{ item }}"
group: dynamically_created_hosts
with_items: |
"{{ ec2_remote_facts.instances |
selectattr('tags.AppName', 'defined') | selectattr('tags.AppName', 'equalto', 'sql') |
selectattr('tags.AppType', 'defined') | selectattr('tags.AppType', 'equalto', 'infra') |
map(attribute='private_ip_address') | list }}"
- hosts:
- dynamically_created_hosts
become: yes
become_user: root
serial: 1
vars_files:
- group_vars/all
tasks:
- name: run command
shell: "uname -a"
I get following when i run in verbose mode
TASK [get instances for tags] **************************************************
task path: /Users/me/gitfork2/fornax/dynhst.yml:39
creating host via 'add_host': hostname="[u'10.112.114.241']"
changed: [localhost] => (item="[u'10.112.114.241']") => {"add_host": {"groups": ["dynamically_created_hosts"], "host_name": "\"[u'10.112.114.241']\"", "host_vars": {"group": "dynamically_created_hosts"}}, "changed": true, "invocation": {"module_args": {"group": "dynamically_created_hosts", "hostname": "\"[u'10.112.114.241']\""}, "module_name": "add_host"}, "item": "\"[u'10.112.114.241']\""}
PLAY [dynamically_created_hosts] ***********************************************
TASK [setup] *******************************************************************
<"[u'10.112.114.241']"> ESTABLISH SSH CONNECTION FOR USER: None
<"[u'10.112.114.241']"> SSH: ansible.cfg set ssh_args: (-F)(/Users/me/.ssh/config)
<"[u'10.112.114.241']"> SSH: ANSIBLE_HOST_KEY_CHECKING/host_key_checking disabled: (-o)(StrictHostKeyChecking=no)
<"[u'10.112.114.241']"> SSH: ansible_password/ansible_ssh_pass not set: (-o)(KbdInteractiveAuthentication=no)(-o)(PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey)(-o)(PasswordAuthentication=no)
<"[u'10.112.114.241']"> SSH: ANSIBLE_TIMEOUT/timeout set: (-o)(ConnectTimeout=10)
<"[u'10.112.114.241']"> SSH: PlayContext set ssh_common_args: ()
<"[u'10.112.114.241']"> SSH: PlayContext set ssh_extra_args: ()
<"[u'10.112.114.241']"> SSH: EXEC ssh -C -vvv -F /Users/me/.ssh/config -o StrictHostKeyChecking=no -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 '"[u'"'"'10.112.114.241'"'"']"' '/bin/sh -c '"'"'( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1509843772.58-176166317659656 `" && echo ansible-tmp-1509843772.58-176166317659656="` echo $HOME/.ansible/tmp/ansible-tmp-1509843772.58-176166317659656 `" ) && sleep 0'"'"''
fatal: ["[u'10.112.114.241']"]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh.", "unreachable": true}
to retry, use: --limit #./dynhst.retry
The odd thing here I see is
SSH: EXEC ssh -C -vvv -F /Users/me/.ssh/config -o StrictHostKeyChecking=no -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 '"[u'"'"'10.112.114.241'"'"']"' '/bin/sh -c '"'"'( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1509843772.58-176166317659656 `" && echo ansible-tmp-1509843772.58-176166317659656="` echo $HOME/.ansible/tmp/ansible-tmp-1509843772.58-176166317659656 `" ) && sleep 0'"'"''
Seems like it is trying to ssh into '"[u'"'"'10.112.114.241'"'"']"' ... seems like the dynamically_created_hosts is being used as a string and not as a list
Any ideas why?
You pass a list (of IP addresses) to an argument name which requires a string:
hostname="[u'10.112.114.241']"
[ ] is a JSON representation of a list (single element in the example above).
If you want the first address from the list (and there seems to be no more for any of your hosts), then:
add_host:
name: "{{ item[0] }}"
group: dynamically_created_hosts
with_items: ...