mirror of
https://github.com/Rudd-O/ansible-qubes.git
synced 2025-03-01 14:22:33 +01:00
Migration to bombshell complete
This commit is contained in:
parent
3fc1883f94
commit
e732006bf0
28
README.md
28
README.md
@ -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!
|
||||
|
||||
|
@ -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)
|
||||
|
36
bin/qrun
36
bin/qrun
@ -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)
|
||||
|
156
bin/qrun-bridge
156
bin/qrun-bridge
@ -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
24
bin/qscp
Executable 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
46
bin/qssh
Executable 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)
|
Loading…
x
Reference in New Issue
Block a user