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

Sign up to get free protection for your applications and to get access to all the features.
@@ -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