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

Handle linux capability if available #3155

Merged
merged 11 commits into from
Nov 13, 2020
24 changes: 22 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,44 +9,63 @@ matrix:
include:
- rvm: 2.4.9
os: linux
env: USE_CAPNG=false
- rvm: 2.4.9
os: linux-ppc64le
env: USE_CAPNG=false
- rvm: 2.5.7
os: linux
env: USE_CAPNG=false
- rvm: 2.5.7
os: linux
arch: s390x
dist: xenial
env: USE_CAPNG=false
- rvm: 2.6.5
os: linux
env: USE_CAPNG=false
- rvm: 2.6.6
os: linux
env: USE_CAPNG=true
- rvm: 2.7.0
os: linux
env: USE_CAPNG=false
- rvm: ruby-head
os: linux
env: USE_CAPNG=false
- rvm: ruby-head
os: linux-ppc64le
env: USE_CAPNG=false
- rvm: 2.4.6
os: osx
osx_image: xcode8.3 # OSX 10.12
env: USE_CAPNG=false
- rvm: ruby-head
os: osx
osx_image: xcode8.3 # OSX 10.12
env: USE_CAPNG=false
allow_failures:
- rvm: 2.4.6
os: osx
osx_image: xcode8.3
env: USE_CAPNG=false
- rvm: 2.5.7
os: linux
arch: s390x
dist: xenial
env: USE_CAPNG=false
- rvm: ruby-head
env: USE_CAPNG=false

branches:
only:
- master

before_install:
- gem update --system=3.1.2
before_install: |
gem update --system=3.1.2
if [[ x"${USE_CAPNG}" == "xtrue" ]]; then
echo 'gem "capng_c"' >> Gemfile.local
fi

sudo: false
dist: trusty # for TLSv1.2 support
Expand All @@ -55,3 +74,4 @@ addons:
apt:
packages:
- libgmp3-dev
- libcap-ng-dev
71 changes: 71 additions & 0 deletions lib/fluent/capability.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#
# Fluent
#
# 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 "fluent/env"

if Fluent.linux?
begin
require 'capng'
rescue LoadError
end
end

module Fluent
class Capability
def initialize(target = :current_process, pid_or_path = nil)
if defined?(CapNG)
cosmo0920 marked this conversation as resolved.
Show resolved Hide resolved
@usable = true
@capng = CapNG.new(target, pid_or_path)
else
@usable = false
end
end

def usable?
@usable
end

def apply(select_set)
return nil unless usable?

@capng.apply(select_set)
end

def clear(select_set)
return nil unless usable?

@capng.clear(select_set)
end

def have_capability?(type, capability)
return false unless usable?

@capng.have_capability?(type, capability)
end

def update(action, type, capability_or_capability_array)
return nil unless usable?

@capng.update(action, type, capability_or_capability_array)
end

def have_capabilities?(select_set)
return false unless usable?

@capng.have_capabilities?(select_set)
end
end
end
4 changes: 4 additions & 0 deletions lib/fluent/env.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ module Fluent
def self.windows?
ServerEngine.windows?
end

def self.linux?
/linux/ === RUBY_PLATFORM
end
end
11 changes: 10 additions & 1 deletion lib/fluent/plugin/in_tail.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
require 'fluent/plugin/buffer'
require 'fluent/plugin/parser_multiline'
require 'fluent/variable_store'
require 'fluent/capability'
require 'fluent/plugin/in_tail/position_file'

if Fluent.windows?
Expand Down Expand Up @@ -171,6 +172,7 @@ def configure(conf)
@dir_perm = system_config.dir_permission || Fluent::DEFAULT_DIR_PERMISSION
# parser is already created by parser helper
@parser = parser_create(usage: parser_config['usage'] || @parser_configs.first.usage)
@capability = Fluent::Capability.new(:current_process)
end

def configure_tag
Expand Down Expand Up @@ -250,6 +252,11 @@ def close
close_watcher_handles
end

def have_read_capability?
@capability.have_capability?(:effective, :dac_read_search) ||
@capability.have_capability?(:effective, :dac_override)
end

def expand_paths
date = Fluent::EventTime.now
paths = []
Expand All @@ -263,7 +270,9 @@ def expand_paths
paths += Dir.glob(path).select { |p|
begin
is_file = !File.directory?(p)
if File.readable?(p) && is_file
if (File.readable?(p) ||
cosmo0920 marked this conversation as resolved.
Show resolved Hide resolved
have_read_capability?) &&
is_file
if @limit_recently_modified && File.mtime(p) < (date.to_time - @limit_recently_modified)
false
else
Expand Down
46 changes: 46 additions & 0 deletions test/plugin/test_in_tail.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ def create_driver(conf = SINGLE_LINE_CONFIG, use_common_conf = true)
assert_equal "#{TMP_DIR}/tail.pos", d.instance.pos_file
assert_equal 1000, d.instance.read_lines_limit
assert_equal false, d.instance.ignore_repeated_permission_error
assert_nothing_raised do
d.instance.have_read_capability?
end
end

data("empty" => config_element,
Expand Down Expand Up @@ -1112,6 +1115,49 @@ def count_timer_object
end
end

sub_test_case "path w/ Linux capability" do
def capability_enabled?
if Fluent.linux?
begin
require 'capng'
true
rescue LoadError
false
end
else
false
end
end

setup do
omit "This environment is not enabled Linux capability handling feature" unless capability_enabled?

@capng = CapNG.new(:current_process)
flexstub(Fluent::Capability) do |klass|
klass.should_receive(:new).with(:current_process).and_return(@capng)
end
end

data("dac_read_search" => [:dac_read_search, true, 1],
"dac_override" => [:dac_override, true, 1],
"chown" => [:chown, false, 0],
)
test "with partially elevated privileges" do |data|
cap, result, readable_paths = data
@capng.update(:add, :effective, cap)

d = create_driver(
config_element("ROOT", "", {
"path" => "/var/log/ker*.log", # Use /var/log/kern.log
"tag" => "t1",
"rotate_wait" => "2s"
}) + PARSE_SINGLE_LINE_CONFIG, false)

assert_equal readable_paths, d.instance.expand_paths.length
assert_equal result, d.instance.have_read_capability?
end
end

def test_pos_file_dir_creation
config = config_element("", "", {
"tag" => "tail",
Expand Down
74 changes: 74 additions & 0 deletions test/test_capability.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
require_relative 'helper'
require 'fluent/test'
require 'fluent/capability'

class FluentCapabilityTest < ::Test::Unit::TestCase
setup do
@capability = Fluent::Capability.new(:current_process)
omit "Fluent::Capability class is not usable on this environment" unless @capability.usable?
end

sub_test_case "check capability" do
test "effective" do
@capability.clear(:both)
assert_true @capability.update(:add, :effective, :dac_read_search)
assert_equal CapNG::Result::PARTIAL, @capability.have_capabilities?(:caps)
assert_nothing_raised do
@capability.apply(:caps)
end
assert_equal CapNG::Result::NONE, @capability.have_capabilities?(:bounds)
assert_true @capability.have_capability?(:effective, :dac_read_search)
assert_false @capability.have_capability?(:inheritable, :dac_read_search)
assert_false @capability.have_capability?(:permitted, :dac_read_search)
end

test "inheritable" do
@capability.clear(:both)
capabilities = [:chown, :dac_override]
assert_equal [true, true], @capability.update(:add, :inheritable, capabilities)
assert_equal CapNG::Result::NONE, @capability.have_capabilities?(:caps)
assert_nothing_raised do
@capability.apply(:caps)
end
assert_equal CapNG::Result::NONE, @capability.have_capabilities?(:bounds)
capabilities.each do |capability|
assert_false @capability.have_capability?(:effective, capability)
assert_true @capability.have_capability?(:inheritable, capability)
assert_false @capability.have_capability?(:permitted, capability)
end
end

test "permitted" do
@capability.clear(:both)
capabilities = [:fowner, :fsetid, :kill]
assert_equal [true, true, true], @capability.update(:add, :permitted, capabilities)
assert_equal CapNG::Result::NONE, @capability.have_capabilities?(:caps)
assert_nothing_raised do
@capability.apply(:caps)
end
assert_equal CapNG::Result::NONE, @capability.have_capabilities?(:bounds)
capabilities.each do |capability|
assert_false @capability.have_capability?(:effective, capability)
assert_false @capability.have_capability?(:inheritable, capability)
assert_true @capability.have_capability?(:permitted, capability)
end
end

test "effective/inheritable/permitted" do
@capability.clear(:both)
capabilities = [:setpcap, :net_admin, :net_raw, :sys_boot, :sys_time]
update_type = CapNG::Type::EFFECTIVE | CapNG::Type::INHERITABLE | CapNG::Type::PERMITTED
assert_equal [true, true, true, true, true], @capability.update(:add, update_type, capabilities)
assert_equal CapNG::Result::PARTIAL, @capability.have_capabilities?(:caps)
assert_nothing_raised do
@capability.apply(:caps)
end
assert_equal CapNG::Result::NONE, @capability.have_capabilities?(:bounds)
capabilities.each do |capability|
assert_true @capability.have_capability?(:effective, capability)
assert_true @capability.have_capability?(:inheritable, capability)
assert_true @capability.have_capability?(:permitted, capability)
end
end
end
end