bwrap 1.0.0.pre.beta1 → 1.1.0.pre.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Implementation for Ruby feature set.
4
+ #
5
+ # @api private
6
+ class Bwrap::Args::Features::RubyBinds < Bwrap::Args::Features::BindsBase
7
+ # Returns mounts needed by Ruby feature set.
8
+ attr_reader :mounts
9
+
10
+ # Bind system paths so scripts works inside sandbox.
11
+ def sitedir_mounts
12
+ raise "@config is required" unless @config
13
+
14
+ ruby_config = @config.features.ruby.ruby_config
15
+
16
+ mounts = []
17
+ mounts << "--ro-bind" << ruby_config["sitedir"] << ruby_config["sitedir"]
18
+ mounts << "--ro-bind" << ruby_config["rubyhdrdir"] << ruby_config["rubyhdrdir"]
19
+ mounts << "--ro-bind" << ruby_config["rubylibdir"] << ruby_config["rubylibdir"]
20
+ mounts << "--ro-bind" << ruby_config["vendordir"] << ruby_config["vendordir"]
21
+
22
+ mounts
23
+ end
24
+
25
+ # Create binds for required system libraries.
26
+ #
27
+ # These are in path like /usr/lib64/ruby/2.5.0/x86_64-linux-gnu/,
28
+ # and as they are mostly shared libraries, they may have some extra
29
+ # dependencies that also need to be bound inside the sandbox.
30
+ def stdlib_mounts stdlib
31
+ raise "@config is required" unless @config
32
+
33
+ ruby_config = @config.features.ruby.ruby_config
34
+
35
+ library_mounts = []
36
+ library = Bwrap::Args::Library.new
37
+ stdlib.each do |lib|
38
+ path = "#{ruby_config["rubyarchdir"]}/#{lib}.so"
39
+
40
+ library.needed_libraries(path).each do |requisite_library|
41
+ library_mounts << "--ro-bind" << requisite_library << requisite_library
42
+ end
43
+ end
44
+
45
+ library_mounts
46
+ end
47
+ end
@@ -8,6 +8,12 @@ require_relative "library"
8
8
  #
9
9
  # @see Config::Features
10
10
  class Bwrap::Args::Features < Hash
11
+ # Requires are here so there is no extra trickiness with namespaces.
12
+ #
13
+ # Feature implementations are not meant to be used outside of this class anyway.
14
+ require_relative "features/binds_base"
15
+ require_relative "features/ruby_binds"
16
+
11
17
  include Bwrap::Output
12
18
 
13
19
  # Implementation for Bash feature set.
@@ -29,41 +35,19 @@ class Bwrap::Args::Features < Hash
29
35
  end
30
36
  end
31
37
 
32
- # Implementation for Ruby feature set.
38
+ # Implementation for nscd feature set.
33
39
  #
34
40
  # @api private
35
- class RubyBinds
36
- # Returns mounts needed by Ruby feature set.
37
- attr_reader :mounts
38
-
39
- # Bind system paths so scripts works inside sandbox.
40
- def sitedir_mounts
41
+ class NscdBinds
42
+ # Custom binds needed by the feature.
43
+ def custom_binds
41
44
  mounts = []
42
- mounts << "--ro-bind" << RbConfig::CONFIG["sitedir"] << RbConfig::CONFIG["sitedir"]
43
- mounts << "--ro-bind" << RbConfig::CONFIG["rubyhdrdir"] << RbConfig::CONFIG["rubyhdrdir"]
44
- mounts << "--ro-bind" << RbConfig::CONFIG["rubylibdir"] << RbConfig::CONFIG["rubylibdir"]
45
- mounts << "--ro-bind" << RbConfig::CONFIG["vendordir"] << RbConfig::CONFIG["vendordir"]
46
-
47
- mounts
48
- end
49
45
 
50
- # Create binds for required system libraries.
51
- #
52
- # These are in path like /usr/lib64/ruby/2.5.0/x86_64-linux-gnu/,
53
- # and as they are mostly shared libraries, they may have some extra
54
- # dependencies that also need to be bound inside the sandbox.
55
- def stdlib_mounts stdlib
56
- library_mounts = []
57
- library = Bwrap::Args::Library.new
58
- stdlib.each do |lib|
59
- path = "#{RbConfig::CONFIG["rubyarchdir"]}/#{lib}.so"
60
-
61
- library.needed_libraries(path).each do |requisite_library|
62
- library_mounts << "--ro-bind" << requisite_library << requisite_library
63
- end
64
- end
46
+ # TODO: Probably some path checking is needed here. Or somewhere.
47
+ # TODO: Since on many systems /var/run is symlinked to /run, that probably should be handled.
48
+ mounts << "--ro-bind" << "/var/run/nscd" << "/var/run/nscd"
65
49
 
66
- library_mounts
50
+ mounts
67
51
  end
68
52
  end
69
53
 
@@ -79,6 +63,7 @@ class Bwrap::Args::Features < Hash
79
63
  # - ruby
80
64
  def feature_binds
81
65
  bash_binds
66
+ nscd_binds
82
67
  ruby_binds
83
68
  end
84
69
 
@@ -87,7 +72,15 @@ class Bwrap::Args::Features < Hash
87
72
 
88
73
  binds = BashBinds.new
89
74
 
90
- @args.append binds.bash_mounts
75
+ @args.add :feature_binds, binds.bash_mounts
76
+ end
77
+
78
+ private def nscd_binds
79
+ return unless @config.features.nscd.enabled?
80
+
81
+ binds = NscdBinds.new
82
+
83
+ @args.add :feature_binds, binds.custom_binds
91
84
  end
92
85
 
93
86
  # @note This does not allow development headers needed for compilation for now.
@@ -95,9 +88,9 @@ class Bwrap::Args::Features < Hash
95
88
  private def ruby_binds
96
89
  return unless @config.features.ruby.enabled?
97
90
 
98
- binds = RubyBinds.new
91
+ binds = RubyBinds.new @config
99
92
 
100
- @args.append binds.sitedir_mounts
101
- @args.append binds.stdlib_mounts(@config.features.ruby.stdlib)
93
+ @args.add :feature_binds, binds.sitedir_mounts
94
+ @args.add :feature_binds, binds.stdlib_mounts(@config.features.ruby.stdlib)
102
95
  end
103
96
  end
@@ -66,8 +66,6 @@ class Bwrap::Args::Library
66
66
  @needed_libraries
67
67
  end
68
68
 
69
- # Used by {Bwrap::Args::Bind#libs_command_requires}.
70
- #
71
69
  # @param binary_paths [String, Array] one or more paths to be resolved
72
70
  def needed_libraries binary_paths
73
71
  trace "Finding libraries #{binary_paths} requires"
@@ -105,7 +103,7 @@ class Bwrap::Args::Library
105
103
  libraries = libraries_line.split ","
106
104
 
107
105
  (@needed_libraries & libraries).each do |library|
108
- verb "Binding #{library} as dependency of #{binary_path}"
106
+ debug "Binding #{library} as dependency of #{binary_path}"
109
107
  end
110
108
 
111
109
  @needed_libraries |= libraries
@@ -25,7 +25,7 @@ class Bwrap::Args::MachineId
25
25
  # Returning [] means that execute() will ignore this fully.
26
26
  # Nil would be converted to empty string, causing spawn() to pass it as argument, causing
27
27
  # bwrap to misbehave.
28
- return unless @config.machine_id
28
+ return unless @config&.machine_id
29
29
 
30
30
  machine_id = @config.machine_id
31
31
 
@@ -52,10 +52,10 @@ class Bwrap::Args::MachineId
52
52
  debug "Using random machine id as /etc/machine-id"
53
53
 
54
54
  @machine_id_file = Tempfile.new "bwrap-random_machine_id-", @config.tmpdir
55
- @machine_id_file.write SecureRandom.uuid.delete("-", "")
55
+ @machine_id_file.write SecureRandom.uuid.tr("-", "")
56
56
  @machine_id_file.flush
57
57
 
58
- %W{ --ro-bind-data #{machine_id_file.fileno} /etc/machine-id }
58
+ %W{ --ro-bind-data #{@machine_id_file.fileno} /etc/machine-id }
59
59
  end
60
60
 
61
61
  # Uses `10000000000000000000000000000000` as machine id.
@@ -80,6 +80,8 @@ class Bwrap::Args::MachineId
80
80
  end
81
81
 
82
82
  # Uses file inside sandbox directory as machine id.
83
+ #
84
+ # TODO: I kind of want to deprecate this one. It may make sense, but eh... Let’s see.
83
85
  private def machine_id_inside_sandbox_dir sandbox_directory
84
86
  machine_id_file = "#{sandbox_directory}/machine-id"
85
87
 
@@ -9,24 +9,25 @@ module Bwrap::Args::Mount
9
9
  return unless @config.root
10
10
 
11
11
  debug "Binding #{@config.root} as /"
12
- @args.append %W{ --bind #{@config.root} / }
12
+ @args.add :root_mount, "--bind", @config.root, "/"
13
+ @args.add :root_mount, "--chdir", "/"
13
14
  end
14
15
 
15
16
  # Arguments for mounting devtmpfs to /dev.
16
17
  private def dev_mount
17
18
  debug "Mounting new devtmpfs to /dev"
18
- @args.append %w{ --dev /dev }
19
+ @args.add :dev_mounts, "--dev", "/dev"
19
20
  end
20
21
 
21
22
  # Arguments for mounting procfs to /proc.
22
23
  private def proc_mount
23
24
  debug "Mounting new procfs to /proc"
24
- @args.append %w{ --proc /proc }
25
+ @args.add :proc_mount, "--proc", "/proc"
25
26
  end
26
27
 
27
28
  # Arguments for mounting tmpfs to /tmp.
28
29
  private def tmp_as_tmpfs
29
30
  debug "Mounting tmpfs to /tmp"
30
- @args.append %w{ --tmpfs /tmp }
31
+ @args.add :tmp_mount, "--tmpfs", "/tmp"
31
32
  end
32
33
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bwrap/output"
4
+ require_relative "args"
5
+
6
+ # Network related binds.
7
+ class Bwrap::Args::Network
8
+ include Bwrap::Output
9
+
10
+ # Instance of {Config}.
11
+ attr_writer :config
12
+
13
+ # @param args [Bwrap::Args::Args] Arguments to be passed to bwrap.
14
+ def initialize args
15
+ @args = args
16
+ end
17
+
18
+ # Arguments to set hostname to whatever is configured.
19
+ def hostname
20
+ return unless @config.hostname
21
+
22
+ debug "Setting hostname to #{@config.hostname}"
23
+ @args.add :hostname, %W{ --hostname #{@config.hostname} }
24
+ end
25
+
26
+ # Arguments to read-only bind /etc/resolv.conf.
27
+ def resolv_conf
28
+ # We can’t really bind symlinks, so let’s resolve real path to resolv.conf, in case it is symlinked.
29
+ source_resolv_conf = Pathname.new "/etc/resolv.conf"
30
+ source_resolv_conf = source_resolv_conf.realpath
31
+
32
+ debug "Binding #{source_resolv_conf} as /etc/resolv.conf"
33
+ @args.add :resolv_conf, %W{ --ro-bind #{source_resolv_conf} /etc/resolv.conf }
34
+ end
35
+
36
+ # Arguments to allow network connection inside sandbox.
37
+ def share_net
38
+ return unless @config.share_net
39
+
40
+ verb "Sharing network"
41
+ @args.add :network, %w{ --share-net }
42
+ end
43
+ end
data/lib/bwrap/bwrap.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  #require "deep-cover" if ENV["DEEP_COVER"]
4
4
 
5
+ require_relative "bwrap_module"
5
6
  require "bwrap/version"
6
7
  require "bwrap/args/construct"
7
8
  require "bwrap/config"
@@ -54,7 +55,9 @@ class Bwrap::Bwrap
54
55
  construct = Bwrap::Args::Construct.new
55
56
  construct.command = command
56
57
  construct.config = @config
57
- bwrap_args = construct.construct_bwrap_args
58
+
59
+ construct.calculate
60
+ bwrap_args = construct.bwrap_arguments
58
61
 
59
62
  exec_command = [ "bwrap" ]
60
63
  exec_command += bwrap_args
@@ -88,7 +91,9 @@ class Bwrap::Bwrap
88
91
  construct = Bwrap::Args::Construct.new
89
92
  construct.command = command
90
93
  construct.config = config
91
- bwrap_args = construct.construct_bwrap_args
94
+
95
+ construct.calculate
96
+ bwrap_args = construct.bwrap_arguments
92
97
 
93
98
  exec_command = [ "bwrap" ]
94
99
  exec_command += bwrap_args
@@ -133,6 +138,9 @@ class Bwrap::Bwrap
133
138
  opt :trace,
134
139
  "Show trace output (noisiest, probably not useful for most of time)",
135
140
  short: :none
141
+ opt [ :quiet, :silent ],
142
+ "Hides notice messages. Warnings and errors are still shown.",
143
+ short: :none
136
144
  opt :version,
137
145
  "Print version and exit",
138
146
  short: "V"
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ruby-bwrap provides easy-to-use interface to run complex programs in sandboxes created with
4
+ # {https://github.com/containers/bubblewrap bubblewrap}.
5
+ #
6
+ # To run a program inside bubblewrap, a wrapper executable can be created. For example:
7
+ #
8
+ # require "bwrap"
9
+ #
10
+ # config = Bwrap::Config.new
11
+ # config.user = "dummy_user"
12
+ # config.full_system_mounts = true
13
+ # config.binaries_from = %w{
14
+ # /bin
15
+ # /usr/bin
16
+ # }
17
+ #
18
+ # bwrap = Bwrap::Bwrap.new config
19
+ # bwrap.parse_command_line_arguments
20
+ # bwrap.run "/bin/true"
21
+ #
22
+ # There also are few generic utilities, {Bwrap::Output} for handling output of scripts and
23
+ # {Bwrap::Execution} to run executables.
24
+ module Bwrap
25
+ # Empty module.
26
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @abstract
4
+ #
5
+ # Base of all features.
6
+ class Bwrap::Config::Features::Base
7
+ # @param features [Bwrap::Config::Features] Instance of features object in {Config}
8
+ def initialize features
9
+ @features = features
10
+ end
11
+
12
+ # Checks if the feature has been enabled.
13
+ #
14
+ # @return [Boolean] whether feature is enabled
15
+ def enabled?
16
+ @enabled
17
+ end
18
+
19
+ # Enable the feature.
20
+ def enable
21
+ @enabled = true
22
+ end
23
+
24
+ # Disable the feature.
25
+ def disable
26
+ @enabled = false
27
+ end
28
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bwrap/execution"
4
+
5
+ # Defines Ruby feature set.
6
+ #
7
+ # Implies {Nscd} feature.
8
+ class Bwrap::Config::Features::Ruby < Bwrap::Config::Features::Base
9
+ include Bwrap::Execution
10
+
11
+ def initialize features
12
+ super features
13
+
14
+ @gem_env_paths = true
15
+ @stdlib = []
16
+ end
17
+
18
+ # @return true if bindirs from “gem environment” should be added to sandbox.
19
+ def gem_env_paths?
20
+ @gem_env_paths
21
+ end
22
+
23
+ # Enable Ruby feature set.
24
+ #
25
+ # Among others, binds `RbConfig::CONFIG["sitedir"]` so scripts works.
26
+ #
27
+ # @note This does not allow development headers needed for compilation for now.
28
+ # I’ll look at it after I have an use for it.
29
+ #
30
+ # @note Also enables {Nscd} feature.
31
+ def enable
32
+ super
33
+
34
+ @features.nscd.enable
35
+ end
36
+
37
+ # @return [Pathname|nil] path to Ruby interpreter.
38
+ def interpreter
39
+ @features.mime&.executable_path
40
+ end
41
+
42
+ # @return [Hash(String, String)] RbConfig::CONFIG from interpreter returned by {#interpreter}
43
+ def ruby_config
44
+ unless interpreter
45
+ raise "Interpreter is not set, so ruby_config() can’t be called yet."
46
+ end
47
+
48
+ return @ruby_config if @ruby_config
49
+
50
+ script = %q{RbConfig::CONFIG.each { |k, v| puts "#{k}=#{v}" }}
51
+ config_string = execvalue %W{ #{interpreter} -e #{script} }
52
+
53
+ ruby_config = {}
54
+ config_string.split("\n").each do |line|
55
+ key, value = line.split "=", 2
56
+ ruby_config[key] = value
57
+ end
58
+
59
+ @ruby_config = ruby_config
60
+ end
61
+
62
+ # Extra libraries to be loaded from script’s interpreter’s `RbConfig::CONFIG["rubyarchdir"]`.
63
+ #
64
+ # @note Existence of library paths are checked here, and not in {#stdlib=},
65
+ # to avoid error about missing interpreter, as it is set in {Bwrap::Bwrap#run},
66
+ # and config is meant to be created beforehand.
67
+ #
68
+ # @note This is only required to be called if extra dependencies are necessary.
69
+ # For example, psych.so requires libyaml.so.
70
+ #
71
+ # @return [Array] list of needed libraries.
72
+ def stdlib
73
+ # Just a little check to have error earlier.
74
+ @stdlib.each do |lib|
75
+ unless File.exist? "#{ruby_config["rubyarchdir"]}/#{lib}.so"
76
+ raise "Library “#{lib}” passed to Bwrap::Config::Ruby.stdlib= does not exist."
77
+ end
78
+ end
79
+
80
+ @stdlib
81
+ end
82
+
83
+ def stdlib= libs
84
+ @stdlib = libs
85
+ end
86
+ end
@@ -3,79 +3,42 @@
3
3
  class Bwrap::Config
4
4
  # Methods to enable or disable feature sets to control various aspects of sandboxing.
5
5
  class Features
6
- # Defines Bash feature set.
7
- class Bash
8
- def enabled?
9
- @enabled
10
- end
6
+ # Requires are here so there is no extra trickiness with namespaces.
7
+ #
8
+ # Feature implementations are not meant to be used outside of this class anyway.
9
+ require_relative "features/base"
10
+ require_relative "features/ruby"
11
11
 
12
- def enable
13
- @enabled = true
14
- end
12
+ # Instance of {Bwrap::Args::Bind::Mime}.
13
+ attr_accessor :mime
15
14
 
16
- # Disable Bash feature set.
17
- def disable
18
- @enabled = false
19
- end
15
+ # Defines Bash feature set.
16
+ class Bash < Base
17
+ # Nya.
20
18
  end
21
19
 
22
- # Defines Ruby feature set.
23
- class Ruby
24
- # Extra libraries to be loaded from `RbConfig::CONFIG["rubyarchdir"]`.
25
- #
26
- # @note This is only required to be called if extra dependencies are necessary.
27
- # For example, psych.so requires libyaml.so.
28
- #
29
- # @note There is stdlib= method also. Yardoc is broken.
30
- #
31
- # @return [Array] list of needed libraries.
32
- attr_reader :stdlib
33
-
34
- def initialize
35
- @stdlib = []
36
- end
37
-
38
- # @see enabled=
39
- def enabled?
40
- @enabled
41
- end
42
-
43
- # Enable Ruby feature set.
44
- #
45
- # Among others, binds `RbConfig::CONFIG["sitedir"]` so scripts works.
46
- #
47
- # @note This does not allow development headers needed for compilation for now.
48
- # I’ll look at it after I have an use for it.
49
- def enable
50
- @enabled = true
51
- end
52
-
53
- # Disable Ruby feature set.
54
- def disable
55
- @enabled = false
56
- end
57
-
58
- # @see #stdlib
59
- def stdlib= libs
60
- # Just a little check to have error earlier.
61
- libs.each do |lib|
62
- unless File.exist? "#{RbConfig::CONFIG["rubyarchdir"]}/#{lib}.so"
63
- raise "Library “#{lib}” passed to Bwrap::Config::Ruby.stdlib= does not exist."
64
- end
65
- end
66
-
67
- @stdlib = libs
68
- end
20
+ # Defines Nscd feature set.
21
+ #
22
+ # nscd is short of name service cache daemon. It may make sense to
23
+ # have this class under another name, but I don’t know how nscd specific
24
+ # this feature can be, so this name it is for now.
25
+ class Nscd < Base
26
+ # Nya.
69
27
  end
70
28
 
71
29
  # @return [Bash] Instance of feature class for Bash
72
30
  def bash
73
- @bash ||= Bash.new
31
+ @bash ||= Bash.new self
32
+ end
33
+
34
+ # @return [Nscd] Instance of feature class for nscd
35
+ def nscd
36
+ @nscd ||= Nscd.new self
74
37
  end
75
38
 
76
39
  # @return [Ruby] Instance of feature class for Ruby
77
40
  def ruby
78
- @ruby ||= Ruby.new
41
+ @ruby ||= Ruby.new self
79
42
  end
80
43
  end
81
44
  end