raise-if-root 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rspec +3 -0
- data/.rubocop-disables.yml +137 -0
- data/.rubocop.yml +3 -0
- data/.travis.yml +24 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +676 -0
- data/README.md +92 -0
- data/Rakefile +15 -0
- data/lib/raise-if-root.rb +3 -0
- data/lib/raise-if-root/library.rb +169 -0
- data/lib/raise-if-root/version.rb +4 -0
- data/raise-if-root.gemspec +34 -0
- data/spec/spec_helper.rb +88 -0
- data/spec/unit/raise-if-root/library_spec.rb +140 -0
- data/spec/unit/raise-if-root_spec.rb +13 -0
- metadata +151 -0
data/README.md
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# Raise If Root
|
2
|
+
|
3
|
+
[](https://rubygems.org/gems/raise-if-root)
|
4
|
+
[](https://travis-ci.org/ab/raise-if-root)
|
5
|
+
[](https://codeclimate.com/github/ab/raise-if-root)
|
6
|
+
[](http://www.rubydoc.info/github/ab/raise-if-root/master)
|
7
|
+
|
8
|
+
*Raise If Root* is a small gem that helps prevent your application from ever
|
9
|
+
running as the root user (uid 0).
|
10
|
+
|
11
|
+
## Why?
|
12
|
+
|
13
|
+
Many software systems rely on user privilege separation for security reasons.
|
14
|
+
Especially within containers or chroots, running as a non-privileged user gives
|
15
|
+
stronger isolation.
|
16
|
+
|
17
|
+
*Raise If Root* helps enforce that you never inadvertently load your
|
18
|
+
application code as root.
|
19
|
+
|
20
|
+
Will it protect you if your attacker is already running as root? Probably not.
|
21
|
+
But it does help remove opportunities for error, where you might accidentally
|
22
|
+
run root rake tasks, cron jobs, or deploy scripts.
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
Add the gem to your application's Gemfile:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
gem 'raise-if-root', '~> 0'
|
30
|
+
```
|
31
|
+
|
32
|
+
Require it from your main application code:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
require 'raise-if-root'
|
36
|
+
```
|
37
|
+
|
38
|
+
There is no step three! This will raise `RaiseIfRoot::AssertionFailed` if the
|
39
|
+
current uid is 0.
|
40
|
+
|
41
|
+
### More complex patterns
|
42
|
+
|
43
|
+
See the [YARD documentation](http://www.rubydoc.info/github/ab/raise-if-root/master).
|
44
|
+
|
45
|
+
If you want to enforce that the application is running as a particular user,
|
46
|
+
there are several more specific functions available.
|
47
|
+
|
48
|
+
Load the library, which doesn't immediately raise when you load it.
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
# load the library, which doesn't raise
|
52
|
+
require 'raise-if-root/library'
|
53
|
+
|
54
|
+
# raise if running as uid 1000
|
55
|
+
RaiseIfRoot.raise_if_uid(1000)
|
56
|
+
|
57
|
+
# raise unless user is nobody
|
58
|
+
RaiseIfRoot.raise_if(username_not: 'nobody')
|
59
|
+
|
60
|
+
# raise with multiple conditions
|
61
|
+
RaiseIfRoot.raise_if(uid_not: 1000, gid_not: 500)
|
62
|
+
```
|
63
|
+
|
64
|
+
### Notification callbacks
|
65
|
+
|
66
|
+
If you want to sound the alarm with something more than just the exception, you
|
67
|
+
can add callbacks to send emails, smoke signals, etc.
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
# load the library, which doesn't raise
|
71
|
+
require 'raise-if-root/library'
|
72
|
+
|
73
|
+
RaiseIfRoot.add_assertion_callback do |err|
|
74
|
+
Mail.deliver do
|
75
|
+
from 'system@example.com'
|
76
|
+
to 'alerts@example.com'
|
77
|
+
subject 'App was run as root'
|
78
|
+
body "RaiseIfRoot is raising an exception:\n #{err.inspect}\n"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# ensure we're not root
|
83
|
+
RaiseIfRoot.raise_if_root
|
84
|
+
```
|
85
|
+
|
86
|
+
## Contributing
|
87
|
+
|
88
|
+
1. Fork it
|
89
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
90
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
91
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
92
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
task :default do
|
6
|
+
sh 'rake -T'
|
7
|
+
end
|
8
|
+
|
9
|
+
RSpec::Core::RakeTask.new(:spec)
|
10
|
+
|
11
|
+
def alias_task(alias_task, original)
|
12
|
+
desc "Alias for rake #{original}"
|
13
|
+
task alias_task, Rake.application[original].arg_names => original
|
14
|
+
end
|
15
|
+
alias_task(:test, :spec)
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'etc'
|
2
|
+
|
3
|
+
require_relative './version'
|
4
|
+
|
5
|
+
module RaiseIfRoot
|
6
|
+
# Error class for RaiseIfRoot assertion failures. Inherits directly from
|
7
|
+
# Exception because we don't want a bare rescue to catch this.
|
8
|
+
# rubocop:disable Lint/InheritException
|
9
|
+
class AssertionFailed < Exception; end
|
10
|
+
|
11
|
+
# Raise if the process UID/EUID is 0 or if the process GID/EGID is 0.
|
12
|
+
#
|
13
|
+
# @raise [AssertionFailed] if running as root
|
14
|
+
#
|
15
|
+
def self.raise_if_root
|
16
|
+
raise_if(uid: 0, gid: 0)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Raise if the process UID or EUID equals +uid+.
|
20
|
+
#
|
21
|
+
# @param uid [Integer]
|
22
|
+
#
|
23
|
+
# @raise [AssertionFailed]
|
24
|
+
#
|
25
|
+
# @see .raise_if
|
26
|
+
#
|
27
|
+
def self.raise_if_uid(uid)
|
28
|
+
raise_if(uid: uid)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Raise AssertionFailed if any of the specified conditions are met. This is
|
32
|
+
# the primary method powering RaiseIfRoot.
|
33
|
+
#
|
34
|
+
# @param uid [Integer] Raise if the process UID or EUID matches
|
35
|
+
# @param gid [Integer] Raise if the process GID or EGID matches
|
36
|
+
#
|
37
|
+
# @param uid_not [Integer] Raise if the process UID or EUID does not match
|
38
|
+
# the provided value
|
39
|
+
# @param gid_not [Integer] Raise if the process GID or EGID does not match
|
40
|
+
# the provided value
|
41
|
+
#
|
42
|
+
# @param username [String] Raise if the username of the process UID or EUID
|
43
|
+
# matches the provided value
|
44
|
+
# @param username_not [String] Raise if the username of the process UID or
|
45
|
+
# EUID does not match the provided value
|
46
|
+
#
|
47
|
+
# @raise [AssertionFailed] if any of the conditions match.
|
48
|
+
#
|
49
|
+
# rubocop:disable Metrics/ParameterLists
|
50
|
+
def self.raise_if(uid: nil, gid: nil, uid_not: nil, gid_not: nil,
|
51
|
+
username: nil, username_not: nil)
|
52
|
+
if uid
|
53
|
+
assert_not_equal('UID', Process.uid, uid)
|
54
|
+
assert_not_equal('EUID', Process.euid, uid)
|
55
|
+
end
|
56
|
+
|
57
|
+
if gid
|
58
|
+
assert_not_equal('GID', Process.gid, gid)
|
59
|
+
assert_not_equal('EGID', Process.egid, gid)
|
60
|
+
end
|
61
|
+
|
62
|
+
if uid_not
|
63
|
+
assert_equal('UID', Process.uid, uid_not)
|
64
|
+
assert_equal('EUID', Process.euid, uid_not)
|
65
|
+
end
|
66
|
+
|
67
|
+
if gid_not
|
68
|
+
assert_equal('GID', Process.gid, gid_not)
|
69
|
+
assert_equal('EGID', Process.egid, gid_not)
|
70
|
+
end
|
71
|
+
|
72
|
+
# raise if username
|
73
|
+
if username
|
74
|
+
assert_not_equal('username', Etc.getpwuid(Process.uid).name, username)
|
75
|
+
assert_not_equal('effective username', Etc.getpwuid(Process.euid).name,
|
76
|
+
username)
|
77
|
+
end
|
78
|
+
|
79
|
+
# raise unless username is username_not
|
80
|
+
if username_not
|
81
|
+
assert_equal('username', Etc.getpwuid(Process.uid).name, username_not)
|
82
|
+
assert_equal('effective username', Etc.getpwuid(Process.euid).name,
|
83
|
+
username_not)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Assert that two values are equal. If they are not, run assertion callbacks
|
88
|
+
# and raise AssertionFailed.
|
89
|
+
#
|
90
|
+
# @param label [String] The label for the comparison we're making
|
91
|
+
# @param actual The actual value
|
92
|
+
# @param expected The expected value
|
93
|
+
#
|
94
|
+
# @raise [AssertionFailed] if the values are not equal
|
95
|
+
#
|
96
|
+
def self.assert_equal(label, actual, expected)
|
97
|
+
if expected.nil?
|
98
|
+
warn('warning: RaiseIfRoot.assert_equal called with expected=nil')
|
99
|
+
end
|
100
|
+
if actual != expected
|
101
|
+
err = new_assertion_failed(label, actual, expected)
|
102
|
+
run_assertion_callbacks(err)
|
103
|
+
raise err
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Assert that two values are not equal. But if they are equal, run assertion
|
108
|
+
# callbacks and raise AssertionFailed.
|
109
|
+
#
|
110
|
+
# @param label [String] The label for the comparison we're making
|
111
|
+
# @param actual The actual value
|
112
|
+
# @param expected The expected value
|
113
|
+
#
|
114
|
+
# @raise [AssertionFailed] if the values are equal
|
115
|
+
#
|
116
|
+
def self.assert_not_equal(label, actual, expected)
|
117
|
+
if actual == expected
|
118
|
+
err = new_assertion_failed(label, actual)
|
119
|
+
run_assertion_callbacks(err)
|
120
|
+
raise err
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Create a new AssertionFailed object.
|
125
|
+
#
|
126
|
+
# @param label [String] The label for the comparison we're making
|
127
|
+
# @param actual The actual value
|
128
|
+
# @param expected The expected value, if any
|
129
|
+
#
|
130
|
+
# @return [AssertionFailed]
|
131
|
+
#
|
132
|
+
def self.new_assertion_failed(label, actual, expected=nil)
|
133
|
+
# rubocop:disable Style/SpecialGlobalVars
|
134
|
+
message = "Process[#{$$}] #{label} is #{actual.inspect}"
|
135
|
+
if expected
|
136
|
+
message << ", expected #{expected.inspect}"
|
137
|
+
end
|
138
|
+
|
139
|
+
AssertionFailed.new(message)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Add a callback to the list of assertion callbacks that are executed when an
|
143
|
+
# assertion fails. The callback will be passed one argument: the
|
144
|
+
# AssertionFailed exception object just before it is raised.
|
145
|
+
def self.add_assertion_callback(&block)
|
146
|
+
raise ArgumentError.new("Must pass block") unless block
|
147
|
+
|
148
|
+
assertion_callbacks << block
|
149
|
+
end
|
150
|
+
|
151
|
+
# The list of stored assertion callbacks. These are executed when an
|
152
|
+
# assertion fails just before the assertion is raised.
|
153
|
+
#
|
154
|
+
# @return [Array<Proc>]
|
155
|
+
#
|
156
|
+
def self.assertion_callbacks
|
157
|
+
@assertion_callbacks ||= []
|
158
|
+
end
|
159
|
+
|
160
|
+
# Execute all of the stored assertion callbacks.
|
161
|
+
#
|
162
|
+
# @param [AssertionFailed] err The exception object to pass to each callback.
|
163
|
+
#
|
164
|
+
# @return [Array] The collected return values of the callbacks.
|
165
|
+
#
|
166
|
+
def self.run_assertion_callbacks(err)
|
167
|
+
assertion_callbacks.map { |block| block.call(err) }
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'raise-if-root/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'raise-if-root'
|
9
|
+
spec.version = RaiseIfRoot::VERSION
|
10
|
+
spec.authors = ['Andy Brody']
|
11
|
+
spec.email = ['git@abrody.com']
|
12
|
+
spec.summary = 'Library that raises when run as root user'
|
13
|
+
spec.description = <<-EOM
|
14
|
+
Raise If Root is a small library that raises an exception when run as the
|
15
|
+
root user (uid 0). This helps ensure that you never accidentally run your
|
16
|
+
application as root.
|
17
|
+
EOM
|
18
|
+
spec.homepage = 'https://github.com/ab/raise-if-root'
|
19
|
+
spec.license = 'GPL-3'
|
20
|
+
|
21
|
+
spec.files = `git ls-files`.split($/)
|
22
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
23
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
24
|
+
spec.require_paths = ['lib']
|
25
|
+
|
26
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
27
|
+
spec.add_development_dependency 'pry'
|
28
|
+
spec.add_development_dependency 'rake'
|
29
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
30
|
+
spec.add_development_dependency 'rubocop', '~> 0'
|
31
|
+
spec.add_development_dependency 'yard'
|
32
|
+
|
33
|
+
spec.required_ruby_version = '>= 2.0'
|
34
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
require_relative '../lib/raise-if-root/library'
|
2
|
+
|
3
|
+
# Given that it is always loaded, you are encouraged to keep this file as
|
4
|
+
# light-weight as possible. Requiring heavyweight dependencies from this file
|
5
|
+
# will add to the boot time of your test suite on EVERY test run, even for an
|
6
|
+
# individual file that may not need all of that loaded. Instead, consider making
|
7
|
+
# a separate helper file that requires the additional dependencies and performs
|
8
|
+
# the additional setup, and require it from the spec files that actually need
|
9
|
+
# it.
|
10
|
+
#
|
11
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
12
|
+
RSpec.configure do |config|
|
13
|
+
# rspec-expectations config goes here. You can use an alternate
|
14
|
+
# assertion/expectation library such as wrong or the stdlib/minitest
|
15
|
+
# assertions if you prefer.
|
16
|
+
config.expect_with :rspec do |expectations|
|
17
|
+
# This option will default to `true` in RSpec 4. It makes the `description`
|
18
|
+
# and `failure_message` of custom matchers include text for helper methods
|
19
|
+
# defined using `chain`, e.g.:
|
20
|
+
# be_bigger_than(2).and_smaller_than(4).description
|
21
|
+
# # => "be bigger than 2 and smaller than 4"
|
22
|
+
# ...rather than:
|
23
|
+
# # => "be bigger than 2"
|
24
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
25
|
+
end
|
26
|
+
|
27
|
+
# rspec-mocks config goes here. You can use an alternate test double
|
28
|
+
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
29
|
+
config.mock_with :rspec do |mocks|
|
30
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
31
|
+
# a real object. This is generally recommended, and will default to
|
32
|
+
# `true` in RSpec 4.
|
33
|
+
mocks.verify_partial_doubles = true
|
34
|
+
end
|
35
|
+
|
36
|
+
# The settings below are suggested to provide a good initial experience
|
37
|
+
# with RSpec, but feel free to customize to your heart's content.
|
38
|
+
|
39
|
+
# These two settings work together to allow you to limit a spec run
|
40
|
+
# to individual examples or groups you care about by tagging them with
|
41
|
+
# `:focus` metadata. When nothing is tagged with `:focus`, all examples
|
42
|
+
# get run.
|
43
|
+
# config.filter_run :focus
|
44
|
+
# config.run_all_when_everything_filtered = true
|
45
|
+
|
46
|
+
# Allows RSpec to persist some state between runs in order to support
|
47
|
+
# the `--only-failures` and `--next-failure` CLI options. We recommend
|
48
|
+
# you configure your source control system to ignore this file.
|
49
|
+
# config.example_status_persistence_file_path = "spec/examples.txt"
|
50
|
+
|
51
|
+
# Limits the available syntax to the non-monkey patched syntax that is
|
52
|
+
# recommended. For more details, see:
|
53
|
+
# - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
|
54
|
+
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
55
|
+
# - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
|
56
|
+
config.disable_monkey_patching!
|
57
|
+
|
58
|
+
# This setting enables warnings. It's recommended, but in some cases may
|
59
|
+
# be too noisy due to issues in dependencies.
|
60
|
+
config.warnings = true
|
61
|
+
|
62
|
+
# Many RSpec users commonly either run the entire suite or an individual
|
63
|
+
# file, and it's useful to allow more verbose output when running an
|
64
|
+
# individual spec file.
|
65
|
+
if config.files_to_run.one?
|
66
|
+
# Use the documentation formatter for detailed output,
|
67
|
+
# unless a formatter has already been configured
|
68
|
+
# (e.g. via a command-line flag).
|
69
|
+
config.default_formatter = 'doc'
|
70
|
+
end
|
71
|
+
|
72
|
+
# Print the 10 slowest examples and example groups at the
|
73
|
+
# end of the spec run, to help surface which specs are running
|
74
|
+
# particularly slow.
|
75
|
+
config.profile_examples = 10
|
76
|
+
|
77
|
+
# Run specs in random order to surface order dependencies. If you find an
|
78
|
+
# order dependency and want to debug it, you can fix the order by providing
|
79
|
+
# the seed, which is printed after each run.
|
80
|
+
# --seed 1234
|
81
|
+
config.order = :random
|
82
|
+
|
83
|
+
# Seed global randomization in this process using the `--seed` CLI option.
|
84
|
+
# Setting this allows you to use `--seed` to deterministically reproduce
|
85
|
+
# test failures related to randomization by passing the same `--seed` value
|
86
|
+
# as the one that triggered the failure.
|
87
|
+
Kernel.srand config.seed
|
88
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# NB: These tests cannot be run as root (uid 0 or gid 0), sorry.
|
2
|
+
RSpec.describe RaiseIfRoot do
|
3
|
+
|
4
|
+
describe '.raise_if_root' do
|
5
|
+
it 'should raise if uid is 0' do
|
6
|
+
allow(Process).to receive(:uid).and_return(0)
|
7
|
+
expect {
|
8
|
+
RaiseIfRoot.raise_if_root
|
9
|
+
}.to raise_error(RaiseIfRoot::AssertionFailed, /\bUID\b/)
|
10
|
+
end
|
11
|
+
it 'should raise if euid is 0' do
|
12
|
+
allow(Process).to receive(:euid).and_return(0)
|
13
|
+
expect {
|
14
|
+
RaiseIfRoot.raise_if_root
|
15
|
+
}.to raise_error(RaiseIfRoot::AssertionFailed, /\bEUID\b/)
|
16
|
+
end
|
17
|
+
it 'should raise if gid is 0' do
|
18
|
+
allow(Process).to receive(:gid).and_return(0)
|
19
|
+
expect {
|
20
|
+
RaiseIfRoot.raise_if_root
|
21
|
+
}.to raise_error(RaiseIfRoot::AssertionFailed, /\bGID\b/)
|
22
|
+
end
|
23
|
+
it 'should raise if egid is 0' do
|
24
|
+
allow(Process).to receive(:egid).and_return(0)
|
25
|
+
expect {
|
26
|
+
RaiseIfRoot.raise_if_root
|
27
|
+
}.to raise_error(RaiseIfRoot::AssertionFailed, /\bEGID\b/)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should not raise otherwise' do
|
31
|
+
expect(RaiseIfRoot.raise_if_root).to eq nil
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'runs assertion callbacks' do
|
35
|
+
allow(Process).to receive(:uid).and_return(0)
|
36
|
+
expect(RaiseIfRoot).to receive(:run_assertion_callbacks).with(
|
37
|
+
instance_of(RaiseIfRoot::AssertionFailed)
|
38
|
+
).once.and_call_original
|
39
|
+
|
40
|
+
expect {
|
41
|
+
RaiseIfRoot.raise_if_root
|
42
|
+
}.to raise_error(RaiseIfRoot::AssertionFailed)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '.add_assertion_callback' do
|
47
|
+
before do
|
48
|
+
RaiseIfRoot.assertion_callbacks.clear
|
49
|
+
end
|
50
|
+
after do
|
51
|
+
RaiseIfRoot.assertion_callbacks.clear
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'adds a callback' do
|
55
|
+
expect(RaiseIfRoot.assertion_callbacks.length).to eq 0
|
56
|
+
|
57
|
+
RaiseIfRoot.add_assertion_callback { :returnvalue }
|
58
|
+
expect(RaiseIfRoot.assertion_callbacks.last).is_a?(Proc)
|
59
|
+
expect(RaiseIfRoot.assertion_callbacks.length).to eq 1
|
60
|
+
|
61
|
+
expect(RaiseIfRoot.run_assertion_callbacks(nil)).to eq([:returnvalue])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe '.run_assertion_callbacks' do
|
66
|
+
before do
|
67
|
+
RaiseIfRoot.assertion_callbacks.clear
|
68
|
+
end
|
69
|
+
after do
|
70
|
+
RaiseIfRoot.assertion_callbacks.clear
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'runs callbacks' do
|
74
|
+
sentinel = double('Sentinel')
|
75
|
+
expect(sentinel).to(receive(:callback).with(
|
76
|
+
instance_of(RaiseIfRoot::AssertionFailed)
|
77
|
+
).twice.and_return(:callbackresponse1, :callbackresponse2))
|
78
|
+
|
79
|
+
expect(RaiseIfRoot.assertion_callbacks.length).to eq 0
|
80
|
+
|
81
|
+
RaiseIfRoot.add_assertion_callback { |err| sentinel.callback(err) }
|
82
|
+
RaiseIfRoot.add_assertion_callback { |err| sentinel.callback(err) }
|
83
|
+
|
84
|
+
expect(RaiseIfRoot.assertion_callbacks.length).to eq 2
|
85
|
+
|
86
|
+
expect(RaiseIfRoot.run_assertion_callbacks(
|
87
|
+
RaiseIfRoot::AssertionFailed.new
|
88
|
+
)).to eq([:callbackresponse1, :callbackresponse2])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe '.raise_if' do
|
93
|
+
before do
|
94
|
+
allow(Process).to receive(:uid).and_return(5000)
|
95
|
+
allow(Process).to receive(:euid).and_return(5000)
|
96
|
+
|
97
|
+
allow(Process).to receive(:gid).and_return(5000)
|
98
|
+
allow(Process).to receive(:egid).and_return(5000)
|
99
|
+
|
100
|
+
@someuser = double('Etc::Passwd', name: 'someuser')
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'does not raise by default' do
|
104
|
+
expect(RaiseIfRoot.raise_if).to eq nil
|
105
|
+
expect(RaiseIfRoot.raise_if(uid: 0)).to eq nil
|
106
|
+
expect(RaiseIfRoot.raise_if(gid: 0)).to eq nil
|
107
|
+
expect(RaiseIfRoot.raise_if(uid_not: 5000)).to eq nil
|
108
|
+
expect(RaiseIfRoot.raise_if(gid_not: 5000)).to eq nil
|
109
|
+
expect(RaiseIfRoot.raise_if(uid: 0, gid: 0)).to eq nil
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'raises when any condition matches' do
|
113
|
+
expect {
|
114
|
+
RaiseIfRoot.raise_if(uid: 0, gid: 0, uid_not: 123, gid_not: 5000)
|
115
|
+
}.to raise_error(RaiseIfRoot::AssertionFailed, /\bUID\b/)
|
116
|
+
expect {
|
117
|
+
RaiseIfRoot.raise_if(uid: 0, gid: 5000, uid_not: 5000, gid_not: 5000)
|
118
|
+
}.to raise_error(RaiseIfRoot::AssertionFailed, /\bGID\b/)
|
119
|
+
expect {
|
120
|
+
RaiseIfRoot.raise_if(uid: 5000, gid: 0)
|
121
|
+
}.to raise_error(RaiseIfRoot::AssertionFailed, /\bUID\b/)
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'handles username and not_username' do
|
125
|
+
allow(Etc).to receive(:getpwuid).with(5000).and_return(@someuser)
|
126
|
+
|
127
|
+
expect {
|
128
|
+
RaiseIfRoot.raise_if(username: 'someuser')
|
129
|
+
}.to raise_error(RaiseIfRoot::AssertionFailed, /\busername\b/)
|
130
|
+
|
131
|
+
expect(RaiseIfRoot.raise_if(username: 'other')).to eq nil
|
132
|
+
|
133
|
+
expect {
|
134
|
+
RaiseIfRoot.raise_if(username_not: 'other')
|
135
|
+
}.to raise_error(RaiseIfRoot::AssertionFailed, /\busername\b/)
|
136
|
+
|
137
|
+
expect(RaiseIfRoot.raise_if(username_not: 'someuser')).to eq nil
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|