diff --git a/.travis.yml b/.travis.yml index 722112bc7f..4d06dc90eb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 @@ -55,3 +74,4 @@ addons: apt: packages: - libgmp3-dev + - libcap-ng-dev diff --git a/lib/fluent/capability.rb b/lib/fluent/capability.rb new file mode 100644 index 0000000000..23f419d554 --- /dev/null +++ b/lib/fluent/capability.rb @@ -0,0 +1,87 @@ +# +# 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 + if defined?(CapNG) + class Capability + def initialize(target = nil, pid = nil) + @capng = CapNG.new(target, pid) + end + + def usable? + true + end + + def apply(select_set) + @capng.apply(select_set) + end + + def clear(select_set) + @capng.clear(select_set) + end + + def have_capability?(type, capability) + @capng.have_capability?(type, capability) + end + + def update(action, type, capability_or_capability_array) + @capng.update(action, type, capability_or_capability_array) + end + + def have_capabilities?(select_set) + @capng.have_capabilities?(select_set) + end + end + else + class Capability + def initialize(target = nil, pid = nil) + end + + def usable? + false + end + + def apply(select_set) + false + end + + def clear(select_set) + false + end + + def have_capability?(type, capability) + false + end + + def update(action, type, capability_or_capability_array) + false + end + + def have_capabilities?(select_set) + false + end + end + end +end diff --git a/lib/fluent/env.rb b/lib/fluent/env.rb index 01eba2f64b..2b0bf5c8d7 100644 --- a/lib/fluent/env.rb +++ b/lib/fluent/env.rb @@ -28,4 +28,8 @@ module Fluent def self.windows? ServerEngine.windows? end + + def self.linux? + /linux/ === RUBY_PLATFORM + end end diff --git a/lib/fluent/plugin/in_tail.rb b/lib/fluent/plugin/in_tail.rb index 4c2b8a3d19..632e5c7b19 100644 --- a/lib/fluent/plugin/in_tail.rb +++ b/lib/fluent/plugin/in_tail.rb @@ -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? @@ -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 @@ -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 = [] @@ -263,7 +270,7 @@ 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) || have_read_capability?) && is_file if @limit_recently_modified && File.mtime(p) < (date.to_time - @limit_recently_modified) false else diff --git a/test/plugin/test_in_tail.rb b/test/plugin/test_in_tail.rb index 504c5035f3..9658ec6e91 100644 --- a/test/plugin/test_in_tail.rb +++ b/test/plugin/test_in_tail.rb @@ -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, @@ -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", diff --git a/test/test_capability.rb b/test/test_capability.rb new file mode 100644 index 0000000000..bfacdc928b --- /dev/null +++ b/test/test_capability.rb @@ -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