Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fluent-cap-ctl command to handle add/drop/get/clear capability operations #3162

Merged
merged 5 commits into from
Nov 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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