diff --git a/docker/BUILD.bazel b/docker/BUILD.bazel index 2ab44e6833..8d66592dfa 100644 --- a/docker/BUILD.bazel +++ b/docker/BUILD.bazel @@ -10,7 +10,7 @@ container_bundle( "scion_cs:latest": ":cs_prod", "scion_dispatcher:latest": ":dispatcher_prod", "scion_sciond:latest": ":sciond_prod", - "scion_sig_nocap:latest": ":sig_prod", + "scion_sig:latest": ":sig_prod", }, ) @@ -21,14 +21,14 @@ container_bundle( "scion_cs_debug:latest": ":cs_debug", "scion_dispatcher_debug:latest": ":dispatcher_debug", "scion_sciond_debug:latest": ":sciond_debug", - "scion_sig_nocap_debug:latest": ":sig_debug", + "scion_sig_debug:latest": ":sig_debug", }, ) container_bundle( name = "test", images = { - "scion_sig_acceptance_nocap:latest": ":scion_sig_acceptance_nocap", + "scion_sig_acceptance:latest": ":scion_sig_acceptance", "scion_tester:latest": ":scion_tester", }, ) @@ -91,6 +91,7 @@ scion_app_images( name = "sig", appdir = "/app", binary = "//go/sig:sig", + caps = "cap_net_admin+ei", entrypoint = [ "/app/sig", "--config", diff --git a/docker/Makefile b/docker/Makefile index c05d68468c..fdb3c7dab6 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -4,18 +4,12 @@ apps: prod test debug: bazel run -c dbg //docker:debug - docker image build -t "scion_sig_debug:latest" -f dockerfiles/sig.debug . - docker image remove scion_sig_nocap_debug prod: bazel run -c opt //docker:prod - docker image build -t "scion_sig:latest" -f dockerfiles/sig.prod . - docker image remove scion_sig_nocap test: bazel run //docker:test - docker build -t "scion_sig_acceptance:latest" -f dockerfiles/sig_accept . - docker image remove scion_sig_acceptance_nocap scionproto/docker-caps clean: docker image ls --filter reference=scion* -q | xargs docker image rm diff --git a/docker/caps.bzl b/docker/caps.bzl new file mode 100644 index 0000000000..a8543eb2d7 --- /dev/null +++ b/docker/caps.bzl @@ -0,0 +1,145 @@ +# Copyright 2017 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Copyright 2020 Anapaya Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This file was initially copied from ocker/docker/util/run.bzl and then adapted, +# see https://github.com/bazelbuild/rules_docker/blob/master/docker/util/run.bzl + +""" +Rules to set capabilities on a container. The container must have the `setcap` +binary in it. +""" + +def _setcap_impl(ctx): + """Implementation for the setcap rule. + This rule sets capabilities on a binary in an image and stores the image. + Args: + ctx: The bazel rule context. + """ + + name = ctx.attr.name + image = ctx.file.image + script = ctx.outputs.build + output_image_tar = ctx.outputs.out + + toolchain_info = ctx.toolchains["@io_bazel_rules_docker//toolchains/docker:toolchain_type"].info + + # Generate a shell script to execute the reset cmd + image_utils = ctx.actions.declare_file("image_util.sh") + ctx.actions.expand_template( + template = ctx.file._image_utils_tpl, + output = image_utils, + substitutions = { + "%{docker_flags}": " ".join(toolchain_info.docker_flags), + "%{docker_tool_path}": toolchain_info.tool_path, + }, + is_executable = True, + ) + + # Generate a shell script to execute the setcap statement + ctx.actions.expand_template( + template = ctx.file._run_tpl, + output = script, + substitutions = { + "%{caps}": ctx.attr.caps, + "%{binary}": ctx.attr.binary, + "%{docker_flags}": " ".join(toolchain_info.docker_flags), + "%{docker_tool_path}": toolchain_info.tool_path, + "%{image_id_extractor_path}": ctx.executable._extract_image_id.path, + "%{image_tar}": image.path, + "%{output_image}": "bazel/%s:%s" % ( + ctx.label.package or "default", + name, + ), + "%{output_tar}": output_image_tar.path, + "%{to_json_tool}": ctx.executable._to_json_tool.path, + "%{util_script}": image_utils.path, + }, + is_executable = True, + ) + + runfiles = [image, image_utils] + + ctx.actions.run( + outputs = [output_image_tar], + inputs = runfiles, + executable = script, + tools = [ctx.executable._extract_image_id, ctx.executable._to_json_tool], + use_default_shell_env = True, + ) + + return struct() + +_setcap_attrs = { + "image": attr.label( + doc = "The image without caps set.", + mandatory = True, + allow_single_file = True, + cfg = "target", + ), + "caps": attr.string( + doc = "The capabilities to add, (example: cap_net_admin+ei)", + mandatory = True, + ), + "binary": attr.string( + doc = "The binary to set the capabilities on, (example: /app/sig)", + mandatory = True, + ), + "_extract_image_id": attr.label( + default = Label("@io_bazel_rules_docker//contrib:extract_image_id"), + cfg = "host", + executable = True, + allow_files = True, + ), + "_image_utils_tpl": attr.label( + default = "@io_bazel_rules_docker//docker/util:image_util.sh.tpl", + allow_single_file = True, + ), + "_run_tpl": attr.label( + default = Label("//docker:setcap.sh.tpl"), + allow_single_file = True, + ), + "_to_json_tool": attr.label( + default = Label("@io_bazel_rules_docker//docker/util:to_json"), + cfg = "host", + executable = True, + allow_files = True, + ), +} +_setcap_outputs = { + "out": "%{name}.tar", + "build": "%{name}.build", +} + +setcap = rule( + attrs = _setcap_attrs, + doc = ("This rules setcap a binary in an image"), + executable = False, + outputs = _setcap_outputs, + implementation = _setcap_impl, + toolchains = ["@io_bazel_rules_docker//toolchains/docker:toolchain_type"], +) diff --git a/docker/dockerfiles/sig.debug b/docker/dockerfiles/sig.debug deleted file mode 100644 index 912581ae98..0000000000 --- a/docker/dockerfiles/sig.debug +++ /dev/null @@ -1,9 +0,0 @@ -# Bazel doesn't have support for adding capabilities to binaries. -# Instead, we create a layer on top of bazel-generated image that -# adds the capabilities as needed. -# For alternative ideas on how to solve this see: -# https://github.com/bazelbuild/rules_docker/issues/752 -# NOTE: this process needs explicit CAP_NET_ADMIN from docker. -# E.g. with `cap_add: NET_ADMIN` from docker-compose. -FROM scion_sig_nocap_debug:latest -RUN ["setcap", "cap_net_admin+ei", "/app/sig"] diff --git a/docker/dockerfiles/sig.prod b/docker/dockerfiles/sig.prod deleted file mode 100644 index 6e2fcedb9d..0000000000 --- a/docker/dockerfiles/sig.prod +++ /dev/null @@ -1,9 +0,0 @@ -# Bazel doesn't have support for adding capabilities to binaries. -# Instead, we create a layer on top of bazel-generated image that -# adds the capabilities as needed. -# For alternative ideas on how to solve this see: -# https://github.com/bazelbuild/rules_docker/issues/752 -# NOTE: this process needs explicit CAP_NET_ADMIN from docker. -# E.g. with `cap_add: NET_ADMIN` from docker-compose. -FROM scion_sig_nocap:latest -RUN ["setcap", "cap_net_admin+ei", "/app/sig"] diff --git a/docker/dockerfiles/sig_accept b/docker/dockerfiles/sig_accept deleted file mode 100644 index 5c71577275..0000000000 --- a/docker/dockerfiles/sig_accept +++ /dev/null @@ -1,11 +0,0 @@ -# Bazel doesn't have support for adding capabilities to binaries. -# Instead, we create a layer on top of bazel-generated image that -# adds the capabilities as needed. -# For alternative ideas on how to solve this see: -# https://github.com/bazelbuild/rules_docker/issues/752 -# NOTE: this process needs explicit CAP_NET_ADMIN from docker. -# E.g. with `cap_add: NET_ADMIN` from docker-compose. -FROM scionproto/docker-caps as caps -FROM scion_sig_acceptance_nocap:latest -COPY --from=caps /bin/setcap /bin -RUN setcap cap_net_admin+ei /app/sig && rm /bin/setcap diff --git a/docker/scion_app.bzl b/docker/scion_app.bzl index e79e40c6e3..5fdf827df4 100644 --- a/docker/scion_app.bzl +++ b/docker/scion_app.bzl @@ -1,6 +1,7 @@ load("@rules_pkg//:pkg.bzl", "pkg_tar") load("@io_bazel_rules_docker//container:container.bzl", "container_image") load("@package_bundle//file:packages.bzl", "packages") +load(":caps.bzl", "setcap") # Defines a common base image for all app images. def scion_app_base(): @@ -63,33 +64,64 @@ def scion_app_base(): # appdir - the directory to deploy the binary to # workdir - working directory # entrypoint - a list of strings that add up to the command line -# stamp - whether to stamp the created images (default=True). -def scion_app_images(name, binary, appdir, workdir, entrypoint, stamp = True): +# caps - capabilities to set on the binary +def scion_app_images(name, binary, appdir, workdir, entrypoint, caps = None): pkg_tar( - name = name + "_docker_files", + name = "%s_docker_files" % name, srcs = [binary], package_dir = appdir, mode = "0755", ) - - container_image( - name = name + "_prod", - repository = "scion", - base = "//docker:app_base", - tars = [":" + name + "_docker_files"], - workdir = workdir, - entrypoint = ["/sbin/su-exec"] + entrypoint, - stamp = stamp, - visibility = ["//visibility:public"], + scion_app_images_internal( + "prod", + "//docker:app_base", + name, + binary, + appdir, + workdir, + ["/sbin/su-exec"] + entrypoint, + caps, ) - - container_image( - name = name + "_debug", - repository = "scion", - base = "//docker:app_base_debug", - tars = [":" + name + "_docker_files"], - workdir = workdir, - entrypoint = entrypoint, - stamp = stamp, - visibility = ["//visibility:public"], + scion_app_images_internal( + "debug", + "//docker:app_base_debug", + name, + binary, + appdir, + workdir, + entrypoint, + caps, ) + +def scion_app_images_internal(suffix, base, name, binary, appdir, workdir, entrypoint, caps): + if not caps: + container_image( + name = "%s_%s" % (name, suffix), + repository = "scion", + base = base, + tars = [":%s_docker_files" % name], + workdir = workdir, + entrypoint = entrypoint, + visibility = ["//visibility:public"], + ) + else: + container_image( + name = "%s_%s_nocap" % (name, suffix), + repository = "scion", + base = base, + tars = [":%s_docker_files" % name], + workdir = workdir, + entrypoint = entrypoint, + ) + setcap( + name = "%s_%s_withcap" % (name, suffix), + image = "%s_%s_nocap.tar" % (name, suffix), + caps = caps, + binary = "%s/%s" % (appdir, name), + ) + container_image( + name = "%s_%s" % (name, suffix), + base = "%s_%s_withcap.tar" % (name, suffix), + entrypoint = entrypoint, + visibility = ["//visibility:public"], + ) diff --git a/docker/setcap.sh.tpl b/docker/setcap.sh.tpl new file mode 100644 index 0000000000..800537acf4 --- /dev/null +++ b/docker/setcap.sh.tpl @@ -0,0 +1,63 @@ +#!/bin/bash + +# Copyright 2017 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Copyright 2020 Anapaya Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This file was initially copied from rules_docker/docker/util/commit.sh.tpl and then adapted, +# see https://github.com/bazelbuild/rules_docker/blob/master/docker/util/commit.sh.tpl + +set -ex + +# Setup tools and load utils +TO_JSON_TOOL="%{to_json_tool}" +source %{util_script} + +# Resolve the docker tool path +DOCKER="%{docker_tool_path}" +DOCKER_FLAGS="%{docker_flags}" + +if [[ -z "$DOCKER" ]]; then + echo >&2 "error: docker not found; do you need to manually configure the docker toolchain?" + exit 1 +fi + +# Load the image and remember its name +image_id=$(%{image_id_extractor_path} %{image_tar}) +$DOCKER $DOCKER_FLAGS load -i %{image_tar} + +id=$($DOCKER $DOCKER_FLAGS run -d --entrypoint setcap $image_id %{caps} %{binary}) +# Actually wait for the container to finish running its commands +retcode=$($DOCKER $DOCKER_FLAGS wait $id) +# Trigger a failure if the run had a non-zero exit status +if [ $retcode != 0 ]; then + $DOCKER $DOCKER_FLAGS logs $id && false +fi + +reset_cmd $image_id $id %{output_image} +$DOCKER $DOCKER_FLAGS save %{output_image} -o %{output_tar} +$DOCKER $DOCKER_FLAGS rm $id diff --git a/docker/sig_tester.bzl b/docker/sig_tester.bzl index 1c19f35107..0a7999f998 100644 --- a/docker/sig_tester.bzl +++ b/docker/sig_tester.bzl @@ -1,6 +1,7 @@ load("@rules_pkg//:pkg.bzl", "pkg_tar") load("@io_bazel_rules_docker//container:container.bzl", "container_bundle", "container_image") load("@package_bundle//file:packages.bzl", "packages") +load(":caps.bzl", "setcap") def build_sigtester_image(): pkg_tar( @@ -36,3 +37,17 @@ def build_sigtester_image(): workdir = "/share", entrypoint = ["./sig.sh"], ) + + setcap( + name = "scion_sig_acceptance_withcap", + image = ":scion_sig_acceptance_nocap.tar", + caps = "cap_net_admin+ei", + binary = "/app/sig", + ) + + container_image( + name = "scion_sig_acceptance", + base = ":scion_sig_acceptance_withcap", + entrypoint = ["./sig.sh"], + visibility = ["//visibility:public"], + )