diff --git a/bin/fluent-cap-ctl b/bin/fluent-cap-ctl new file mode 100755 index 0000000000..b71c2fcea3 --- /dev/null +++ b/bin/fluent-cap-ctl @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby +# -*- coding: utf-8 -*- +here = File.dirname(__FILE__) +$LOAD_PATH << File.expand_path(File.join(here, '..', 'lib')) +require 'fluent/command/cap_ctl' + +Fluent::CapCtl.new.call \ No newline at end of file diff --git a/lib/fluent/command/cap_ctl.rb b/lib/fluent/command/cap_ctl.rb new file mode 100644 index 0000000000..6d80f9f4f8 --- /dev/null +++ b/lib/fluent/command/cap_ctl.rb @@ -0,0 +1,155 @@ +# +# Fluentd +# +# 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. +# + +require 'optparse' +require 'fluent/log' +require 'fluent/env' +require 'fluent/capability' + +module Fluent + class CapCtl + def prepare_option_parser + @op = OptionParser.new + + @op.on('--clear-cap', '--clear-capabilities', "Clear Fluentd Ruby capability") {|s| + @opts[:clear_capabilities] = true + } + + @op.on('--add-cap', '--add-capabilities [CAPABILITITY1, CAPABILITY2, ...]', "Add capabilities into Fluentd Ruby") {|s| + @opts[:add_capabilities] = s + } + + @op.on('--drop-cap', '--drop-capabilities [CAPABILITITY1, CAPABILITY2, ...]', "Drop capabilities into Fluentd Ruby") {|s| + @opts[:drop_capabilities] = s + } + + @op.on('--get-cap', '--get-capabilities', "Get capabilities for Fluentd") {|s| + @opts[:get_capabilities] = true + } + + @op.on('-f', '--file FILE', "Specify target file to add Linux capabilities") {|s| + @opts[:target_file] = s + } + end + + def usage(msg) + puts @op.to_s + puts "error: #{msg}" if msg + exit 1 + end + + def initialize(argv = ARGV) + @opts = {} + @argv = argv + + if Fluent.linux? + begin + require 'capng' + + @capng = CapNG.new + rescue LoadError + puts "Error: capng_c is not loaded. Please install it first." + exit 1 + end + else + puts "Error: This environment is not supported." + exit 2 + end + + prepare_option_parser + end + + def call + parse_options!(@argv) + + target_file = if !!@opts[:target_file] + @opts[:target_file] + else + File.readlink("/proc/self/exe") + end + + if @opts[:clear_capabilities] + clear_capabilities(@opts, target_file) + elsif @opts[:add_capabilities] + add_capabilities(@opts, target_file) + elsif @opts[:drop_capabilities] + drop_capabilities(@opts, target_file) + end + if @opts[:get_capabilities] + get_capabilities(@opts, target_file) + end + end + + def clear_capabilities(opts, target_file) + if !!opts[:clear_capabilities] + @capng.clear(:caps) + ret = @capng.apply_caps_file(target_file) + puts "Clear capabilities #{ret ? 'done' : 'fail'}." + end + end + + def add_capabilities(opts, target_file) + if add_caps = opts[:add_capabilities] + @capng.clear(:caps) + @capng.caps_file(target_file) + capabilities = add_caps.split(/\s*,\s*/) + ret = @capng.update(:add, + CapNG::Type::EFFECTIVE | CapNG::Type::INHERITABLE | CapNG::Type::PERMITTED, + capabilities) + puts "Updating #{add_caps} #{ret ? 'done' : 'fail'}." + ret = @capng.apply_caps_file(target_file) + puts "Adding #{add_caps} #{ret ? 'done' : 'fail'}." + end + end + + def drop_capabilities(opts, target_file) + if drop_caps = opts[:drop_capabilities] + @capng.clear(:caps) + @capng.caps_file(target_file) + capabilities = drop_caps.split(/\s*,\s*/) + ret = @capng.update(:drop, + CapNG::Type::EFFECTIVE | CapNG::Type::INHERITABLE | CapNG::Type::PERMITTED, + capabilities) + puts "Updating #{drop_caps} #{ret ? 'done' : 'fail'}." + @capng.apply_caps_file(target_file) + puts "Dropping #{drop_caps} #{ret ? 'done' : 'fail'}." + end + end + + def get_capabilities(opts, target_file) + if opts[:get_capabilities] + @capng.caps_file(target_file) + print = CapNG::Print.new + puts "Capabilities in '#{target_file}'," + puts "Effective: #{print.caps_text(:buffer, :effective)}" + puts "Inheritable: #{print.caps_text(:buffer, :inheritable)}" + puts "Permitted: #{print.caps_text(:buffer, :permitted)}" + end + end + + def parse_options!(argv) + begin + rest = @op.parse(argv) + + if rest.length != 0 + usage nil + end + rescue + usage $!.to_s + end + end + end +end diff --git a/test/command/test_cap_ctl.rb b/test/command/test_cap_ctl.rb new file mode 100644 index 0000000000..35b3df4d1a --- /dev/null +++ b/test/command/test_cap_ctl.rb @@ -0,0 +1,96 @@ +require_relative '../helper' + +require 'tempfile' +require 'fluent/command/cap_ctl' + +class TestFluentCapCtl < Test::Unit::TestCase + sub_test_case "success" do + test "clear capability" do + logs = capture_stdout do + Fluent::CapCtl.new(["--clear-cap"]).call + end + expression = /\AClear capabilities .*\n/m + assert_match expression, logs + end + + test "add capability" do + logs = capture_stdout do + Fluent::CapCtl.new(["--add-cap", "dac_override"]).call + end + expression = /\AUpdating .* done.\nAdding .*\n/m + assert_match expression, logs + end + + test "drop capability" do + logs = capture_stdout do + Fluent::CapCtl.new(["--drop-cap", "chown"]).call + end + expression = /\AUpdating .* done.\nDropping .*\n/m + assert_match expression, logs + end + + test "get capability" do + logs = capture_stdout do + Fluent::CapCtl.new(["--get-cap"]).call + end + expression = /\ACapabilities in .*,\nEffective: .*\nInheritable: .*\nPermitted: .*/m + assert_match expression, logs + end + end + + sub_test_case "success with file" do + test "clear capability" do + logs = capture_stdout do + Tempfile.create("fluent-cap-") do |tempfile| + Fluent::CapCtl.new(["--clear-cap", "-f", tempfile.path]).call + end + end + expression = /\AClear capabilities .*\n/m + assert_match expression, logs + end + + test "add capability" do + logs = capture_stdout do + Tempfile.create("fluent-cap-") do |tempfile| + Fluent::CapCtl.new(["--add-cap", "dac_override", "-f", tempfile.path]).call + end + end + expression = /\AUpdating .* done.\nAdding .*\n/m + assert_match expression, logs + end + + test "drop capability" do + logs = capture_stdout do + Tempfile.create("fluent-cap-") do |tempfile| + Fluent::CapCtl.new(["--drop-cap", "chown", "-f", tempfile.path]).call + end + end + expression = /\AUpdating .* done.\nDropping .*\n/m + assert_match expression, logs + end + + test "get capability" do + logs = capture_stdout do + Tempfile.create("fluent-cap-") do |tempfile| + Fluent::CapCtl.new(["--get-cap", "-f", tempfile.path]).call + end + end + expression = /\ACapabilities in .*,\nEffective: .*\nInheritable: .*\nPermitted: .*/m + assert_match expression, logs + end + end + + sub_test_case "invalid" do + test "add capability" do + assert_raise(RuntimeError) do + Fluent::CapCtl.new(["--add-cap", "nonexitent"]).call + end + end + + test "drop capability" do + assert_raise(RuntimeError) do + Fluent::CapCtl.new(["--drop-cap", "invalid"]).call + end + end + end +end