sudo 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.
- data/README.rdoc +101 -0
- data/examples/block.rb +18 -0
- data/examples/dsl.rb +13 -0
- data/examples/new.rb +24 -0
- data/lib/sudo.rb +44 -0
- data/lib/sudo/support/kernel.rb +20 -0
- data/lib/sudo/support/object.rb +5 -0
- data/lib/sudo/support/process.rb +14 -0
- data/lib/sudo/wrapper.rb +120 -0
- data/libexec/server.rb +18 -0
- metadata +77 -0
data/README.rdoc
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
= Ruby Sudo
|
2
|
+
|
3
|
+
Give Ruby objects superuser privileges.
|
4
|
+
|
5
|
+
Based on dRuby[http://ruby-doc.org/ruby-1.9/classes/DRb.html] and
|
6
|
+
sudo[http://www.sudo.ws/].
|
7
|
+
|
8
|
+
Only tested with {MRI 1.9}[http://en.wikipedia.org/wiki/Ruby_MRI] .
|
9
|
+
|
10
|
+
== REQUIREMENTS
|
11
|
+
|
12
|
+
Your user must be allowed, in +/etc/sudoers+, to run +ruby+ and +kill+
|
13
|
+
commands as root.
|
14
|
+
|
15
|
+
A password will be required from the console, or not, depending on
|
16
|
+
the +NOPASSWD+ options in +/etc/sudoers+.
|
17
|
+
|
18
|
+
== USAGE
|
19
|
+
|
20
|
+
=== DSL style
|
21
|
+
|
22
|
+
require 'fileutils'
|
23
|
+
require 'sudo'
|
24
|
+
|
25
|
+
include Sudo::DSL
|
26
|
+
|
27
|
+
# The String will be passed as options to sudo-ed Ruby interpreter
|
28
|
+
sudo_start "-rfileutils"
|
29
|
+
|
30
|
+
# only readable by root
|
31
|
+
puts sudo(File).read '/etc/shadow'
|
32
|
+
|
33
|
+
# write into the /
|
34
|
+
sudo(FileUtils).mkdir_p '/TEST_DIR/SUB_DIR'
|
35
|
+
|
36
|
+
# Stop the dRuby server (whish is running as root), as soon as you can
|
37
|
+
sudo_stop
|
38
|
+
|
39
|
+
=== Explicit creation of a Wrapper object, block given
|
40
|
+
|
41
|
+
require 'fileutils'
|
42
|
+
require 'sudo'
|
43
|
+
|
44
|
+
Sudo::Wrapper.run('-rfileutils) do |su|
|
45
|
+
# here you use square brackets [] :
|
46
|
+
# su is an object, not a (top-level) method.
|
47
|
+
su[FileUtils].mkdir_p '/ONLY/ROOT/CAN/DO/THAT'
|
48
|
+
end
|
49
|
+
# Sockets and processes are closed automatically when the block exits
|
50
|
+
|
51
|
+
=== Explicit creation of a Wrapper object, without block
|
52
|
+
|
53
|
+
require 'mygem/myclass'
|
54
|
+
require 'sudo'
|
55
|
+
|
56
|
+
obj = MyGem::MyClass.new
|
57
|
+
|
58
|
+
sudo = Sudo::Wrapper.new(-rmygem/myclass -rmygem/myclass2)
|
59
|
+
|
60
|
+
sudo.start!
|
61
|
+
|
62
|
+
sudo[obj].method # will be run as root (well, a sudo-ed copy)
|
63
|
+
|
64
|
+
# when you've done:
|
65
|
+
sudo.stop!
|
66
|
+
|
67
|
+
== PRINCIPLES OF OPERATION
|
68
|
+
|
69
|
+
Spawns a sudo-ed Ruby process running a
|
70
|
+
DRb[http://ruby-doc.org/ruby-1.9/classes/DRb.html] server. Communication is
|
71
|
+
done via a Unix socket (and, of course, permissions are set to +0600+).
|
72
|
+
|
73
|
+
No long-running daemons involved, everything is created on demand.
|
74
|
+
|
75
|
+
Access control is entirely delegated to +sudo+.
|
76
|
+
|
77
|
+
== TODO
|
78
|
+
|
79
|
+
* +sudo+ has a +-A+ option to accept password via an external program
|
80
|
+
(maybe graphical): support this feature.
|
81
|
+
|
82
|
+
* more options in Sudo::Wrapper.new, maybe a Hash.
|
83
|
+
|
84
|
+
== THANKS
|
85
|
+
|
86
|
+
Thanks to Tony Arcieri and Brian Candler for suggestions on
|
87
|
+
ruby-talk[http://www.ruby-forum.com/topic/262655].
|
88
|
+
|
89
|
+
== AUTHOR
|
90
|
+
|
91
|
+
Copyright (c) 2010 {Guido De Rosa}[http://github.com/gderosa/].
|
92
|
+
|
93
|
+
Sponsored by {VEMAR s.a.s.}[http://www.vemarsas.it/]
|
94
|
+
|
95
|
+
== LICENSE
|
96
|
+
|
97
|
+
Ruby's.
|
98
|
+
|
99
|
+
|
100
|
+
|
101
|
+
|
data/examples/block.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'sudo'
|
3
|
+
|
4
|
+
Sudo::Wrapper.run('-rfileutils') do |su|
|
5
|
+
|
6
|
+
su[File].open '/TEST', 'w' do |f|
|
7
|
+
f.puts "Hello from UID=#{su[Process].uid}!"
|
8
|
+
end
|
9
|
+
|
10
|
+
su[FileUtils].cp '/etc/shadow', '/etc/shadow2'
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
|
18
|
+
|
data/examples/dsl.rb
ADDED
data/examples/new.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'sudo'
|
3
|
+
|
4
|
+
su = Sudo::Wrapper.new('-rfileutils')
|
5
|
+
|
6
|
+
su.start!
|
7
|
+
|
8
|
+
su[File].open '/TEST', 'w' do |f|
|
9
|
+
f.puts "Hello from UID=#{su[Process].uid}!"
|
10
|
+
end
|
11
|
+
|
12
|
+
su[FileUtils].cp '/etc/shadow', '/etc/shadow2'
|
13
|
+
|
14
|
+
# i you don't call stop! explicitly, the corresponding process and file
|
15
|
+
# cleanup will be done automatically, when the object gets out of scope
|
16
|
+
#
|
17
|
+
# su.stop!
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
|
data/lib/sudo.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'drb/drb'
|
2
|
+
require 'sudo/support/kernel'
|
3
|
+
require 'sudo/support/object'
|
4
|
+
require 'sudo/support/process'
|
5
|
+
require 'sudo/wrapper'
|
6
|
+
|
7
|
+
module Sudo
|
8
|
+
|
9
|
+
VERSION = '0.0.1'
|
10
|
+
ROOTDIR = File.expand_path File.join File.dirname(__FILE__), '..'
|
11
|
+
LIBDIR = File.join ROOTDIR, 'lib'
|
12
|
+
SERVER_SCRIPT = File.join ROOTDIR, 'libexec/server.rb'
|
13
|
+
|
14
|
+
class RuntimeError < RuntimeError; end
|
15
|
+
|
16
|
+
module DSL
|
17
|
+
def sudo_start(*args, &blk)
|
18
|
+
@__default_sudo_wrapper = Sudo::Wrapper.new(*args, &blk).start!
|
19
|
+
end
|
20
|
+
def sudo_stop
|
21
|
+
@__default_sudo_wrapper.stop!
|
22
|
+
end
|
23
|
+
def sudo(object)
|
24
|
+
@__default_sudo_wrapper[object]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class MethodProxy
|
29
|
+
def initialize(object, proxy)
|
30
|
+
@object = object
|
31
|
+
@proxy = proxy
|
32
|
+
end
|
33
|
+
def method_missing(method=:self, *args, &blk)
|
34
|
+
@proxy.proxy @object, method, *args, &blk
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Proxy
|
39
|
+
def proxy(object, method=:self, *args, &blk)
|
40
|
+
object.send method, *args, &blk
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Kernel
|
2
|
+
def wait_for(conf)
|
3
|
+
start = Time.now
|
4
|
+
defaults = {
|
5
|
+
:timeout => nil,
|
6
|
+
:step => 0.125
|
7
|
+
}
|
8
|
+
conf = defaults.update conf
|
9
|
+
condition = false
|
10
|
+
loop do
|
11
|
+
condition = yield
|
12
|
+
|
13
|
+
break if condition
|
14
|
+
break if conf[:timeout] and Time.now - start > conf[:timeout]
|
15
|
+
|
16
|
+
sleep conf[:step]
|
17
|
+
end
|
18
|
+
condition
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Process
|
2
|
+
class << self
|
3
|
+
# Thanks to:
|
4
|
+
# http://stackoverflow.com/questions/141162/how-can-i-determine-if-a-different-process-id-is-running-using-java-or-jruby-on-l
|
5
|
+
def exists?(pid)
|
6
|
+
begin
|
7
|
+
Process.getpgid( pid )
|
8
|
+
true
|
9
|
+
rescue Errno::ESRCH
|
10
|
+
false
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/sudo/wrapper.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'drb/drb'
|
2
|
+
require 'sudo/support/kernel'
|
3
|
+
require 'sudo/support/object'
|
4
|
+
require 'sudo/support/process'
|
5
|
+
|
6
|
+
begin
|
7
|
+
DRb.current_server
|
8
|
+
rescue DRb::DRbServerNotFound
|
9
|
+
DRb.start_service
|
10
|
+
end
|
11
|
+
|
12
|
+
module Sudo
|
13
|
+
class Wrapper
|
14
|
+
|
15
|
+
class RuntimeError < RuntimeError; end
|
16
|
+
class NotRunning < RuntimeError; end
|
17
|
+
class SudoFailed < RuntimeError; end
|
18
|
+
class SocketStillExists < RuntimeError; end
|
19
|
+
class SudoProcessExists < RuntimeError; end
|
20
|
+
class SudoProcessAlreadyExists < SudoProcessExists; end
|
21
|
+
class SudoProcessStillExists < RuntimeError; end
|
22
|
+
class NoValidSocket < RuntimeError; end
|
23
|
+
class SocketNotFound < NoValidSocket; end
|
24
|
+
class NoValidSudoPid < RuntimeError; end
|
25
|
+
class SudoProcessNotFound < NoValidSudoPid; end
|
26
|
+
|
27
|
+
class << self
|
28
|
+
|
29
|
+
# with blocks
|
30
|
+
def run(*args)
|
31
|
+
sudo = new(*args)
|
32
|
+
yield sudo.start!
|
33
|
+
sudo.stop!
|
34
|
+
end
|
35
|
+
|
36
|
+
# Not an instance method, so it may act as a finalizer
|
37
|
+
# (as in ObjectSpace.define_finalizer)
|
38
|
+
def cleanup!(h)
|
39
|
+
if h[:pid] and Process.exists? h[:pid]
|
40
|
+
system "sudo kill #{h[:pid]}" or
|
41
|
+
system "sudo kill -9 #{h[:pid]}" or
|
42
|
+
raise SudoProcessStillExists,
|
43
|
+
"Couldn't kill sudo process (PID=#{h[:pid]})"
|
44
|
+
end
|
45
|
+
if h[:socket] and File.exists? h[:socket]
|
46
|
+
system "sudo rm -f #{h[:socket]}" or
|
47
|
+
raise SocketStillExists,
|
48
|
+
"Couldn't delete socket #{h[:socket]}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize(ruby_opts='')
|
55
|
+
@proxy = nil
|
56
|
+
@socket = "/tmp/rubysu-#{Process.pid}-#{object_id}"
|
57
|
+
@sudo_pid = nil
|
58
|
+
@ruby_opts = ruby_opts
|
59
|
+
end
|
60
|
+
|
61
|
+
def server_uri; "drbunix:#{@socket}"; end
|
62
|
+
|
63
|
+
def start!
|
64
|
+
# just to check if we can sudo; and we'll receive a sudo token
|
65
|
+
raise SudoFailed unless system "sudo ruby -e ''"
|
66
|
+
|
67
|
+
raise SudoProcessAlreadyExists if @sudo_pid and Process.exists? @sudo_pid
|
68
|
+
|
69
|
+
@sudo_pid = spawn(
|
70
|
+
"sudo ruby -I#{LIBDIR} #{@ruby_opts} #{SERVER_SCRIPT} #{@socket} #{Process.uid}"
|
71
|
+
)
|
72
|
+
Process.detach(@sudo_pid) if @sudo_pid # avoid zombies
|
73
|
+
ObjectSpace.define_finalizer self, Finalizer.new(
|
74
|
+
:pid => @sudo_pid, :socket => @socket
|
75
|
+
)
|
76
|
+
|
77
|
+
if wait_for(:timeout => 1){File.exists? @socket}
|
78
|
+
@proxy = DRbObject.new_with_uri(server_uri)
|
79
|
+
else
|
80
|
+
raise RuntimeError, "Couldn't create DRb socket #{@socket}"
|
81
|
+
end
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
def running?
|
86
|
+
true if (
|
87
|
+
@sudo_pid and Process.exists? @sudo_pid and
|
88
|
+
@socket and File.exists? @socket and
|
89
|
+
@proxy
|
90
|
+
)
|
91
|
+
end
|
92
|
+
|
93
|
+
def stop!
|
94
|
+
self.class.cleanup!(:pid => @sudo_pid, :socket => @socket)
|
95
|
+
@proxy = nil
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
def [](object)
|
100
|
+
if running?
|
101
|
+
MethodProxy.new object, @proxy
|
102
|
+
else
|
103
|
+
raise NotRunning
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Inspired by Remover class in tmpfile.rb (Ruby std library)
|
108
|
+
class Finalizer
|
109
|
+
def initialize(h)
|
110
|
+
@data = h
|
111
|
+
end
|
112
|
+
|
113
|
+
# mimic proc-like behavior (walk like a duck)
|
114
|
+
def call(*args)
|
115
|
+
Sudo::Wrapper.cleanup! @data
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
data/libexec/server.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'drb/drb'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'sudo'
|
4
|
+
|
5
|
+
socket = ARGV[0]
|
6
|
+
|
7
|
+
owner = ARGV[1]
|
8
|
+
|
9
|
+
uri = "drbunix:#{socket}"
|
10
|
+
|
11
|
+
DRb.start_service(uri, Sudo::Proxy.new)
|
12
|
+
|
13
|
+
FileUtils.chown owner, 0, socket
|
14
|
+
FileUtils.chmod 0600, socket
|
15
|
+
|
16
|
+
DRb.thread.join
|
17
|
+
|
18
|
+
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sudo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Guido De Rosa
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-10-22 00:00:00 +02:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: |
|
22
|
+
Give Ruby objects superuser privileges.
|
23
|
+
|
24
|
+
Based on dRuby and sudo (the Unix program).
|
25
|
+
|
26
|
+
email: guido.derosa@vemarsas.it
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files: []
|
32
|
+
|
33
|
+
files:
|
34
|
+
- lib/sudo/support/object.rb
|
35
|
+
- lib/sudo/support/kernel.rb
|
36
|
+
- lib/sudo/support/process.rb
|
37
|
+
- lib/sudo/wrapper.rb
|
38
|
+
- lib/sudo.rb
|
39
|
+
- libexec/server.rb
|
40
|
+
- examples/block.rb
|
41
|
+
- examples/dsl.rb
|
42
|
+
- examples/new.rb
|
43
|
+
- README.rdoc
|
44
|
+
has_rdoc: true
|
45
|
+
homepage: http://github.com/gderosa/rubysu
|
46
|
+
licenses: []
|
47
|
+
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options:
|
50
|
+
- --charset=UTF-8
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
segments:
|
67
|
+
- 0
|
68
|
+
version: "0"
|
69
|
+
requirements: []
|
70
|
+
|
71
|
+
rubyforge_project:
|
72
|
+
rubygems_version: 1.3.7
|
73
|
+
signing_key:
|
74
|
+
specification_version: 3
|
75
|
+
summary: Give Ruby objects superuser privileges
|
76
|
+
test_files: []
|
77
|
+
|