Commit f32664b2 authored by Nikos Skalkotos's avatar Nikos Skalkotos
Browse files

Add helper-monitor.py program

This program transforms raw helper monitor messages into json strings
under very strict rules. This reduces security concerns since the
helper VM output cannot be trusted.
parent 9baf3e2c
......@@ -40,9 +40,10 @@ CLEANUP=( )
ERRORS=( )
WARNINGS=( )
MSG_TYPE_ERROR="error"
MSG_TYPE_TASK_START="task-start"
MSG_TYPE_TASK_END="task-end"
MSG_TYPE_TASK_START="TASK_START"
MSG_TYPE_TASK_END="TASK_END"
STDERR_LINE_SIZE=10
add_cleanup() {
local cmd=""
......@@ -57,68 +58,30 @@ log_error() {
}
warn() {
WARNINGS+=("$@")
echo "Warning: $@" >&2
echo "WARNING:$@" > "$MONITOR"
}
report_task_start() {
local type="$MSG_TYPE_TASK_START"
local timestamp=$(date +%s.%N)
local name="${PROGNAME}"
report+="{\"type\":\"$type\","
report+="\"timestamp\":$(date +%s.%N),"
report+="\"name\":\"$name\"}"
echo "$report" > "$MONITOR"
}
json_list() {
declare -a items=("${!1}")
report="["
for item in "${items[@]}"; do
report+="\"$(sed 's/"/\\"/g' <<< "$item")\","
done
if [ ${#report} -gt 1 ]; then
# remove last comma(,)
report="${report%?}"
fi
report+="]"
echo "$report"
echo "$MSG_TYPE_TASK_START:${PROGNAME:2}" > "$MONITOR"
}
report_task_end() {
local type="$MSG_TYPE_TASK_END"
local timestam=$(date +%s.%N)
local name=${PROGNAME}
local warnings=$(json_list WARNINGS[@])
report="{\"type\":\"$type\","
report+="\"timestamp\":$(date +%s.%N),"
report+="\"name\":\"$name\","
report+="\"warnings\":\"$warnings\"}"
echo "$report" > "$MONITOR"
echo "$MSG_TYPE_TASK_END:${PROGNAME:2}" > "$MONITOR"
}
report_error() {
local type="$MSG_TYPE_ERROR"
local timestamp=$(date +%s.%N)
local location="${PROGNAME}"
local errors=$(json_list ERRORS[@])
local warnings=$(json_list WARNINGS[@])
local stderr="$(cat "$STDERR_FILE" | sed 's/"/\\"/g')"
report="{\"type\":\"$type\","
report+="\"timestamp\":$(date +%s),"
report+="\"location\":\"$location\","
report+="\"errors\":$errors,"
report+="\"warnings\":$warnings,"
report+="\"stderr\":\"$stderr\"}"
echo "$report" > "$MONITOR"
if [ ${#ERRORS[*]} -eq 0 ]; then
# No error message. Print stderr
local lines=$(tail --lines=${STDERR_LINE_SIZE} "$STDERR_FILE" | wc -l)
echo -n "STDERR:${lines}:" > "$MONITOR"
tail --lines=$lines "$STDERR_FILE" > "$MONITOR"
else
echo -n "ERROR:" > "$MONITOR"
for line in "${ERRORS[@]}"; do
echo "$line" > "$MONITOR"
done
fi
}
get_base_distro() {
......@@ -415,7 +378,7 @@ check_if_excluded() {
local name="$(tr [a-z] [A-Z] <<< ${PROGNAME:2})"
local exclude="SNF_IMAGE_PROPERTY_EXCLUDE_TASK_${name}"
if [ -n "${!exclude}" ]; then
warn "Task $PROGNAME was excluded and will not run."
warn "Task ${PROGNAME:2} was excluded and will not run."
exit 0
fi
......
......@@ -10,7 +10,8 @@ defaultdir=$(DEFAULT_DIR)
variantsdir=${sysconfdir}/ganeti/snf-image/variants
dist_os_SCRIPTS = ${srcdir}/create ${srcdir}/import ${srcdir}/export \
${srcdir}/rename ${srcdir}/verify ${srcdir}/pithcat ${srcdir}/copy-monitor.py
${srcdir}/rename ${srcdir}/verify ${srcdir}/pithcat \
${srcdir}/copy-monitor.py ${srcdir}/helper-monitor.py
dist_os_DATA = ${srcdir}/ganeti_api_version ${srcdir}/parameters.list \
${srcdir}/variants.list
......
......@@ -55,23 +55,25 @@ report_error() {
local type="$MSG_TYPE_ERROR"
local location="host"
local msg="["
for err in "${ERROR_MSGS[@]}"; do
msg+="\"$(sed 's/"/\\"/g' <<< "$err")\","
done
if [ ${#msg} -gt 1 ]; then
# remove last comma (,)
msg="${msg%?}"
fi
msg+="]"
local stderr="$(cat "$error_file" | sed 's/"/\\"/g')"
report="\"type\":\"$type\","
report+="\"timestamp\":$(date +%s.%N),"
report+="\"location\":\"$location\","
report+="\"messages\":$msg,"
report+="\"stderr\":\"$stderr\"}"
if [ ${#ERROR_MSGS[@]} -gt 0 ]; then
local msg="["
for err in "${ERROR_MSGS[@]}"; do
msg+="\"$(sed 's/"/\\"/g' <<< "$err")\","
done
if [ ${#msg} -gt 1 ]; then
# remove last comma (,)
msg="${msg%?}"
fi
msg+="]"
report+="\"messages\":$msg}"
else
local stderr="$(cat "$error_file" | sed 's/"/\\"/g')"
report+="\"stderr\":\"$stderr\"}"
fi
eval "echo $(printf "%q" "$report") >&${monitor_fd}"
}
......
......@@ -157,7 +157,7 @@ $TIMEOUT -k "$HELPER_HARD_TIMEOUT" "$HELPER_SOFT_TIMEOUT" \
kvm -runas "$HELPER_USER" -drive file="$snapshot" \
-drive file="$blockdev",format=raw,if=virtio,cache=none \
-boot c -serial stdio -serial "file:$(printf "%q" "$result_file")" \
-serial "file:$(printf "%q" "$monitor_pipe")" \
-serial file:>(./helper-monitor.py ${MONITOR_FD}) \
-fda "$floppy" -vga none -nographic -parallel none -monitor null \
-kernel "$HELPER_KERNEL" -initrd "$HELPER_INITRD" \
-append "quiet ro root=/dev/sda1 console=ttyS0,9600n8 snf_image_activate_helper" \
......@@ -182,16 +182,11 @@ else
echo "$(date +%Y:%m:%d-%H:%M:%S.%N) Helper VM finished."
fi
# Do not report errors after this. If the result is not "SUCCESS" then the
# helper VM should have reported the error.
trap cleanup EXIT
# Read the first line. This will remove \r and \n chars
result=$(sed 's|\r||g' "$result_file" | head -1)
if [ "x$result" != "xSUCCESS" ]; then
log_error "Helper VM returned error:"
log_error "$result"
log_error "Helper VM did not return SUCCESS"
exit 1
fi
......
#!/usr/bin/env python
# Copyright (C) 2012 GRNET S.A.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
import sys
import os
import time
import json
import re
LINESIZE = 512
PROGNAME = os.path.basename(sys.argv[0])
STDERR_MAXLINES = 10
MAXLINES = 100
PROTOCOL = {
'TASK_START': ('task-start', 'task'),
'TASK_END': ('task-end', 'task'),
'WARNING': ('warning', 'msg'),
'STDERR': ('error', 'stderr'),
'ERROR': ('error', 'msg')}
def error(msg):
sys.stderr.write("helper-monitor error: %s\n" % msg)
sys.exit(1)
def send(fd, msg_type, value):
subtype, value_name = PROTOCOL[msg_type]
msg = {}
msg['type'] = 'helper'
msg['subtype'] = subtype
msg[value_name] = value
msg['timestamp'] = time.time()
os.write(fd, "%s\n" % json.dumps(msg))
if __name__ == "__main__":
usage = "Usage: %s <file-descriptor>\n" % PROGNAME
if len(sys.argv) != 2:
sys.stderr.write(usage)
sys.exit(1)
try:
fd = int(sys.argv[1])
except ValueError:
error("File descriptor is not an integer")
try:
os.fstat(fd)
except OSError:
error("File descriptor is not valid")
lines_left = 0
stderr = ""
line_count = 0
while 1:
line = sys.stdin.readline(LINESIZE)
if not line:
break
else:
line_count += 1
if line[-1] != '\n':
# Line is too long...
error("Too long line...")
sys.exit(1)
if lines_left > 0:
stderr += line
lines_left -= 1
if lines_left == 0:
send(fd, "STDERR", stderr)
stderr = ""
continue
if line_count >= MAXLINES + 1:
error("Maximum allowed helper monitor number of lines exceeded.")
line = line.strip()
if len(line) == 0:
continue
if line.startswith("STDERR:"):
m = re.match("STDERR:(\d+):(.*)", line)
if not m:
error("Invalid syntax for STDERR line")
try:
lines_left = int(m.group(1))
except ValueError:
error("Second field in STDERR line must be an integer")
if lines_left > STDERR_MAXLINES:
error("Too many lines in the STDERR output")
elif lines_left < 0:
error("Second field of STDERR: %d is invalid" % lines_left)
if lines_left > 0:
stderr = m.group(2) + "\n"
lines_left -= 1
if lines_left == 0:
send(fd, "STDERR", stderr)
stderr = ""
elif line.startswith("TASK_START:") or line.startswith("TASK_END:") \
or line.startswith("WARNING:") or line.startswith("ERROR:"):
(msg_type, _, value) = line.partition(':')
send(fd, msg_type, value)
else:
error("Unknown command!")
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :
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