Expect script to run Ansible playbooks - ssh

I have written an Ansible playbook which prompts me to enter password interactively for SSH and SUDO like below.
$ ansible-playbook -i test --limit dev app_name.yml -vv --tags=stop
SSH password:
SUDO password[defaults to SSH password]:
There are various options available with Ansible like defining password in ansible_ssh_password under group_vars but it don't seem to work for me since I can't have sshpass installed in my target server nor I am allowed to make any changes to my sudoers file.
I tried to execute ansible-playbook from a little expect script below
#!/usr/bin/expect -f
set password PASSWORD
set where_to_execute [lindex $argv 0]
set which_app_to_execute [lindex $argv 1]
set what_to_execute [lindex $argv 2]
send "ansible-playbook -i test --limit $where_to_execute $which_app_to_execute -vv --tags=$what_to_execute \r"
expect "SSH password:"
send "$password \r"
expect "SUDO password*"
send "$password \r"
expect "$"
send "exit \r"
Unfortunately this is also not working may be because SSH process is not spawned by expect. Did anyone try this method and got things working. Please suggest. Thanks.

The problem with your expect scripts is that you aren't actually running the ansible command there (or any command for that matter).
You use
send "ansible-playbook -i test --limit $where_to_execute $which_app_to_execute -vv --tags=$what_to_execute \r"
which sends that string to ... nowhere as far as I know. There's nowhere for it to go.
What you want to be doing is spawning that ansible command and then using expect to communicate with it.
Something like this:
spawn ansible-playbook -i test --limit $where_to_execute $which_app_to_execute -vv --tags=$what_to_execute
You may also want to set the timeout value if the ansible command can take a little while (to prevent expect from killing it when it doesn't return quickly enough).

It works for me using the python implementation of expect. pexpect
install pexpect using pip: pip install pexpect
You can use this code as an workaround for your expect script:
#!/usr/bin/python
import pexpect
def main(args):
#Setup variables
password, where, which, what = args
cmd = "ansible-playbook -i test --limit %s %s -vv --tags=%s" % (where, which, what)
child = pexpect.spawn(cmd)
child.sendline(password)
child.expect('SSH password:')
child.sendline(password)
child.expect('SUDO password*')
child.expect(pexpect.EOF)
print child.before
if __name__ == '__main__':
main(sys.argv[1:])
This is the most simple example but it's working fine for me.
./myscript.py mypassword dev app_name.yml stop
As #Etan Reisner pointed out, the main difference between your code that isn't working and my pexpect code is the spawn ansible command. The above code on expect also works fine:
#!/usr/bin/expect -f
spawn /usr/bin/ansible -m ping myserver --ask-pass
expect "SSH password:"
send "mypassword\r"
expect "$ "

Related

Piping the output of ssh sudo

I sometimes have a need to run commands as root on a remote server, and parse the output of the command on my local server. The remote server does not allow root login by ssh, but has sudo configured in a way that requires a password. A simplified example of what I need to do is
ssh remote sudo echo bar | tr bar foo
(Obviously in this simplified example, there's no good reason to need to run echo on a different machine to tr: this is just a toy example to explain what I'm trying to do.)
If I run the command above, I get an error that sudo has no way to prompt for a password:
richard#local:~$ ssh remote sudo echo bar | tr bar foo
sudo: no tty present and no askpass program specified
One way I can try to fix this is by adding the -t option to ssh. If I do that, sudo does wait for and accept a password, but the output of ssh's pseudo-terminal goes to stdout, meaning the sudo prompt message is piped to tr and not displayed to the user. If the user doesn't know sudo is waiting for a password, they will think the script has hung, and passing the prompt message to the pipe probably breaks further processing:
richard#local:~$ ssh -t remote sudo echo bar | tr bar foo
[sudo] posswood foo oichood:
foo
(This admittedly silly example shows the prompt has been processed by the tr command the output is piped to.)
The other way I can see to try to fix this is by adding the -S option to sudo. If I do that, sudo prompts on stderr for the password, so the prompt is not passed down the pipeline. That's good, but sudo also accepts the password on standard input meaning it's echoed to the terminal where anyone looking over the user's shoulder can read it:
richard#local:~$ ssh remote sudo -S echo bar | tr bar foo
[sudo] password for richard: p8ssw0rd
foo
I've found inelegant ways of working around the problems with these two options, but my workarounds hit a problem if the user gets their password wrong the first time. That in itself is a problem. Examples of this are:
richard#local:~$ echo "[sudo] password for $USER:"; \
ssh -t remote sudo echo bar | tail +2 | tr bar foo
richard#local:~$ (read -s password; echo $password; echo >&2) \
| ssh remote sudo -S echo bar | tr bar foo
I'm sure there must be a good solution to this, as it doesn't seem an uncommon thing to want to do. Any ideas?
The best solution I've come up with is to use sudo -S and disable local echo so the password isn't shown as you type it:
$ { stty -echo; ssh remote sudo -S echo hello; stty echo; echo 1>&2; }
[sudo] password for user:
hello
This leaves sudo in charge of the password prompting, so it works properly if the user types the password wrong.
I don't think any solution using ssh -t can ever work properly, since it combines stderr and stdout.

Executing a script remotely through ssh using expect

I am new to using expect. I try the code below and it fails:
expect -c 'spawn ssh user#host < script.ecma3
expect Password: ;
send "******\r";
send "exit\r";'
anyone could clarify
This might help you
#!/usr/bin/expect
set timeout -1
spawn -noecho bash -c "ssh -t user#host '<here-comes-your-command>'"
expect {
-re ".*assword.*" {
exp_send "$env(PASS_WORD)\n"
exp_continue
}
}
Note :-
1) Copy script to remote host before running it. passing whole script is not good thing to do.
2) to access enviornment variables in expect , $env(variable_name) is used.
In above example , for $PASS_WORD, i used $env(PASS_WORD) .

how does fabric execute commands?

i am wondering how does fabric execute commands.
Let's say I give him env.user=User, env.host=HOST. Then i ask him to sudo('ls')
Is that equivalent to me typing in a shell : ssh User#host 'sudo(/bin/ls)'
or it's more : ssh User#host in a first time, then sudo ls commande in a seconde time ?
I'm asking that because sometimes using a shell, if the TTY has a bad configuration (I am a bit blurry on this), ssh User#Host 'sudo /bin/ls'
return : sudo: no tty present and no askpass program specified
but you can first log in with ssh User#Host then sudo ls and it works.
I don't know how to replicate the no tty error, but I know it can occurs. Would this block the sudo commande from Fabric?
Basically how it works is:
First a connection is established (equivalent as doing ssh User#host)
Over this connection a command is executed as follows:
sudo -S -p 'sudo password:' /bin/bash -l -c "your_command"
You can also allow Fabric not to request a pty with either pty=False argument, env.always_use_pty=False or --no-pty commandline option.

ssh client (dropbear on a router) does no output when put in background

I'm trying to automate some things on remote Linux machines with bash scripting on Linux machine and have a working command (the braces are a relict from cmd concatenations):
(ssh -i /path/to/private_key user#remoteHost 'sh -c "echo 1; echo 2; echo 3; uname -a"')
But if an ampersand is concatenated to execute it in background, it seems to execute, but no output is printed, neither on stdout, nor on stderr, and even a redirection to a file (inside the braces) does not work...:
(ssh -i /path/to/private_key user#remoteHost 'sh -c "echo 1; echo 2; echo 3; uname -a"') &
By the way, I'm running the ssh client dropbear v0.52 in BusyBox v1.17.4 on Linux 2.4.37.10 (TomatoUSB build on a WRT54G).
Is there a way to get the output either? What's the reason for this behaviour?
EDIT:
For convenience, here's the plain ssh help output (on my TomatoUSB):
Dropbear client v0.52
Usage: ssh [options] [user#]host[/port][,[user#]host/port],...] [command]
Options are:
-p <remoteport>
-l <username>
-t Allocate a pty
-T Don't allocate a pty
-N Don't run a remote command
-f Run in background after auth
-y Always accept remote host key if unknown
-s Request a subsystem (use for sftp)
-i <identityfile> (multiple allowed)
-L <listenport:remotehost:remoteport> Local port forwarding
-g Allow remote hosts to connect to forwarded ports
-R <listenport:remotehost:remoteport> Remote port forwarding
-W <receive_window_buffer> (default 12288, larger may be faster, max 1MB)
-K <keepalive> (0 is never, default 0)
-I <idle_timeout> (0 is never, default 0)
-B <endhost:endport> Netcat-alike forwarding
-J <proxy_program> Use program pipe rather than TCP connection
Amendment after 1 day:
The braces do not hurt, with and without its the same result. I wanted to put the ssh authentication to background, so the -f option is not a solution. Interesting side note: if an unexpected option is specified (like -v), the error message WARNING: Ignoring unknown argument '-v' is displayed - even when put in background, so getting output from background processes generally works in my environment.
I tried on x86 Ubuntu regular ssh client: it works. I also tried dbclient on x86 Ubuntu: works, too. So this problem seems to be specific to the TomatoUSB build - or inside the "dropbear v0.52" was an unknown fix between the build in TomatoUSB and the one Ubuntu provides (difference in help output is just the double-sized default receive window buffer on Ubuntu)... how can a process know if it was put in background? Is there a solution to the problem?
I had the similar problem on my OpenWRT router. Dropbear SSH client does not write anything to output if there is no stdin, e.g. when run by cron. I presume that & has the same effect on process stdin (no input).
I found some workaround on author's bugtracker. Try to redirect input from /dev/zero.
Like:
ssh -i yourkey user#remotehost "echo 123" </dev/zero &
It worked for me as I tried to describe at my blog page.

How to inject commands at the start of an interactive SSH session?

I want to be able to just ssh to a server where I cannot modify profiles and set up the environment with several commands before getting the usual interactive session.
Any ideas?
I've been using an expect script with an "interact" command at the end - which works for most things but is clumsy and breaks some console apps. Also been extermienting with empty-expect and socat. Any other suggestions?
If you're able to write somewhere on the filesystem, you may be able to invoke bash with a custom rc file like this:
ssh me#example.com -t bash --rcfile /home/user/my_private_profile -i
Note that this appears to only work for interactive shell, not login shells. The -t option to ssh makes it allocate a pty even though you're specifying a command.
If you can't write to the filesystem anywhere, you could use a subshell to supply a named pipe as the rcfile:
$ ssh ares -t "bash --rcfile <(echo 'FOO=foo';echo 'BAR=bar') -i"
axa#ares:~$ echo $FOO
foo
axa#ares:~$ echo $BAR
bar