Skip to content

Commit

Permalink
Merge pull request #3162 from fluent/linux-capability-ctl
Browse files Browse the repository at this point in the history
Add fluent-cap-ctl command to handle add/drop/get/clear capability operations
  • Loading branch information
cosmo0920 authored Nov 17, 2020
2 parents 5e036cf + 8e0a1e2 commit 210013e
Show file tree
Hide file tree
Showing 3 changed files with 281 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
174 changes: 174 additions & 0 deletions lib/fluent/command/cap_ctl.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
#
# 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', "Clear Fluentd Ruby capability") {|s|
@opts[:clear_capabilities] = true
}

@op.on('--add [CAPABILITITY1,CAPABILITY2, ...]', "Add capabilities into Fluentd Ruby") {|s|
@opts[:add_capabilities] = s
}

@op.on('--drop [CAPABILITITY1,CAPABILITY2, ...]', "Drop capabilities from Fluentd Ruby") {|s|
@opts[:drop_capabilities] = s
}

@op.on('--get', "Get capabilities for Fluentd Ruby") {|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*/)
check_capabilities(capabilities, get_valid_capabilities)
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*/)
check_capabilities(capabilities, get_valid_capabilities)
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 get_valid_capabilities
capabilities = []
cap = CapNG::Capability.new
cap.each do |_code, capability|
capabilities << capability
end
capabilities
end

def check_capabilities(capabilities, valid_capabilities)
capabilities.each do |capability|
unless valid_capabilities.include?(capability)
raise ArgumentError, "'#{capability}' is not valid capability. Valid Capabilities are: #{valid_capabilities.join(", ")}"
end
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
100 changes: 100 additions & 0 deletions test/command/test_cap_ctl.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
require_relative '../helper'

require 'tempfile'
require 'fluent/command/cap_ctl'

class TestFluentCapCtl < Test::Unit::TestCase
setup do
omit "This environment does not handle Linux capability" unless defined?(CapNG)
end

sub_test_case "success" do
test "clear capability" do
logs = capture_stdout do
Fluent::CapCtl.new(["--clear"]).call
end
expression = /\AClear capabilities .*\n/m
assert_match expression, logs
end

test "add capability" do
logs = capture_stdout do
Fluent::CapCtl.new(["--add", "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", "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"]).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", "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", "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", "-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(ArgumentError) do
Fluent::CapCtl.new(["--add", "nonexitent"]).call
end
end

test "drop capability" do
assert_raise(ArgumentError) do
Fluent::CapCtl.new(["--drop", "invalid"]).call
end
end
end
end

0 comments on commit 210013e

Please sign in to comment.