Commit 1d3dfa29 authored by Michael Hanselmann's avatar Michael Hanselmann
Browse files

import/export daemon: Add support for a magic prefix



This “magic” value will be used to ensure that we don't accidentially
connect to the wrong daemon (e.g. due to a bug), comparable to DRBD's
per-disk secret. Just depending on the SSL certificate isn't enough
as it's always per instance and not per disk.
Signed-off-by: default avatarMichael Hanselmann <hansmi@google.com>
Reviewed-by: default avatarGuido Trotter <ultrotter@google.com>
parent fbb6b864
......@@ -384,6 +384,8 @@ def ParseOptions():
parser.add_option("--expected-size", dest="exp_size", action="store",
type="string", default=None,
help="Expected import/export size (MiB)")
parser.add_option("--magic", dest="magic", action="store",
type="string", default=None, help="Magic string")
parser.add_option("--cmd-prefix", dest="cmd_prefix", action="store",
type="string", help="Command prefix")
parser.add_option("--cmd-suffix", dest="cmd_suffix", action="store",
......@@ -421,6 +423,10 @@ def ParseOptions():
parser.error("Invalid value for --expected-size: %s (%s)" %
(options.exp_size, err))
if not (options.magic is None or constants.IE_MAGIC_RE.match(options.magic)):
parser.error("Magic must match regular expression %s" %
constants.IE_MAGIC_RE.pattern)
return (status_file_path, mode)
......
......@@ -225,6 +225,8 @@ IEC_ALL = frozenset([
IE_CUSTOM_SIZE = "fd"
IE_MAGIC_RE = re.compile(r"^[-_.a-zA-Z0-9]{5,100}$")
# Import/export I/O
# Direct file I/O, equivalent to a shell's I/O redirection using '<' or '>'
IEIO_FILE = "file"
......
......@@ -115,6 +115,9 @@ class CommandBuilder(object):
self._dd_stderr_fd = dd_stderr_fd
self._dd_pid_fd = dd_pid_fd
assert (self._opts.magic is None or
constants.IE_MAGIC_RE.match(self._opts.magic))
@staticmethod
def GetBashCommand(cmd):
"""Prepares a command to be run in Bash.
......@@ -197,21 +200,63 @@ class CommandBuilder(object):
",".join(addr1), ",".join(addr2)
]
def _GetMagicCommand(self):
"""Returns the command to read/write the magic value.
"""
if not self._opts.magic:
return None
# Prefix to ensure magic isn't interpreted as option to "echo"
magic = "M=%s" % self._opts.magic
cmd = StringIO()
if self._mode == constants.IEM_IMPORT:
cmd.write("{ ")
cmd.write(utils.ShellQuoteArgs(["read", "-n", str(len(magic)), "magic"]))
cmd.write(" && ")
cmd.write("if test \"$magic\" != %s; then" % utils.ShellQuote(magic))
cmd.write(" echo %s >&2;" % utils.ShellQuote("Magic value mismatch"))
cmd.write(" exit 1;")
cmd.write("fi;")
cmd.write(" }")
elif self._mode == constants.IEM_EXPORT:
cmd.write(utils.ShellQuoteArgs(["echo", "-E", "-n", magic]))
else:
raise errors.GenericError("Invalid mode '%s'" % self._mode)
return cmd.getvalue()
def _GetDdCommand(self):
"""Returns the command for measuring throughput.
"""
dd_cmd = StringIO()
magic_cmd = self._GetMagicCommand()
if magic_cmd:
dd_cmd.write("{ ")
dd_cmd.write(magic_cmd)
dd_cmd.write(" && ")
dd_cmd.write("{ ")
# Setting LC_ALL since we want to parse the output and explicitely
# redirecting stdin, as the background process (dd) would have /dev/null as
# stdin otherwise
dd_cmd.write("{ LC_ALL=C dd bs=%s <&0 2>&%d & pid=${!};" %
dd_cmd.write("LC_ALL=C dd bs=%s <&0 2>&%d & pid=${!};" %
(BUFSIZE, self._dd_stderr_fd))
# Send PID to daemon
dd_cmd.write(" echo $pid >&%d;" % self._dd_pid_fd)
# And wait for dd
dd_cmd.write(" wait $pid;")
dd_cmd.write(" }")
if magic_cmd:
dd_cmd.write(" }")
return dd_cmd.getvalue()
def _GetTransportCommand(self):
......
......@@ -45,6 +45,7 @@ class CmdBuilderConfig(objects.ConfigObject):
"host",
"port",
"compress",
"magic",
"connect_timeout",
"connect_retries",
"cmd_prefix",
......@@ -66,6 +67,20 @@ class TestCommandBuilder(unittest.TestCase):
comprcmd = "gzip"
for compress in [constants.IEC_NONE, constants.IEC_GZIP]:
for magic in [None, 10 * "-", "HelloWorld", "J9plh4nFo2",
"24A02A81-2264-4B51-A882-A2AB9D85B420"]:
opts = CmdBuilderConfig(magic=magic, compress=compress)
builder = impexpd.CommandBuilder(mode, opts, 1, 2, 3)
magic_cmd = builder._GetMagicCommand()
dd_cmd = builder._GetDdCommand()
if magic:
self.assert_(("M=%s" % magic) in magic_cmd)
self.assert_(("M=%s" % magic) in dd_cmd)
else:
self.assertFalse(magic_cmd)
for host in ["localhost", "1.2.3.4", "192.0.2.99"]:
for port in [0, 1, 1234, 7856, 45452]:
for cmd_prefix in [None, "PrefixCommandGoesHere|",
......
......@@ -122,6 +122,11 @@ for mode in import export; do
$impexpd $src_statusfile $mode --port="$port" >/dev/null 2>&1 &&
err "daemon-util succeeded with invalid port '$port'"
done
for magic in '' ' ' 'this`is' 'invalid!magic' 'he"re'; do
$impexpd $src_statusfile $mode --magic="$magic" >/dev/null 2>&1 &&
err "daemon-util succeeded with invalid magic '$magic'"
done
done
upto 'Generate test data'
......@@ -156,6 +161,7 @@ start_test() {
connect_timeout=30
connect_retries=1
compress=gzip
magic=
}
wait_import_ready() {
......@@ -172,7 +178,7 @@ do_export() {
--cmd-prefix="$cmd_prefix" --cmd-suffix="$cmd_suffix" \
--connect-timeout=$connect_timeout \
--connect-retries=$connect_retries \
--compress=$compress
--compress=$compress ${magic:+--magic="$magic"}
}
do_import() {
......@@ -182,7 +188,7 @@ do_import() {
--cmd-prefix="$cmd_prefix" --cmd-suffix="$cmd_suffix" \
--connect-timeout=$connect_timeout \
--connect-retries=$connect_retries \
--compress=$compress
--compress=$compress ${magic:+--magic="$magic"}
}
upto 'Generate X509 certificates and keys'
......@@ -305,6 +311,40 @@ checkpids $exppid $imppid && err 'Did not fail when it should'
cmp -s $testdata $statusdir/recv-miscompr2 && \
err 'Received data matches input when it should not'
start_test 'Magic without compression'
compress=none magic=MagicValue13582 \
do_import > $statusdir/recv-magic1 2>$dst_output & imppid=$!
if port=$(wait_import_ready 2>$src_output); then
compress=none magic=MagicValue13582 \
do_export $port < $testdata >>$src_output 2>&1 & exppid=$!
fi
checkpids $exppid $imppid || err 'An error occurred'
cmp $testdata $statusdir/recv-magic1 || err 'Received data does not match input'
start_test 'Magic with compression'
compress=gzip magic=yzD1FBH7Iw \
do_import > $statusdir/recv-magic2 2>$dst_output & imppid=$!
if port=$(wait_import_ready 2>$src_output); then
compress=gzip magic=yzD1FBH7Iw \
do_export $port < $testdata >>$src_output 2>&1 & exppid=$!
fi
checkpids $exppid $imppid || err 'An error occurred'
cmp $testdata $statusdir/recv-magic2 || err 'Received data does not match input'
start_test 'Magic mismatch A (same length)'
magic=h0tmIKXK do_import > $statusdir/recv-magic3 2>$dst_output & imppid=$!
if port=$(wait_import_ready 2>$src_output); then
magic=bo6m9uAw do_export $port < $testdata >>$src_output 2>&1 & exppid=$!
fi
checkpids $exppid $imppid && err 'Did not fail when it should'
start_test 'Magic mismatch B'
magic=AUxVEWXVr5GK do_import > $statusdir/recv-magic4 2>$dst_output & imppid=$!
if port=$(wait_import_ready 2>$src_output); then
magic=74RiP9KP do_export $port < $testdata >>$src_output 2>&1 & exppid=$!
fi
checkpids $exppid $imppid && err 'Did not fail when it should'
start_test 'Large transfer'
do_import > $statusdir/recv-large 2>$dst_output & imppid=$!
if port=$(wait_import_ready 2>$src_output); then
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment