Skip to content

Commit f52f5ed

Browse files
authored
Merge pull request #33 from dotconfig404/fix/chown-in-rootless-containers
fix: skip chown in rootless containers via ensure_ownership helper
2 parents bada32e + 016db7f commit f52f5ed

2 files changed

Lines changed: 70 additions & 48 deletions

File tree

lib/puppetserver/ca/utils/file_system.rb

Lines changed: 31 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,26 @@ class FileSystem
1515
:signeddir => 0755
1616
}
1717

18-
def self.instance
19-
@instance ||= new
20-
end
21-
22-
def self.write_file(*args)
23-
instance.write_file(*args)
18+
def self.write_file(path, one_or_more_objects, mode)
19+
File.open(path, 'w', mode) do |f|
20+
Array(one_or_more_objects).each do |object|
21+
f.puts object.to_s
22+
end
23+
end
24+
ensure_ownership(path)
2425
end
2526

2627
def self.ensure_dirs(one_or_more_dirs)
2728
Array(one_or_more_dirs).each do |directory|
28-
instance.ensure_dir(directory)
29+
ensure_dir(directory)
30+
end
31+
end
32+
33+
# Warning: directory mode should be specified in DIR_MODES above
34+
def self.ensure_dir(directory)
35+
if !File.exist?(directory)
36+
FileUtils.mkdir_p(directory, mode: DIR_MODES[directory])
37+
ensure_ownership(directory)
2938
end
3039
end
3140

@@ -53,56 +62,30 @@ def self.check_for_existing_files(one_or_more_paths)
5362
def self.forcibly_symlink(source, link_target)
5463
FileUtils.remove_dir(link_target, true)
5564
FileUtils.symlink(source, link_target)
56-
# Ensure the symlink has the same ownership as the source.
57-
# This requires using `FileUtils.chown` rather than `File.chown`, as
58-
# the latter will update the ownership of the source rather than the
59-
# link itself.
60-
# Symlink permissions are ignored in favor of the source's permissions,
61-
# so we don't have to change those.
62-
source_info = File.stat(source)
63-
FileUtils.chown(source_info.uid, source_info.gid, link_target)
64-
end
65-
66-
def initialize
67-
@user, @group = find_user_and_group
65+
ensure_ownership(link_target)
6866
end
6967

70-
def find_user_and_group
71-
if !running_as_root?
72-
return Process.euid, Process.egid
73-
else
74-
if pe_puppet_exists?
75-
return 'pe-puppet', 'pe-puppet'
76-
else
77-
return 'puppet', 'puppet'
78-
end
79-
end
68+
# Chown the path to the puppet user when running as root.
69+
# Skipped otherwise: a non-root process can only have created the path
70+
# as itself, so ownership is already correct, and chowning to any other
71+
# user would require CAP_CHOWN (unavailable in rootless containers).
72+
#
73+
# Uses `FileUtils.chown` rather than `File.chown` so that when `path`
74+
# is a symlink it operates on the link itself rather than its target.
75+
def self.ensure_ownership(path)
76+
return unless running_as_root?
77+
user = pe_puppet_exists? ? 'pe-puppet' : 'puppet'
78+
group = pe_puppet_exists? ? 'pe-puppet' : 'puppet'
79+
FileUtils.chown(user, group, path)
8080
end
8181

82-
def running_as_root?
82+
def self.running_as_root?
8383
!Gem.win_platform? && Process.euid == 0
8484
end
8585

86-
def pe_puppet_exists?
86+
def self.pe_puppet_exists?
8787
!!(Etc.getpwnam('pe-puppet') rescue nil)
8888
end
89-
90-
def write_file(path, one_or_more_objects, mode)
91-
File.open(path, 'w', mode) do |f|
92-
Array(one_or_more_objects).each do |object|
93-
f.puts object.to_s
94-
end
95-
end
96-
FileUtils.chown(@user, @group, path)
97-
end
98-
99-
# Warning: directory mode should be specified in DIR_MODES above
100-
def ensure_dir(directory)
101-
if !File.exist?(directory)
102-
FileUtils.mkdir_p(directory, mode: DIR_MODES[directory])
103-
FileUtils.chown(@user, @group, directory)
104-
end
105-
end
10689
end
10790
end
10891
end
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
require 'spec_helper'
2+
require 'puppetserver/ca/utils/file_system'
3+
4+
RSpec.describe Puppetserver::Ca::Utils::FileSystem do
5+
describe '.ensure_ownership' do
6+
let(:path) { '/some/path' }
7+
8+
context 'when not running as root' do
9+
before do
10+
allow(described_class).to receive(:running_as_root?).and_return(false)
11+
end
12+
13+
it 'does not change ownership' do
14+
expect(FileUtils).not_to receive(:chown)
15+
described_class.ensure_ownership(path)
16+
end
17+
end
18+
19+
context 'when running as root' do
20+
before do
21+
allow(described_class).to receive(:running_as_root?).and_return(true)
22+
end
23+
24+
it 'chowns to puppet when pe-puppet does not exist' do
25+
allow(described_class).to receive(:pe_puppet_exists?).and_return(false)
26+
27+
expect(FileUtils).to receive(:chown).with('puppet', 'puppet', path)
28+
described_class.ensure_ownership(path)
29+
end
30+
31+
it 'chowns to pe-puppet when pe-puppet exists' do
32+
allow(described_class).to receive(:pe_puppet_exists?).and_return(true)
33+
34+
expect(FileUtils).to receive(:chown).with('pe-puppet', 'pe-puppet', path)
35+
described_class.ensure_ownership(path)
36+
end
37+
end
38+
end
39+
end

0 commit comments

Comments
 (0)