raise-if-root 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/raise-if-root.svg)](https://rubygems.org/gems/raise-if-root)
|
4
|
+
[![Build status](https://travis-ci.org/ab/raise-if-root.svg)](https://travis-ci.org/ab/raise-if-root)
|
5
|
+
[![Code Climate](https://codeclimate.com/github/ab/raise-if-root.svg)](https://codeclimate.com/github/ab/raise-if-root)
|
6
|
+
[![Inline Docs](http://inch-ci.org/github/ab/raise-if-root.svg?branch=master)](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
|