Migration to bombshell complete

This commit is contained in:
Manuel Amador (Rudd-O) 2015-10-20 03:21:00 +00:00
parent 3fc1883f94
commit e732006bf0
6 changed files with 125 additions and 194 deletions

View File

@ -15,11 +15,13 @@ You integrate it into your Ansible setup by:
1. placing the `qubes.py` connection plugin in your Ansible
`connection_plugins` directory, then
2. placing the `qrun` and `qrun-bridge` executables in one of two locations:
2. placing the `bombshell-client` executable in one of two locations:
* Anywhere on your Ansible machine's `PATH`.
* In a `../../bin` directory relative to the `qubes.py` file.
3. placing the `qrun` executable in the same location as `bombshell-client`.
After having done that, you can add Qubes VMs to your Ansible `hosts` file:
```
@ -27,12 +29,22 @@ workvm ansible_connection=qubes
vmonremotehost ansible_connection=qubes management_proxy=1.2.3.4
```
Experimental bombshell replacement for qrun-bridge and friends
--------------------------------------------------------------
You are now free to run `ansible-playbook` or `ansible` against those hosts.
There is a *much faster* way to run commands in other VMs that employs the `bombshell-client` script on this repository. Said script is still not part of the Ansible Qubes automation system, but it's the future of Ansible Qubes automation. Despite the fact that the script is not yet wired into the Ansible automation system for Qubes, it can be used right now to execute commands against other VMs in a much faster way than through the legacy `qrun` script.
Additionally, you can use the `qssh` and `qscp` commands, which will
transparently attempt to SSH into a host unless it is unresolvable,
in which case it will fall back to using the `bombshell-client` to
communicate with a local VM. Simply place these commands within the
same `bin` directory mentioned above, and they will just work. If you
symlink `ssh` and `scp` to those commands respectively, SaltStack's
SSH-based automation will work transparently as well.
Usage instructions:
Bombshell remote shell technology
---------------------------------
Bombshell is a way to run commands in other VMs, that employs the `bombshell-client` script on this repository. Said method is now integrated in these programs and will only work with Qubes OS 3.
Direct (non-Ansible and non-SaltStack) usage instructions:
./bombshell-client <vmname> command-to-run [arguments...]
@ -42,11 +54,7 @@ The command above spawns a `command-to-run` on `vmname`, interactively. Standar
Spawns the `command-to-run` on the `vmname`, interactively, printing communication channel interaction behavior into the standard error of the invoker, and into the root journal of the `vmname`.
I'm pledging bounties for the following bugs:
* US$65 per bug fix that solves problems with the script handling extraneous error conditions (you must explain how the condition arises, and how your fix prevents it).
* US$230 per bug fix that fixes data losses (you must explain what the data loss is, and demonstrate how your fix fixes it).
* US$830 per bug fix that fixes security issues (you must demo the security flaw after explaining what the insecurity scenario is and justifying the scenario). This one is capped at two fixes.
The bounties that were published have been collected. Sorry! Open source works!
Enjoy!

View File

@ -54,25 +54,32 @@ class Connection(object):
def produce_command(self, cmd, executable='/bin/sh'):
proxy = ["--proxy=%s" % self.proxy] if self.proxy else []
chroot = "%s" % self.chroot if self.chroot else self.host
if executable:
local_cmd = [self.chroot_cmd] + proxy + [chroot, cmd]
vvv("EXEC (with executable %s) %s" % (executable, local_cmd), host=self.chroot)
chroot = self.chroot if self.chroot else self.host
if isinstance(cmd, basestring):
cmd = ["bash", "-c", cmd]
if proxy:
local_cmd = [self.chroot_cmd] + proxy + [chroot] + cmd
else:
if proxy:
local_cmd = '%s %s "%s" %s' % (self.chroot_cmd, proxy, chroot, cmd)
else:
local_cmd = '%s "%s" %s' % (self.chroot_cmd, chroot, cmd)
vvv("EXEC (without executable) %s" % (local_cmd), host=self.chroot)
local_cmd = [self.chroot_cmd] + [chroot] + cmd
if executable:
local_cmd = [executable, "-c", " ".join(pipes.quote(x) for x in local_cmd)]
vvv("EXEC (with %s) %s" % (executable, local_cmd), host=self.chroot)
else:
vvv("EXEC (without executable) %s" % (local_cmd), host=self.chroot)
return local_cmd
def exec_command(self, cmd, tmp_path, become_user=None, sudoable=False, executable='/bin/sh', in_data=None):
''' run a command on the chroot '''
# We enter qrun as root so sudo stuff can be ignored
if become_user is not None and sudoable:
if isinstance(cmd, basestring):
cmd = "sudo -H -u %s bash -c %s" % (pipes.quote(become_user), pipes.quote(cmd))
else:
assert 0, "is not basestring: %r" % cmd
local_cmd = self.produce_command(cmd, executable)
p = subprocess.Popen(local_cmd, shell=isinstance(local_cmd, basestring),
p = subprocess.Popen(local_cmd, shell=False,
cwd=self.runner.basedir,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)

View File

@ -13,20 +13,29 @@ else:
remotehost = None
host, parms = argv[0], argv[1:]
path_to_this_file = os.path.dirname(__file__)
path_to_qrun = os.path.join(path_to_this_file, "qrun-bridge")
path_to_qrun = os.path.abspath(path_to_qrun)
path_to_bombshell = os.path.join(os.path.dirname(__file__), "bombshell-client")
if os.getenv("BOMBSHELL_DEBUG"):
cmd = [
path_to_bombshell,
"-d",
host,
] + parms
else:
cmd = [
path_to_bombshell,
host,
] + parms
if remotehost:
args = " ".join(pipes.quote(x) for x in parms)
poop = file(path_to_qrun, "rb").read().encode("hex_codec")
therest_template = ("test -x ./.qrun-bridge-stub || "
"python -c 'import os; file(\"./.qrun-bridge-stub\", \"wb\").write(\"%s\".decode(\"hex_codec\")); os.chmod(\"./.qrun-bridge-stub\", 0700)' || "
poop = file(path_to_bombshell, "rb").read().encode("hex_codec")
therest_template = ("test -x ./.bombshell-client || "
"python -c 'import os; file(\"./.bombshell-client\", \"wb\").write(\"%s\".decode(\"hex_codec\")); os.chmod(\"./.bombshell-client\", 0700)' || "
"exit 127 ;"
"export PASS_LOCAL_STDERR=1 ;"
"/usr/lib/qubes/qrexec-client-vm %s "
"qubes.VMShell ./.qrun-bridge-stub %s")
therest = therest_template % (poop, host, args)
"export BOMBSHELL_DEBUG=%s ;"
"./.bombshell-client %s %s")
therest = therest_template % (poop, pipes.quote(os.getenv("BOMBSHELL_DEBUG")), pipes.quote(host), args)
cmd = [
'ssh',
'-o', 'BatchMode yes',
@ -34,12 +43,5 @@ if remotehost:
therest,
]
else:
os.environ["PASS_LOCAL_STDERR"] = "1"
cmd = [
'/usr/lib/qubes/qrexec-client-vm',
host,
'qubes.VMShell',
path_to_qrun,
] + parms
os.execvp(cmd[0], cmd)

View File

@ -1,156 +0,0 @@
#!/bin/sh
echo "exec python -c '
import sys
import os
import socket
import subprocess
import threading
def start_process():
prefix = [\"su\", \"-\", \"root\"]
if socket.gethostname().startswith(\"dom0.\") or socket.gethostname() == \"dom0\":
prefix = [\"sudo\", \"-H\"]
if sys.argv[1:]:
cmd = prefix + [\"sh\", \"-c\", \" \".join(sys.argv[1:])]
else:
cmd = prefix + [\"sh\"]
p = subprocess.Popen(
cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
return p, p.stdin, p.stdout, p.stderr
def supervise_process(p):
ret = process.wait()
sys.stdout.write(\"P\" + str(ret) + \" \")
sys.stdout.flush()
def relay_stdin(i):
while True:
b = sys.stdin.read(1)
if b == \"i\":
data = sys.stdin.read(1)
i.write(data)
i.flush()
elif b == \"I\":
i.close()
break
elif not b:
i.close()
break
def relay_stdout(o):
while True:
b = o.read(1)
if not b:
sys.stdout.write(\"O\")
sys.stdout.flush()
break
sys.stdout.write(\"o\" + b)
sys.stdout.flush()
def relay_stderr(e):
while True:
b = e.read(1)
if not b:
sys.stdout.write(\"E\")
sys.stdout.flush()
break
sys.stdout.write(\"e\" + b)
sys.stdout.flush()
process, i, o, e = start_process()
relayer_stdin = threading.Thread(target=lambda: relay_stdin(i))
relayer_stdout = threading.Thread(target=lambda: relay_stdout(o))
relayer_stderr = threading.Thread(target=lambda: relay_stderr(e))
relayer_stdin.start()
relayer_stdout.start()
relayer_stderr.start()
process_supervisor = threading.Thread(target=lambda: supervise_process(process))
process_supervisor.start()
def close_stdout():
relayer_stdout.join()
relayer_stderr.join()
relayer_stdin.join()
process_supervisor.join()
sys.stdout.close()
stdout_closer = threading.Thread(target=close_stdout)
stdout_closer.start()
stdout_closer.join()
'" \"$@\" >&1
exec python -c '
import sys
import os
import threading
import select
import fcntl
saved_fd_0 = int(sys.argv[1])
local_stdin = os.fdopen(saved_fd_0, "r")
saved_fd_1 = int(sys.argv[2])
local_stdout = os.fdopen(saved_fd_1, "w")
saved_fd_2 = int(sys.argv[3])
local_stderr = os.fdopen(saved_fd_2, "w")
def relay_stdin():
while True:
b = local_stdin.read(1)
if not b:
sys.stdout.write("I")
sys.stdout.flush()
sys.stdout.close()
break
sys.stdout.write("i" + b)
sys.stdout.flush()
retval = 0
def display_stdouterr():
global retval
conditions = 3
while conditions:
b = sys.stdin.read(1)
if not b: break
elif b == "o":
b = sys.stdin.read(1)
local_stdout.write(b)
local_stdout.flush()
elif b == "e":
b = sys.stdin.read(1)
local_stderr.write(b)
local_stderr.flush()
elif b == "O":
conditions = conditions - 1
elif b == "E":
conditions = conditions - 1
elif b == "P":
chars = ""
while not chars.endswith(" "):
char = sys.stdin.read(1)
chars = chars + char
chars = chars.replace(" ", "")
retval = int(chars)
conditions = conditions - 1
stdin_relayer = threading.Thread(target=relay_stdin)
stdin_relayer.setDaemon(True)
stdin_relayer.start()
displayer = threading.Thread(target=display_stdouterr)
displayer.start()
displayer.join()
sys.exit(int(retval))
' $SAVED_FD_0 $SAVED_FD_1 $SAVED_FD_2

24
bin/qscp Executable file
View File

@ -0,0 +1,24 @@
#!/usr/bin/env python
import sys
import os
import subprocess
import socket
parms = sys.argv[1:]
try:
parmwithcolons = [x for x in parms if ":" in x][-1]
ipaddr = parmwithcolons.split(":",1)[0]
socket.inet_aton(ipaddr)
os.execv("/usr/bin/scp", ["/usr/bin/scp"] + parms)
except socket.error:
pass
path_to_this_file = os.path.dirname(__file__)
path_to_ssh = os.path.join(path_to_this_file, "qssh")
path_to_ssh = os.path.abspath(path_to_ssh)
scmd = ["/usr/bin/scp"] + ["-S", path_to_ssh] + parms
os.execvp(scmd[0], scmd)

46
bin/qssh Executable file
View File

@ -0,0 +1,46 @@
#!/usr/bin/env python
import sys
import os
import subprocess
import socket
parms = sys.argv[1:]
try:
socket.inet_aton(parms[0])
os.execv("/usr/bin/ssh", ["/usr/bin/ssh"] + parms)
except socket.error:
pass
path_to_bombshell = os.path.abspath(os.path.join(os.path.dirname(__file__), "bombshell-client"))
host = None
rest = parms
while True:
if not rest:
break
if rest[0] == "--":
if host is None:
_, host, rest = rest[0], rest[1], rest[2:]
else:
_, rest = rest[0], rest[1:]
break
elif rest[0].startswith("-o") and len(rest[0]) > 2:
_, rest = rest[0], rest[1:]
elif rest[0].startswith("-o"):
_, rest = rest[0:1], rest[2:]
elif rest[0].startswith("-"):
_, rest = rest[0], rest[1:]
else:
if host is None:
host, rest = rest[0], rest[1:]
else:
break
cmd = [
path_to_bombshell,
host,
] + rest
os.execvp(cmd[0], cmd)