package_to_container: create a signed app
In addition to updating package_to_container and the generated
config.json files (includes changes from CL:417097), output the
manifest and sign it.
The layout is:
manifest.json - file w/config.json & rootfs hashes
manifest.json.sig - signature of manifest.json
By default we use the devkey from vboot.
BUG=chromium:660209
TEST=run_oci only runs containers with a valid manifest.json{,.sig}
CQ-DEPEND=CL:427498
Change-Id: I3f570ade96e267b420a4609919ebc3af3c7cdc5b
Reviewed-on: https://chromium-review.googlesource.com/415231
Commit-Ready: Mike Frysinger <vapier@chromium.org>
Tested-by: Mike Frysinger <vapier@chromium.org>
Reviewed-by: Mike Frysinger <vapier@chromium.org>
diff --git a/generic_container_files/config.json b/generic_container_files/config.json
index d5eedc8..87dd2f6 100644
--- a/generic_container_files/config.json
+++ b/generic_container_files/config.json
@@ -1,78 +1,97 @@
{
- "version": "0.2.0",
+ "ociVersion": "1.0.0-rc1",
"platform": {
- "os": "linux"
+ "os": "linux",
+ "arch": "all"
},
"process": {
+ "terminal": @TERMINAL@,
"user": {
- "uid": 1000,
- "gid": 1000,
- "additionalGids": null
+ "uid": @USER_UID@,
+ "gid": @USER_GID@
},
"args": [
- ":"
- ]
+ "@ARGV@"
+ ],
+ "cwd": "/"
},
"root": {
- "path": "root"
+ "path": "rootfs",
+ "readonly": false
},
- "hostname": "generic_container",
+ "hostname": "@APP_NAME@",
"mounts": [
{
- "name": "dev",
- "path": "/dev"
+ "destination": "/",
+ "type": "squashfs",
+ "source": "@APP_NAME@.squashfs",
+ "options": [
+ "ro",
+ "loop",
+ "nodev",
+ "nosuid",
+ "dm=@VERITY@"
+ ]
},
{
- "name": "dev_bus",
- "path": "/dev/bus"
+ "destination": "/proc",
+ "type": "proc",
+ "source": "proc",
+ "options": [
+ "nosuid",
+ "noexec",
+ "nodev"
+ ]
},
{
- "name": "dev_bus_usb",
- "path": "/dev/bus/usb"
- },
- {
- "name": "dev_null",
- "path": "/dev/null"
- },
- {
- "name": "dev_random",
- "path": "/dev/random"
- },
- {
- "name": "dev_urandom",
- "path": "/dev/urandom"
- },
- {
- "name": "proc",
- "path": "/proc"
- },
- {
- "name": "run_broker_service",
- "path": "/run/broker_service"
- },
- {
- "name": "sys",
- "path": "/sys"
- },
- {
- "name": "sys_bus",
- "path": "/sys/bus"
- },
- {
- "name": "sys_bus_usb",
- "path": "/sys/bus/usb"
- },
- {
- "name": "sys_bus_usb_devices",
- "path": "/sys/bus/usb/devices"
- },
- {
- "name": "sys_devices",
- "path": "/sys/devices"
- },
- {
- "name": "tmp",
- "path": "/tmp"
+ "destination": "/dev",
+ "type": "tmpfs",
+ "source": "tmpfs",
+ "options": [
+ "mode=755",
+ "nosuid",
+ "noexec"
+ ]
}
- ]
+ ],
+ "hooks": {},
+ "linux": {
+ "namespaces": [
+ {
+ "type": "cgroup"
+ },
+ {
+ "type": "pid"
+ },
+ {
+ "type": "network"
+ },
+ {
+ "type": "ipc"
+ },
+ {
+ "type": "user"
+ },
+ {
+ "type": "uts"
+ },
+ {
+ "type": "mount"
+ }
+ ],
+ "uidMappings": [
+ {
+ "hostID": @USER_UID@,
+ "containerID": 0,
+ "size": 1
+ }
+ ],
+ "gidMappings": [
+ {
+ "hostID": @USER_GID@,
+ "containerID": 0,
+ "size": 1
+ }
+ ]
+ }
}
diff --git a/generic_container_files/runtime.json b/generic_container_files/runtime.json
deleted file mode 100644
index 20e327b..0000000
--- a/generic_container_files/runtime.json
+++ /dev/null
@@ -1,126 +0,0 @@
-{
- "mounts": {
- "dev": {
- "type": "tmpfs",
- "source": "tmpfs",
- "options": [
- "nosuid",
- "mode=0755"
- ]
- },
- "dev_bus": {
- "type": "tmpfs",
- "source": "tmpfs",
- "options": [
- "nosuid",
- "mode=0755"
- ]
- },
- "dev_bus_usb": {
- "type": "bind",
- "source": "/dev/bus/usb",
- "options": [
- "bind"
- ]
- },
- "dev_random": {
- "type": "bind",
- "source": "/dev/random",
- "options": [
- "bind"
- ]
- },
- "dev_urandom": {
- "type": "bind",
- "source": "/dev/urandom",
- "options": [
- "bind"
- ]
- },
- "dev_null": {
- "type": "bind",
- "source": "/dev/null",
- "options": [
- "bind"
- ]
- },
- "sys": {
- "type": "tmpfs",
- "source": "tmpfs",
- "options": [
- "nosuid",
- "mode=0755"
- ]
- },
- "proc": {
- "type": "proc",
- "source": "proc",
- "options": [
- "noexec",
- "nodev",
- "nosuid"
- ]
- },
- "run": {
- "type": "tmpfs",
- "source": "tmpfs",
- "options": [
- "nosuid",
- "mode=0755"
- ]
- },
- "run_broker_service" : {
- "type": "bind",
- "source": "/run/broker_service",
- "options": [
- "noexec",
- "nodev",
- "bind"
- ]
- },
- "sys_bus": {
- "type": "tmpfs",
- "source": "tmpfs",
- "options": [
- "nosuid",
- "mode=0755"
- ]
- },
- "sys_bus_usb": {
- "type": "tmpfs",
- "source": "tmpfs",
- "options": [
- "nosuid",
- "mode=0755"
- ]
- },
- "sys_bus_usb_devices": {
- "type": "bind",
- "source": "/sys/bus/usb/devices",
- "options": [
- "bind"
- ]
- },
- "sys_devices": {
- "type": "bind",
- "source": "/sys/devices",
- "options": [
- "bind"
- ]
- },
- "tmp": {
- "type": "tmpfs",
- "source": "tmpfs",
- "options": [
- "nosuid",
- "mode=0755"
- ]
- }
- },
- "linux": {
- "uidMappings": "0 1000 1",
- "gidMappings": "0 1000 1",
- "devices": [],
- "altSysCallTable": "third_party"
- }
-}
diff --git a/package_to_container b/package_to_container
index 8fec90f..4dcaa1e 100755
--- a/package_to_container
+++ b/package_to_container
@@ -7,92 +7,187 @@
SCRIPT_ROOT=$(dirname $(readlink -f "$0"))
. "${SCRIPT_ROOT}/build_library/build_common.sh" || exit 1
+assert_inside_chroot "$@"
+
DEFINE_string board "${DEFAULT_BOARD}" \
"The board to build an image for."
DEFINE_string package "" \
"Package whose bare minimum deps the final container should have."
-DEFINE_string name "" \
+DEFINE_string name "example" \
"Name of the container."
DEFINE_string extra "" \
"Comma separated extra packages to be included in the final image."
+DEFINE_string argv "" \
+ "The command (and args) to run inside the container."
+
+FLAGS_HELP="USAGE: $(basename "$0") [flags]
+
+The --package and --argv options are required.
+
+To build a container for fastboot, you might want something like:
+$ $(basename "$0") --package dev-util/android-tools --argv /usr/bin/fastboot
+"
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"
switch_to_strict_mode
+# Sign the manifest.json file.
+sign_manifest() {
+ "${VBOOT_SIGNING_DIR}"/sign_official_build.sh \
+ oci-container "$1" "${VBOOT_DEVKEYS_DIR}" "$1"
+}
+
+# Generate the manifest.json for this container.
+generate_manifest() {
+ local pkg_name="$1"
+ local output="$2"
+ local manifest="${output}/manifest.json"
+
+ (
+ hash() {
+ local file="$1"
+ local comma="$2"
+ # The Chromium base library does not support dots in dict keys. They
+ # use it as a short hand to access children dicts. Normalize them.
+ local dict_key="${file//./_}"
+ local size sha
+
+ size=$(stat -c %s "${file}")
+ sha=$(sha256sum <"${file}" | awk '{print $1}')
+
+ printf ' "%s": {\n "size": %s,\n "sha256": "%s"\n }%s\n' \
+ "${dict_key}" "${size}" "${sha}" "${comma}"
+ }
+
+ cd "${output}"
+ printf '{\n"version": 1,\n"files": {\n'
+ hash "config.json" ","
+ hash "${pkg_name}.squashfs" ""
+ printf '}\n}\n'
+ ) >"${manifest}"
+
+ # Sanity check the generated manifest.
+ python -mjson.tool <"${manifest}" >/dev/null
+
+ sign_manifest "${output}"
+}
+
+# Sign the specified disk image using verity so we can load it with dm-verity
+# at runtime. We don't allow algorithm selection -- sha1 should be good enough
+# for everyone! :)
+# Note: We write the verity command line to the "verity" variable as an output.
+sign_disk_image() {
+ local img="$1"
+ local hashtree="${img}.hashtree"
+ # TODO: Add "salt=random" here.
+ verity=$(verity mode=create alg=sha256 payload="${img}" \
+ hashtree="${hashtree}")
+ verity=$(echo "${verity}" | sed -E -e 's:(ROOT|HASH)_DEV:@DEV@:g')
+ cat "${hashtree}" >>"${img}"
+ rm "${hashtree}"
+}
+
# Produce the container image.
build_container_image() {
local pkg_name="$1"
local container_name="$2"
- local ROOTDIR="out"
+ local argv="$3"
+ # For now we hardcode chronos.
+ local container_uid="1000"
+ local container_gid="1000"
+ local output="${PWD}/${container_name}"
+ local ROOTDIR="${output}/rootfs"
- info "Cleaning previously generated files ... "
- sudo rm -rf "${ROOTDIR}"
+ export INSTALL_MASK="${DEFAULT_INSTALL_MASK}"
- install_with_root_deps "${pkg_name}" "${ROOTDIR}"
+ mkdir -p "${output}"
- local pkg
- for pkg in $(echo $FLAGS_extra | sed "s/,/ /g"); do
- install_with_root_deps "${pkg}" "${ROOTDIR}"
- done
+ install_with_root_deps "${ROOTDIR}" "${pkg_name}" sys-libs/gcc-libs \
+ ${FLAGS_extra//,/ }
- info "Installing libc.so ... "
+ info "Installing C library ... "
install_libc "${ROOTDIR}"
- info "Installing libcontainer_overrides ... "
- install_with_no_deps "chromeos-base/libcontainer_overrides" "${ROOTDIR}"
+ info "Cleaning excess files ... "
+ sudo rm -rf "${ROOTDIR}"/var "${ROOTDIR}"/usr/lib*/gconv/ \
+ "${ROOTDIR}"/sbin/ldconfig
+ sudo find "${ROOTDIR}"/ -type d -depth -exec rmdir {} + 2>/dev/null || :
info "Creating top level dirs and socket dirs ... "
- sudo mkdir -p "${ROOTDIR}"/{dev,proc,root,sys,home/user,config}
- sudo mkdir -p "${ROOTDIR}"/run/broker_service
-
- info "Adding runtime.json and config.json files ... "
- sudo cp generic_container_files/config.json "${ROOTDIR}/config"
- sudo cp generic_container_files/runtime.json "${ROOTDIR}/config"
+ sudo mkdir -p "${ROOTDIR}"/{dev,proc,root,sys,home/user,run,tmp,var}
info "Generating squashfs file ... "
- mksquashfs "${ROOTDIR}" "${container_name}.sqsh"
+ local args=(
+ -all-root
+ -noappend
+ )
+ local img="${output}/${container_name}.squashfs"
+ # We need to run through sudo because some files might be read-only by root.
+ sudo mksquashfs "${ROOTDIR}" "${img}" "${args[@]}"
+ sudo chown $(id -u):$(id -g) "${img}"
+ info "Signing squashfs file ... "
+ local verity
+ sign_disk_image "${img}"
+
+ info "Adding config.json ... "
+ sed \
+ -e "s:@APP_NAME@:${container_name}:g" \
+ -e "s:@TERMINAL@:true:g" \
+ -e "s:@USER_UID@:${container_uid}:g" \
+ -e "s:@USER_GID@:${container_gid}:g" \
+ -e "s:@ARGV@:${argv// /\", \"}:g" \
+ -e "s:@VERITY@:${verity}:" \
+ generic_container_files/config.json >"${output}/config.json"
+
+ info "Generating manifest ..."
+ generate_manifest "${container_name}" "${output}"
info "Cleaning up ... "
sudo rm -rf "${ROOTDIR}"
+ mkdir "${ROOTDIR}"
+}
+
+run_emerge() {
+ emerge-${BOARD} \
+ --quiet --jobs ${NUM_JOBS} \
+ --usepkgonly \
+ "$@"
}
# Normal emerge.
install_with_no_deps() {
- local package_to_install="$1"
- local root_dir="$2"
- info "Installing '${package_to_install}' with no deps ... "
- emerge-${BOARD} "${package_to_install}" --nodeps --usepkgonly \
- --root="${root_dir}" --quiet
+ local root_dir="$1"
+ shift
+ info "Installing '$*' with no deps ... "
+ run_emerge --root="${root_dir}" "$@" --nodeps
}
# Emerge with root deps.
install_with_root_deps() {
- local package_to_install="$1"
- local root_dir="$2"
- info "Installing '${package_to_install}' with root deps ... "
- emerge-${BOARD} "${package_to_install}" --root-deps=rdeps --usepkgonly \
- --root="${root_dir}" --quiet
+ local root_dir="$1"
+ shift
+ info "Installing '$*' with root deps ... "
+ run_emerge --root="${root_dir}" "$@" --root-deps=rdeps
}
main() {
. "${BUILD_LIBRARY_DIR}/board_options.sh" || exit 1
. "${BUILD_LIBRARY_DIR}/base_image_util.sh" || exit 1
- if [[ -n "${FLAGS_package}" ]]; then
- if [[ -n "${FLAGS_name}" ]]; then
- local container_name="${FLAGS_name}"
- else
- local container_name="${FLAGS_package}"
- fi
- if [[ -f "${container_name}.sqsh" ]]; then
- die_notrace "File already exists : ${container_name}.sqsh"
- fi
- info "Building container '${container_name}' for ${FLAGS_package} ..."
- build_container_image "${FLAGS_package}" "${container_name}"
- else
+ if [[ -z "${FLAGS_argv}" ]]; then
+ die_notrace "--argv is needed."
+ fi
+ if [[ -z "${FLAGS_package}" ]]; then
die_notrace "--package is needed."
fi
+
+ local container_name="${FLAGS_name}"
+ if [[ -d "${container_name}" ]]; then
+ die_notrace "Output dir already exists : ${container_name}/"
+ fi
+ info "Building container '${container_name}' for ${FLAGS_package} ..."
+ build_container_image "${FLAGS_package}" "${container_name}" "${FLAGS_argv}"
}
main "$@"