Ansible change connection from winrm to ssh between task - ansible

I have had an issue with connecting to local_action when running my playbook, my playbook is used to create users in SQL Server and need to run local action to get generate random password
fatal: [w961412]: UNREACHABLE! => {"changed": false, "msg": "ntlm: HTTPConnectionPool(host='localhost', port=5985): Max retries exceeded with url: /wsman (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f4572858790>: Failed to establish a new connection: [Errno 111] Connection refused',))", "unreachable": true}
task for ssh
- name: get random password
command: tr -dc 'A-HJ-NP-Za-km-z2-9' < /dev/urandom | dd bs=12 count=1 status=none
register: secret
tasks for winrm:
- name: Alter database credentials.
win_shell: |
sqlcmd -S {{ sql_hostname }},{{ SQL_PORT }} -E -q "alter login {{ dbuser }} with password=N'{{ secret.password }}'" -o alter.log
register: alter_result

Foremost, what you said is actually correct: you want a local action; I don't think it needs to connect back to your control host over ssh just to generate a password
So, I would expect you could use:
- name: get a random password
connection: local
shell: tr -dc 'A-HJ-NP-Za-km-z2-9' < /dev/urandom | dd bs=12 count=1 status=none
register: secret
- win_shell: |
echo "and now you are back to the normal playbook connection"
Your code snippet also had a bug in it by trying to use command: with a string containing a pipe -- shell operators are not supported by command:, that's why shell: exists
Then, separately, you don't have to use a bunch of shell commands, along with some magic tr string literal: ansible has a random password lookup such that you can:
- win_shell: |
sqlcmd -q "alter login with password=N'{{ item }}'"
register: alter_result
with_password: /dev/null length=12

Related

Ansible Builtin Shell: how can I get a table output instead of "\t" delimiters?

While checking a port range on my nodes with following playbook extract,
- name: bash commands
ansible.builtin.shell: |
grep -E '\b(500[0-9]|50[1-9][0-9]|5[1-4][0-9]{2}|5500)' \
/etc/services | sort -k 2
args:
chdir: /root
executable: /bin/bash
async: 2000
poll: 3
register: output
- debug: msg="{{ output.stdout_lines }}"
- debug: msg="{{ output.stderr_lines }}"
I am getting below sample output, where everything is concatenated with \t delimiters:
ok: [sea_r] => {
"msg": [
"enbd-cstatd\t5051/tcp\t\t\t# ENBD client statd",
"enbd-sstatd\t5052/tcp\t\t\t# ENBD server statd",
"sip\t\t5060/tcp\t\t\t# Session Initiation Protocol",
"sip\t\t5060/udp",
"sip-tls\t\t5061/tcp",
"sip-tls\t\t5061/udp",
"pcrd\t\t5151/tcp\t\t\t# PCR-1000 Daemon",
"xmpp-client\t5222/tcp\tjabber-client\t# Jabber Client Connection",
"xmpp-server\t5269/tcp\tjabber-server\t# Jabber Server Connection",
"cfengine\t5308/tcp",
"mdns\t\t5353/udp\t\t\t# Multicast DNS",
"noclog\t\t5354/tcp\t\t\t# noclogd with TCP (nocol)",
"noclog\t\t5354/udp\t\t\t# noclogd with UDP (nocol)",
"hostmon\t\t5355/tcp\t\t\t# hostmon uses TCP (nocol)",
"hostmon\t\t5355/udp\t\t\t# hostmon uses UDP (nocol)",
"postgresql\t5432/tcp\tpostgres\t# PostgreSQL Database"
]
}
But running the same thing with below bash script,
for r in "${IPS[#]}"; do
ssh -tt root#"$r" "
grep -E --color=always '\b(500[0-9]|50[1-9][0-9]|5[1-4][0-9]{2}|5500)' \
/etc/services | sort -k 2
echo -e "continue to next node"; read
"
done
it outputs the following nice table:
Is it possible to get such a table output with the playbook instead of those \t delimiters?
To allow Ansible to display values like new lines (\n) and tabs (\t), you can use the debug callback.
This can be done, either modifying the ansible.cfg, if you want to apply it on your whole Ansible installation, e.g.
[defaults]
stdout_callback = debug
Or, invoking ansible-playbook this way:
ANSIBLE_STDOUT_CALLBACK=debug ansible-playbook play.yaml
For example; given the task:
- debug:
msg: "some\tfields\tin\ttable\nfoo\tbar\tbaz\tqux"
When run with the debug callback, this yields:
TASK [debug] ***************************************************************
ok: [node1] => {}
MSG:
some fields in table
foo bar baz qux
In your specific case, what you can, then, do, is:
- debug:
msg: "{{ output.stdout_lines | join('\n') }}"
Which would yield, with the usage of the debug callback:
TASK [debug] ***************************************************************
ok: [node1] => {}
MSG:
enbd-cstatd 5051/tcp # ENBD client statd
enbd-sstatd 5052/tcp # ENBD server statd
sip 5060/tcp # Session Initiation Protocol
sip 5060/udp
sip-tls 5061/tcp
sip-tls 5061/udp
pcrd 5151/tcp # PCR-1000 Daemon
xmpp-client 5222/tcp jabber-client # Jabber Client Connection
xmpp-server 5269/tcp jabber-server # Jabber Server Connection
cfengine 5308/tcp
mdns 5353/udp # Multicast DNS
noclog 5354/tcp # noclogd with TCP (nocol)
noclog 5354/udp # noclogd with UDP (nocol)
hostmon 5355/tcp # hostmon uses TCP (nocol)
hostmon 5355/udp # hostmon uses UDP (nocol)
postgresql 5432/tcp postgres # PostgreSQL Database

Ansible variable manipulation within vars

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"
}

Ansible | Variable declaration to bash script

I have a bash script under files which needs a variable to get passed from default\main1.yml. How can I declare the same?
roles
config
defaults
main1.yml
files
script.sh
tasks
main2.yml
defaults\main1.yml
log_group_name: "{{ lookup('env','LOG_GROUP') }}"
script.sh
for NAME in $(ls -1p /home/ec2-user/ |grep -v "^_" | grep -v "/$" |cut -d. -f1);
do
if ! grep $NAME /etc/awslogs/awslogs.conf;
then
sudo tee -a /etc/awslogs/awslogs.conf << END
[$DAGNAME]
datetime_format = %b %d %H:%M:%S
file = /var/log/airflow/$NAME/*/*/*.log
buffer_duration = 5000
log_stream_name = $NAME
initial_position = start_of_file
log_group_name = ${log_group_name}
END
fi
done
sudo service awslogsd start
sudo systemctl enable awslogsd
task/cloud.yml
---
- name: 'Cloud | AWSlogs.conf file'
when: inventory_hostname == 'master'
script: ../files/cloud.sh
- name: 'Cloud | AWSlogs.conf file'
when: inventory_hostname == 'worker_1'
script: ../files/cloud.sh
ansible command to execute the play:- deploy.py
execute(['sudo', '-E', 'ansible-playbook', 'ansible/plays/deploy.yml', '-i', 'hosts.yml'],
stdout=sys.stdout, stderr=sys.stderr, env=aug_env)
See if this works -
- hosts: localhost
tasks:
- shell: "script.sh {{ log_group_name }}"
You need to set the variable within the execution environment of the script, but it clearly already exists since you're pulling it out of the ENV so you don't even need to do that. If you did, you could install the script via the Ansible template module and execute the script that way.
https://docs.ansible.com/ansible/latest/user_guide/playbooks_environment.html
Simply use the script's parameter
script.sh
log_group_name=$1
...
and run the script with the parameter
roles/config/tasks/main2.yml
- command: "{{ role_path }}/files/script.sh {{ log_group_name }}"
You probably know, but just to be sure. This will work with the "localhost" only. In other cases, the script must be copied to the "remote hosts" first and the path to the script must be changed.

Ansible User module - how to tell it to remove expired user accounts?

I am setting up user administration through Ansible for the first time. Can I use Ansible's user module to remove accounts that are past their expiration date? What conditional statement would I use?
Pardon my untested pseudocode, but I am looking for something like the following:
tasks:
- name: remove expired users
user: name=users.key state=absent force=yes
when: expired <----- what condition do I put here?
with_dict: users
You can use the shell module to get back the list of users on each host that are expired (as in useradd -e $expire_time) and then pass that to the user module.
As an example we can set up some users that expire around now:
sudo useradd testexpires -e 2015-09-24
sudo useradd testexpires2 -e 2015-09-22
sudo useradd testexpires3 -e 2015-09-21
sudo useradd testexpires4 -e 2015-09-28
sudo useradd testexpires5 -e 2015-09-21
sudo cat /etc/shadow then shows:
...
testexpires:!:16701:0:99999:7::16702:
testexpires2:!:16701:0:99999:7::16700:
testexpires3:!:16701:0:99999:7::16699:
testexpires4:!:16701:0:99999:7::16706:
testexpires5:!:16701:0:99999:7::16699:
We can then check whether that epoch date in the 8th column is older than today by using this reasonably horribly shell one liner:
sudo cat /etc/shadow | cut -d: -f1,8 | awk -F: '{if($2<{{ epoch_day }} && $2 != ""){print $0}}' | cut -d: -f1
We can easily get the epoch date using Ansible's built in ansible_date_time variable which gives us the epoch time in seconds and dividing through using Jinja's math filters:
epoch_day : "{{ ansible_date_time.epoch | int / 86400 | round() }}"
Putting this together (and escaping out the quotes in the awk) gives us a playbook that if you wanted to run it on localhost would look something like this:
- hosts : localhost
connection : local
gather_facts : yes
vars :
epoch_day : "{{ ansible_date_time.epoch | int / 86400 | round() }}"
tasks :
- name : debug epoch day
debug : var=epoch_day
- name : get users expired before today
shell : "cat /etc/shadow | cut -d: -f1,8 | awk -F: '{if($2<{{ epoch_day }} && $2 != \"\"){print $0}}' | cut -d: -f1"
changed_when : False
register : expired_users
- name : debug expired_users
debug : var=expired_users.stdout_lines
- name : remove expired users
user :
name : "{{ item }}"
state : absent
force : yes
with_items : expired_users.stdout_lines
Running this playbook when you don't have any expired users will make Ansible skip the last task because you don't have any items to pass to the task.
I simply maintain two lists of users: "current" and "former". Don't delete a user, move it from one list to the other.
tasks:
- name: ensure users
user: name=item.key state=present force=yes
with_dict: current_users
tasks:
- name: remove expired users
user: name=item.key state=absent force=yes
with_dict: former_users
If you wish to search for user accounts you'll need to script it, remove system accounts, etc.

Ansible mysql_user priv - "invalid privileges string: Invalid privileges specified"

I'm using the mysql_user module to attempt to add a user and privileges. I'm getting an "invalid privileges string" error on the following task.
- name: Add api user
mysql_user:
name="user_name"
host="{{ item }}"
password={{ mysql_password }}
priv={{ mysql_project_database }}.console_users:"SELECT (col_one, col_two)"
with_items:
- "%"
tags:
- mysql-user
I'm running the task from the command line like so:
ansible-playbook playbook.yml -i inventory/develop -vv --tags=mysql-user
Which results in the following error:
failed: [192.168.37.100] => (item=%) => {"failed": true, "item": "%"}
msg: invalid privileges string: Invalid privileges specified: frozenset(['SELECT (COL_ONE', ' COL_TWO)'])
I'm not 100% sure, but I think the issue is related to the space in the privilege. I can't seem to escape it or solve it with quotes (I've seen that suggested a few places).
I have tried:
Only granting SELECT - works
Putting quotes around the entire privilege - fails
Escaping the space with a \ - fails
I'm currently running ansible 1.8.4 installed via Homebrew on Mac OSX 10.10.2.
The playbook is provisioning an Ubuntu 14.04 box and MySQL 5.5.41-0ubuntu0.14.04.1 (Ubuntu)
Surely I'm missing something obvious. Any advice?
UPDATE
Debug output:
TASK: [console | Add api user] ************************************************
<192.168.37.100>
<192.168.37.100> host=% password=VALUE_HIDDEN name=user_name priv=db_name.console_users:"SELECT (col_one, col_two)"
<192.168.37.100> IdentityFile=/Users/jeremykendall/.ssh/id_rsa ConnectTimeout=10 PasswordAuthentication=no KbdInteractiveAuthentication=no User=deploy ForwardAgent=yes PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey StrictHostKeyChecking=no
<192.168.37.100>
<192.168.37.100> IdentityFile=/Users/jeremykendall/.ssh/id_rsa ConnectTimeout=10 'sudo -k && sudo -H -S -p "[sudo via ansible, key=sxqmfmrnzwqhgohgejmdydblzjczuvyr] password: " -u root /bin/sh -c '"'"'echo SUDO-SUCCESS-sxqmfmrnzwqhgohgejmdydblzjczuvyr; LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/deploy/.ansible/tmp/ansible-tmp-1425679033.3-193113161035706/mysql_user; rm -rf /home/deploy/.ansible/tmp/ansible-tmp-1425679033.3-193113161035706/ >/dev/null 2>&1'"'"'' PasswordAuthentication=no KbdInteractiveAuthentication=no User=deploy ForwardAgent=yes PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey StrictHostKeyChecking=no
failed: [192.168.37.100] => (item=%) => {"failed": true, "item": "%"}
msg: invalid privileges string: Invalid privileges specified: frozenset(['SELECT (COL_ONE', ' COL_TWO)'])
FATAL: all hosts have already failed -- aborting
This is currently an open issue in the Ansible MySQL module.
It started after the module began validating the privileges against a hardcoded list in the module. The validation is done by checking whether the list of new privileges is a subset of the list of valid privileges.
Of course, SELECT, UPDATE, etc. are in the list. However SELECT(columnname) obviously is not. The validation logic is simply not smart enough.
This explains why it worked before and now no longer does.
You can find the issue report including a fix here: https://github.com/ansible/ansible-modules-core/issues/1120
UPDATE 30.06.: fix has been committed to the Ansible repository and should land in the next stable release: https://github.com/ansible/ansible-modules-core/commit/4ee18957dc95db427fec5a0cd66b6257b549213b
I've got something like this in my playbooks working OK:
priv=db1.table_name1:SELECT,UPDATE(visible,refid,user_signature)/db2.table_name2:SELECT,INSERT
any good? So for yours:
priv={{ mysql_project_database }}.console_users:SELECT(col_one,col_two)
It's work for me.
# cat stackoverflow_28908155.yml
---
- hosts: localhost
user: ansible
tasks:
- name: Add api user
mysql_user: >
name="combo"
host="{{ item }}"
password="bogFanJoadFojBacUlvimudFilpUrcac"
priv=dashboard.metrics:"SELECT (report_id, category)"
with_items:
- "%"
tags:
- mysql-user
# mysql -e 'desc dashboard.metrics'
+-----------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| report_id | int(11) | NO | MUL | NULL | |
| category | varchar(255) | YES | | NULL | |
| name | varchar(255) | YES | | NULL | |
| value | decimal(12,6) | YES | | NULL | |
+-----------+---------------+------+-----+---------+----------------+
# ansible-playbook stackoverflow_28908155.yml
PLAY [localhost] **************************************************************
GATHERING FACTS ***************************************************************
ok: [localhost]
TASK: [Add api user] **********************************************************
changed: [localhost] => (item=%)
PLAY RECAP ********************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0
# mysql -Bse 'select Host,User,Password from mysql.user where User="combo";'
% combo *5E916E80BFCBBEEDFEB614DEF49616F7F124D6EE
Looks like handling of spaces might have changed recently. My run of mysql_user started failing in a recent ansible version because of ", SUPER"; fixed by changing to ",SUPER".

Resources