Skip to content

Commit

Permalink
command: Add flunet-cap-ctl to manage Linux capability for Fluentd Ruby
Browse files Browse the repository at this point in the history
Signed-off-by: Hiroshi Hatake <[email protected]>
  • Loading branch information
cosmo0920 committed Nov 10, 2020
1 parent 9a4ad43 commit fa89e68
Show file tree
Hide file tree
Showing 3 changed files with 258 additions and 0 deletions.
7 changes: 7 additions & 0 deletions bin/fluent-cap-ctl
Original file line number Diff line number Diff line change
@@ -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
155 changes: 155 additions & 0 deletions lib/fluent/command/cap_ctl.rb
Original file line number Diff line number Diff line change
@@ -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
96 changes: 96 additions & 0 deletions test/command/test_cap_ctl.rb
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit fa89e68

Please sign in to comment.