hrr_rb_lxns 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 22886b993b258c672b8f37d59c51108e40027365f5932fe5ae841ec4ccfe4735
4
- data.tar.gz: 0e04c670687c4575bec9e874d5f0748e280452f387ebe9ea7cbd9a06a82823ad
3
+ metadata.gz: c2638d4c8b3db4d56a38f2eb038b9079f8449e20645a875a2a3c11c31099716a
4
+ data.tar.gz: 1b92b52c8db60ba8ebd6b5f517a74daf8125986fc52c1719c51416875ec015d7
5
5
  SHA512:
6
- metadata.gz: b55815a702403feff42698e15b8ada181bf32ac2da3362331b8cf87ccc8f5413462896f1a63181a3736954f743659f4e0d56004919c5bfbbb8774559a6211a92
7
- data.tar.gz: 114b6d13402c24de77662525bfee59c6d3f7e02b680a76903f3810238cdd2ac91ce99cd5096dcbb22e195d9287d7252ecf1b86a6476c170eea1903abf2c3e98e
6
+ metadata.gz: 73a2767cf322ee5ba66f2187e81e5878a4233e11baa32c8a24ee87b1ecede422a52cada11f263af2bcd34c2a9d219e25be467b668fb8e6a7b37591b668bbe2de
7
+ data.tar.gz: c674dbbabb9c3708255dab0480acd8c8be21fe8c0635fc68c694441c2d14b7845b513b3a85074adb10123b941555a0dd3ca7ede5abf16302aa797e4cfccfaaf6
data/README.md CHANGED
@@ -23,7 +23,7 @@ Or install it yourself as:
23
23
 
24
24
  ## Usage
25
25
 
26
- hrr_rb_lxns provides unshare and setns wrappers.
26
+ hrr_rb_lxns provides unshare and setns wrappers, and namespace files information collector.
27
27
 
28
28
  ### Unshare
29
29
 
@@ -78,6 +78,28 @@ else
78
78
  end
79
79
  ```
80
80
 
81
+ HrrRbLxns.unshare also supports mapping UIDs and/or GIDs for user namespace. When `:map_gid` option is specified, the method also writes `deny\n` in /proc/PID/setgroups.
82
+
83
+ ```ruby
84
+ options = {:map_uid => [[0, 0, 1], [1, 10000, 1000]]}
85
+ HrrRbLxns.unshare HrrRbLxns::NEWUSER, options # => 0
86
+ File.read("/proc/self/uid_map").gsub(/ +/, " ") # => " 0 0 1\n 1 10000 1000\n"
87
+
88
+ options = {:map_uid => "0 0 1", :map_gid => "0 0 1"}
89
+ HrrRbLxns.unshare HrrRbLxns::NEWUSER, options # => 0
90
+ File.read("/proc/self/uid_map").gsub(/ +/, " ") # => " 0 0 1\n"
91
+ File.read("/proc/self/setgroups") # => "deny\n"
92
+ File.read("/proc/self/gid_map").gsub(/ +/, " ") # => " 0 0 1\n"
93
+ ```
94
+
95
+ Time namespace provides per-namespace monotonic and boottime clocks. The clock offsets can be set by `:monotonic` and `:boottime` options.
96
+
97
+ ```ruby
98
+ options = {:monotonic => 123.456, :boottime => "123.456"}
99
+ HrrRbLxns.unshare HrrRbLxns::NEWTIME, options # => 0
100
+ File.read("/proc/self/timens_offsets").gsub(/ +/, " ") # => "monotonic 123 456000000\nboottime 123 456000000\n"
101
+ ```
102
+
81
103
  ### Setns
82
104
 
83
105
  HrrRbLxns.setns method wraps around setns(2) system call. The system call associate the caller process's namespace to an existing one, which is disassociated by some other process.
@@ -110,6 +132,22 @@ system "ip netns add ns0"
110
132
  HrrRbLxns.setns HrrRbLxns::NETNET, nil, {network: "/run/netns/ns0"}
111
133
  ```
112
134
 
135
+ ### Files
136
+
137
+ HrrRbLxns.files Collects the caller process's or a specific process's namespace files information, which are the file path and its inode number.
138
+
139
+ ```ruby
140
+ # Collects the caller process's namespace files information
141
+ files = HrrRbLxns.files
142
+ # Collects a specific process's namespace files information
143
+ files = HrrRbLxns.files 12345
144
+
145
+ # Then, paths and their inode numbers are accesible
146
+ files.uts.path # => "/proc/12345/ns/uts"
147
+ files[:ipc].ino # => 4026531839
148
+ files["net"].ino # => 4026531840
149
+ ```
150
+
113
151
  ## Note
114
152
 
115
153
  Some of the namespace operations are not multi-thread friendly. The library expects that only main thread is running before unshare or setns operation.
@@ -2,6 +2,7 @@
2
2
  #define _GNU_SOURCE 1
3
3
  #include <sched.h>
4
4
  #include <linux/version.h>
5
+ #include <time.h>
5
6
 
6
7
  VALUE rb_mHrrRbLxns;
7
8
  VALUE rb_mHrrRbLxnsConst;
@@ -117,4 +118,12 @@ Init_hrr_rb_lxns(void)
117
118
  /* Represents time namespace. */
118
119
  rb_define_const(rb_mHrrRbLxnsConst, "NEWTIME", INT2FIX(CLONE_NEWTIME));
119
120
  #endif
121
+ #ifdef CLOCK_MONOTONIC
122
+ /* Represents monotonic clock. */
123
+ rb_define_const(rb_mHrrRbLxnsConst, "MONOTONIC", INT2FIX(CLOCK_MONOTONIC));
124
+ #endif
125
+ #ifdef CLOCK_BOOTTIME
126
+ /* Represents boottime clock. */
127
+ rb_define_const(rb_mHrrRbLxnsConst, "BOOTTIME", INT2FIX(CLOCK_BOOTTIME));
128
+ #endif
120
129
  }
@@ -0,0 +1,33 @@
1
+ module HrrRbLxns
2
+ class Files
3
+
4
+ # A class that takes a path to a namespace file and collects then keeps its inode.
5
+ #
6
+ # @example
7
+ # file = HrrRbLxns::Files::File.new "/proc/12345/ns/uts"
8
+ # file.path # => "/proc/12345/ns/uts"
9
+ # file.ino # => 4026531839
10
+ #
11
+ class File
12
+
13
+ # Returns the file information of attribute :path.
14
+ #
15
+ # @return [String] The path to the namespace file.
16
+ #
17
+ attr_reader :path
18
+
19
+ # Returns the file information of attribute :ino.
20
+ #
21
+ # @return [Integer,nil] The inode number of the namespace file. If the path is not valid, then ino is nil.
22
+ #
23
+ attr_reader :ino
24
+
25
+ # @param path [String] The path to a namespace file.
26
+ #
27
+ def initialize path
28
+ @path = path
29
+ @ino = ::File.exist?(path) ? ::File.stat(path).ino : nil
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,130 @@
1
+ module HrrRbLxns
2
+
3
+ # Represents namespace files information in /proc/PID/ns/ directory of a process.
4
+ #
5
+ # @example
6
+ # # Collects the caller process's or a specific process's namespace files information
7
+ # files = HrrRbLxns::Files.new
8
+ # files = HrrRbLxns::Files.new 12345
9
+ #
10
+ # # Each namespace file is accessible as a method or a Hash-like key
11
+ # files.uts.path # => "/proc/12345/ns/uts"
12
+ # files[:uts].path # => "/proc/12345/ns/uts"
13
+ # files["uts"].path # => "/proc/12345/ns/uts"
14
+ class Files
15
+ include Enumerable
16
+
17
+ # Returns the file information of attribute :mnt.
18
+ #
19
+ # @return [HrrRbLxns::Files::File]
20
+ #
21
+ attr_reader :mnt
22
+
23
+ # Returns the file information of attribute :uts.
24
+ #
25
+ # @return [HrrRbLxns::Files::File]
26
+ #
27
+ attr_reader :uts
28
+
29
+ # Returns the file information of attribute :ipc.
30
+ #
31
+ # @return [HrrRbLxns::Files::File]
32
+ #
33
+ attr_reader :ipc
34
+
35
+ # Returns the file information of attribute :net.
36
+ #
37
+ # @return [HrrRbLxns::Files::File]
38
+ #
39
+ attr_reader :net
40
+
41
+ # Returns the file information of attribute :pid.
42
+ #
43
+ # @return [HrrRbLxns::Files::File]
44
+ #
45
+ attr_reader :pid
46
+
47
+ # Returns the file information of attribute :pid_for_children.
48
+ #
49
+ # @return [HrrRbLxns::Files::File]
50
+ #
51
+ attr_reader :pid_for_children
52
+
53
+ # Returns the file information of attribute :user.
54
+ #
55
+ # @return [HrrRbLxns::Files::File]
56
+ #
57
+ attr_reader :user
58
+
59
+ # Returns the file information of attribute :cgroup.
60
+ #
61
+ # @return [HrrRbLxns::Files::File]
62
+ #
63
+ attr_reader :cgroup
64
+
65
+ # Returns the file information of attribute :time.
66
+ #
67
+ # @return [HrrRbLxns::Files::File]
68
+ #
69
+ attr_reader :time
70
+
71
+ # Returns the file information of attribute :time_for_children.
72
+ #
73
+ # @return [HrrRbLxns::Files::File]
74
+ #
75
+ attr_reader :time_for_children
76
+
77
+ # @param pid [Integer,String] The pid of a process to collect namespace files information. If nil, assumes that it is the caller process.
78
+ #
79
+ def initialize pid="self"
80
+ @mnt = File.new "/proc/#{pid}/ns/mnt"
81
+ @uts = File.new "/proc/#{pid}/ns/uts"
82
+ @ipc = File.new "/proc/#{pid}/ns/ipc"
83
+ @net = File.new "/proc/#{pid}/ns/net"
84
+ @pid = File.new "/proc/#{pid}/ns/pid"
85
+ @pid_for_children = File.new "/proc/#{pid}/ns/pid_for_children"
86
+ @user = File.new "/proc/#{pid}/ns/user"
87
+ @cgroup = File.new "/proc/#{pid}/ns/cgroup"
88
+ @time = File.new "/proc/#{pid}/ns/time"
89
+ @time_for_children = File.new "/proc/#{pid}/ns/time_for_children"
90
+ end
91
+
92
+ # Returns the file information of the specified key.
93
+ #
94
+ # @example
95
+ # files = HrrRbLxns::Files.new 12345
96
+ # files[:uts].path # => "/proc/12345/ns/uts"
97
+ # files["uts"].path # => "/proc/12345/ns/uts"
98
+ #
99
+ # @param key [Symbol,String] The namespace file name, which is :mnt, :uts, pid, :pid_for_children, ....
100
+ # @return [HrrRbLxns::Files::File]
101
+ #
102
+ def [] key
103
+ __send__ key
104
+ end
105
+
106
+ # Calls the given block once for each key with associated file information.
107
+ # If no block is given, an Enumerator is returned.
108
+ #
109
+ # @example
110
+ # files = HrrRbLxns::Files.new 12345
111
+ # files.each do |type, namespace_file|
112
+ # # at first iteration
113
+ # type # => :cgroup
114
+ # namespace_file.path # => "/proc/12345/ns/cgroup"
115
+ # end
116
+ #
117
+ def each
118
+ keys_with_files = [:mnt, :uts, :ipc, :net, :pid, :pid_for_children, :user, :cgroup, :time, :time_for_children].map{ |key| [key, __send__(key)] }
119
+ if block_given?
120
+ keys_with_files.each do |kv|
121
+ yield kv
122
+ end
123
+ else
124
+ keys_with_files.each
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ require "hrr_rb_lxns/files/file"
@@ -1,3 +1,3 @@
1
1
  module HrrRbLxns
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/hrr_rb_lxns.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "hrr_rb_lxns/version"
2
2
  require "hrr_rb_lxns/hrr_rb_lxns"
3
+ require "hrr_rb_lxns/files"
3
4
  require "hrr_rb_mount"
4
5
 
5
6
  # Utilities working with Linux namespaces for CRuby.
@@ -9,6 +10,20 @@ module HrrRbLxns
9
10
  module Constants
10
11
  end
11
12
 
13
+ # Collects namespace files information in /proc/PID/ns/ directory of a process.
14
+ #
15
+ # @example
16
+ # # Collects the caller process's or a specific process's namespace files information
17
+ # files = HrrRbLxns.files
18
+ # files = HrrRbLxns.files 12345
19
+ # files.uts.path # => "/proc/12345/ns/uts"
20
+ #
21
+ # @param pid [Integer,String] The pid of a process to collect namespace files information. If nil, assumes that it is the caller process.
22
+ # @return [HrrRbLxns::Files]
23
+ def self.files pid="self"
24
+ Files.new pid
25
+ end
26
+
12
27
  # A wrapper around unshare(2) system call.
13
28
  #
14
29
  # @example
@@ -44,18 +59,26 @@ module HrrRbLxns
44
59
  # @option options [String] :cgroup A persistent cgroup namespace to be created by bind mount.
45
60
  # @option options [String] :time A persistent time namespace to be created by bind mount.
46
61
  # @option options [Boolean] :fork If specified, the caller process forks after unshare.
62
+ # @option options [String,Array<String>,Array<#to_i>,Array<Array<#to_i>>] :map_uid If specified, the caller process writes UID map in /proc/PID/uid_map.
63
+ # @option options [String,Array<String>,Array<#to_i>,Array<Array<#to_i>>] :map_gid If specified, the caller process writes deny in /proc/PID/setgroups and GID map in /proc/PID/gid_map.
64
+ # @option options [Numeric,String] :monotonic If specified, writes monotonic offset in /proc/PID/timens_offsets.
65
+ # @option options [Numeric,String] :boottime If specified, writes boottime offset in /proc/PID/timens_offsets.
47
66
  # @return [Integer, nil] Usually 0. If :fork is specified in options, then PID of the child process in parent, nil in child (as same as Kernel.#fork).
48
67
  # @raise [ArgumentError] When given flags argument is not appropriate.
68
+ # @raise [TypeError] When map_uid and/or map_gid value is not appropriate.
49
69
  # @raise [Errno::EXXX] In case unshare(2) system call failed.
50
70
  def self.unshare flags, options={}
51
71
  _flags = interpret_flags flags
52
72
  bind_ns_files_from_child(_flags, options) do
53
- if fork? options
54
- __unshare__ _flags
55
- fork
56
- else
57
- __unshare__ _flags
73
+ ret = nil
74
+ map_uid_gid_from_child(_flags, options) do
75
+ ret = __unshare__ _flags
76
+ set_timens_offsets(_flags, options)
58
77
  end
78
+ if fork?(options)
79
+ ret = fork
80
+ end
81
+ ret
59
82
  end
60
83
  end
61
84
 
@@ -109,15 +132,35 @@ module HrrRbLxns
109
132
  _flags = interpret_flags flags
110
133
  nstype_file_h = get_nstype_file_h _flags, pid, options
111
134
  do_setns nstype_file_h
135
+ return 0
112
136
  end
113
137
 
114
138
  private
115
139
 
116
140
  def self.interpret_flags arg
117
141
  case arg
118
- when Integer then arg
119
- when String then chars_to_flags arg
120
- else raise TypeError, "unsupported flags: #{arg.inspect}"
142
+ when Integer
143
+ check_flags arg
144
+ arg
145
+ when String
146
+ chars_to_flags arg
147
+ else
148
+ raise TypeError, "unsupported flags: #{arg.inspect}"
149
+ end
150
+ end
151
+
152
+ def self.check_flags flags
153
+ valid_flags = 0
154
+ valid_flags += NEWIPC if const_defined?(:NEWIPC)
155
+ valid_flags += NEWNS if const_defined?(:NEWNS)
156
+ valid_flags += NEWNET if const_defined?(:NEWNET)
157
+ valid_flags += NEWPID if const_defined?(:NEWPID)
158
+ valid_flags += NEWUTS if const_defined?(:NEWUTS)
159
+ valid_flags += NEWUSER if const_defined?(:NEWUSER)
160
+ valid_flags += NEWCGROUP if const_defined?(:NEWCGROUP)
161
+ valid_flags += NEWTIME if const_defined?(:NEWTIME)
162
+ unless (flags - (flags & valid_flags)).zero?
163
+ raise ArgumentError, "unsupported flags are set"
121
164
  end
122
165
  end
123
166
 
@@ -214,12 +257,133 @@ module HrrRbLxns
214
257
  end
215
258
  end
216
259
 
260
+ def self.map_uid_gid? flags, options
261
+ const_defined?(:NEWUSER) && (flags & NEWUSER).zero?.! && (options.has_key?(:map_uid) || options.has_key?(:map_gid))
262
+ end
263
+
264
+ # This method calls fork and the child process writes into /proc/PID/uid_map, /proc/PID/gid_map, and /proc/PID/setgroups.
265
+ def self.map_uid_gid_from_child flags, options
266
+ if map_uid_gid? flags, options
267
+ pid_to_map = Process.pid
268
+ IO.pipe do |io_r, io_w|
269
+ if pid = fork
270
+ begin
271
+ ret = yield
272
+ rescue Exception
273
+ Process.kill "KILL", pid
274
+ Process.waitpid pid
275
+ raise
276
+ else
277
+ io_w.write "1"
278
+ io_w.close
279
+ Process.waitpid pid
280
+ raise Marshal.load(io_r.read) unless $?.to_i.zero?
281
+ ret
282
+ end
283
+ else
284
+ begin
285
+ io_r.read 1
286
+ map_uid_gid options, pid_to_map
287
+ rescue Exception => e
288
+ io_w.write Marshal.dump(e)
289
+ exit! false
290
+ else
291
+ exit! true
292
+ end
293
+ end
294
+ end
295
+ else
296
+ yield
297
+ end
298
+ end
299
+
300
+ def self.map_uid_gid options, pid
301
+ if options.has_key?(:map_uid)
302
+ write_id_map options[:map_uid], pid, "uid"
303
+ end
304
+ if options.has_key?(:map_gid)
305
+ deny_setgroups pid
306
+ write_id_map options[:map_gid], pid, "gid"
307
+ end
308
+ end
309
+
310
+ def self.write_id_map mapping, pid, type
311
+ path = "/proc/#{pid}/#{type}_map"
312
+ _mapping = case mapping
313
+ when String # "0 0 1", "0 0 1\n1 10000 1000\n"
314
+ mapping.chomp
315
+ when Array
316
+ if mapping.all?{|e| e.respond_to?(:match?) && e.match?(/^\d+ \d+ \d+$/)} # ["0 0 1", "1 10000 1000"]
317
+ mapping.join("\n")
318
+ elsif mapping.all?{|e| e.respond_to?(:to_i)} # [0, 0, 1], ["0", "0", "1"]
319
+ mapping.map(&:to_i).join(" ")
320
+ elsif mapping.all?{|e| e.respond_to?(:all?) && e.all?{ |e2| e2.respond_to?(:to_i)}} # [[0, 0, 1], ["1", "10000", "1000"]]
321
+ mapping.map{|e| e.map(&:to_i).join(" ")}.join("\n")
322
+ else
323
+ raise TypeError, "map_#{type}"
324
+ end
325
+ else
326
+ raise TypeError, "map_#{type}"
327
+ end
328
+ File.open(path, "w") do |f|
329
+ f.puts _mapping
330
+ end
331
+ end
332
+
333
+ def self.deny_setgroups pid
334
+ File.open("/proc/#{pid}/setgroups", "w") do |f|
335
+ f.puts "deny"
336
+ end
337
+ end
338
+
339
+ def self.set_timens_offsets? flags, options
340
+ const_defined?(:NEWTIME) && (flags & NEWTIME).zero?.! && (options.has_key?(:monotonic) || options.has_key?(:boottime))
341
+ end
342
+
343
+ def self.set_timens_offsets(flags, options)
344
+ if set_timens_offsets? flags, options
345
+ File.open("/proc/self/timens_offsets", "w") do |f|
346
+ [
347
+ [MONOTONIC, :monotonic],
348
+ [BOOTTIME, :boottime ],
349
+ ].each do |clk_id, key|
350
+ if options.has_key? key
351
+ offset_secs = options[key].to_i
352
+ offset_nanosecs = ((Rational(options[key]) - offset_secs) * 10**9).to_i
353
+ f.puts "#{clk_id} #{offset_secs} #{offset_nanosecs}"
354
+ end
355
+ end
356
+ end
357
+ end
358
+ end
359
+
217
360
  def self.do_setns nstype_file_h
218
- nstype_file_h.map{ |nstype, file|
219
- File.open(file, File::RDONLY) do |f|
220
- __setns__ f.fileno, nstype
361
+ list_without_user = nstype_file_h.keys - [NEWUSER]
362
+ success = {}
363
+ error = {}
364
+ nstype_fileobj_h = {}
365
+ begin
366
+ nstype_file_h.each do |nstype, file|
367
+ nstype_fileobj_h[nstype] = File.open(file, File::RDONLY)
221
368
  end
222
- }.max or 0
369
+ (list_without_user + [NEWUSER] + list_without_user).each do |nstype|
370
+ next unless nstype_file_h.has_key? nstype
371
+ begin
372
+ next if success.has_key? nstype
373
+ fileobj = nstype_fileobj_h[nstype]
374
+ __setns__ fileobj.fileno, nstype
375
+ rescue
376
+ raise if error.has_key? nstype
377
+ error[nstype] = true
378
+ else
379
+ success[nstype] = true
380
+ end
381
+ end
382
+ ensure
383
+ nstype_fileobj_h.each do |nstype, fileobj|
384
+ fileobj.close
385
+ end
386
+ end
223
387
  end
224
388
 
225
389
  def self.get_nstype_file_h flags, pid, options
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hrr_rb_lxns
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - hirura
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-02 00:00:00.000000000 Z
11
+ date: 2020-05-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hrr_rb_mount
@@ -47,6 +47,8 @@ files:
47
47
  - ext/hrr_rb_lxns/hrr_rb_lxns.h
48
48
  - hrr_rb_lxns.gemspec
49
49
  - lib/hrr_rb_lxns.rb
50
+ - lib/hrr_rb_lxns/files.rb
51
+ - lib/hrr_rb_lxns/files/file.rb
50
52
  - lib/hrr_rb_lxns/version.rb
51
53
  homepage: https://github.com/hirura/hrr_rb_lxns
52
54
  licenses: