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