sshkit-sudo-next 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +10 -0
- data/Rakefile +2 -0
- data/lib/sshkit/sudo.rb +5 -0
- data/lib/sshkit/sudo/command.rb +14 -0
- data/lib/sshkit/sudo/password_sending_interaction_handler.rb +28 -0
- data/lib/sshkit/sudo/sudo_netssh.rb +155 -0
- data/lib/sshkit/sudo/version.rb +5 -0
- data/sshkit-sudo-next.gemspec +26 -0
- metadata +96 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: eaf0ceec3960adf1b245082ce6c353db05abf9e5a8941ed267454eb0a6b9683f
|
4
|
+
data.tar.gz: 64d65fd50dc8624827c2479c898f4c4a294bbb26814c204c613dfe5fb5d2d9cf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 061ddb73088198fd52acabba6a3db5d66ee7d965f8d34eaf1aef29af62e45bc41ef41a10565dca26cf85d132112da400534c22c507764f0dd315626190f6cf5f
|
7
|
+
data.tar.gz: a09448741593b69288b0f32cb1428eafd25e009063f651f8759f8614eb31b1ddd3b0267aa21a5cdd4a119bac250e75e5a28b463ec584fd44944faecf4b16056f
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright for portions of project ssh-sudo-next are held by Kentaro Imai, 2015 as part of project Bar. All other copyright for project Foo are held by Saverio Miroddi, 2021.
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# SSHKit::Sudo ("next" version)
|
2
|
+
|
3
|
+
Fork of the [SSHKit::Sudo](https://github.com/kentaroi/sshkit-sudo.git) project, with significant cleanups and some improvements.
|
4
|
+
|
5
|
+
As the original project, this gem allows:
|
6
|
+
|
7
|
+
- sudo commands with password support;
|
8
|
+
- perform all the tasks of a given session (eg. a whole deploy) as a specified user.
|
9
|
+
|
10
|
+
We're in the process of preparing and releasing thorough documentation (estimated time: beginning of May/2021).
|
data/Rakefile
ADDED
data/lib/sshkit/sudo.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'sshkit'
|
2
|
+
|
3
|
+
# This is cosmetic, although it considerably improves the output. See https://github.com/capistrano/sshkit/issues/490.
|
4
|
+
#
|
5
|
+
module SSHKit
|
6
|
+
class Command
|
7
|
+
def with(&_block)
|
8
|
+
return yield if options[:user]
|
9
|
+
env_string = environment_string
|
10
|
+
return yield if env_string.empty?
|
11
|
+
"( export #{env_string} ; #{yield} )"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module SSHKit
|
2
|
+
module Sudo
|
3
|
+
# Slightly simplified version of the original:
|
4
|
+
#
|
5
|
+
# - assumed that there is a password
|
6
|
+
# - assumes the same pwd for the hosts
|
7
|
+
# - replaced the class/dead methods with constants
|
8
|
+
# - removed the InteractionHandler subclass
|
9
|
+
#
|
10
|
+
class PasswordSendingInteractionHandler
|
11
|
+
WRONG_PASSWORD_MESSAGE_REGEX = /Sorry.*\stry\sagain/
|
12
|
+
PASSWORD_PROMPT_REGEX = /[Pp]assword.*:/
|
13
|
+
|
14
|
+
def initialize(servers_password)
|
15
|
+
@servers_password = servers_password
|
16
|
+
end
|
17
|
+
|
18
|
+
def on_data(command, stream_name, data, channel)
|
19
|
+
raise "Wrong password!" if data =~ WRONG_PASSWORD_MESSAGE_REGEX
|
20
|
+
|
21
|
+
if data =~ PASSWORD_PROMPT_REGEX
|
22
|
+
pass = @servers_password
|
23
|
+
channel.send_data(pass)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'sshkit'
|
2
|
+
require_relative 'password_sending_interaction_handler'
|
3
|
+
require_relative 'command'
|
4
|
+
|
5
|
+
module SSHKit
|
6
|
+
module Backend
|
7
|
+
# Values that are static, like the filtered logging patterns, or the name of the env variables,
|
8
|
+
# can be implemented, if needed, as Configuration attributes.
|
9
|
+
#
|
10
|
+
class SudoNetssh < Netssh
|
11
|
+
PASSWORD_PROMPT_REGEX = /\[sudo\] password for \S+\:/
|
12
|
+
|
13
|
+
SKIP_STDOUT_LOGGING_PATTERNS = [
|
14
|
+
/^\r\n$/,
|
15
|
+
PASSWORD_PROMPT_REGEX, # Skipping this removes context from the wrong password prompt, but it's
|
16
|
+
# still understandable.
|
17
|
+
]
|
18
|
+
|
19
|
+
class << self
|
20
|
+
# `pool` is a class-level instance variable, so we can't use the superclass' attr_accessor.
|
21
|
+
# The attribute is only read though, so we don't need to handle assignments.
|
22
|
+
#
|
23
|
+
def pool
|
24
|
+
self.superclass.pool
|
25
|
+
end
|
26
|
+
|
27
|
+
# It's not possible to send a custom configuration class, so we need to create a custom one.
|
28
|
+
# The :owner could be an instance variable, but it's a bit ugly to use a different setting
|
29
|
+
# strategy.
|
30
|
+
#
|
31
|
+
def config
|
32
|
+
@config ||= Class.new(Netssh::Configuration) do
|
33
|
+
attr_accessor :owner, :servers_password, :commands_log
|
34
|
+
end.new
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(*args, &block)
|
39
|
+
super
|
40
|
+
|
41
|
+
@interaction_handler = SSHKit::Sudo::PasswordSendingInteractionHandler.new(self.class.config.servers_password + "\n")
|
42
|
+
end
|
43
|
+
|
44
|
+
def capture(*args)
|
45
|
+
# To ensure that we clean out the sudo part when the results are returned,
|
46
|
+
# otherwise the commands will be corrupt.
|
47
|
+
#
|
48
|
+
super.gsub(PASSWORD_PROMPT_REGEX, '')
|
49
|
+
end
|
50
|
+
|
51
|
+
# Required because the uploaded file is owned by the SSH user, not the owner.
|
52
|
+
#
|
53
|
+
def upload!(local, remote, options = {})
|
54
|
+
super
|
55
|
+
|
56
|
+
# We can't check the user inside the :as block, because @user is root.
|
57
|
+
#
|
58
|
+
target_user = @user || self.class.config.owner
|
59
|
+
|
60
|
+
as :root do
|
61
|
+
execute :chown, target_user, remote
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def command(args, options)
|
68
|
+
options[:interaction_handler] ||= @interaction_handler
|
69
|
+
|
70
|
+
env = (@env || {})
|
71
|
+
|
72
|
+
user = @user || self.class.config.owner
|
73
|
+
|
74
|
+
# As general Linux practice, switching user is not enough - some variables need to be updated
|
75
|
+
# as well.
|
76
|
+
# For an explanation, see https://saveriomiroddi.github.io/Chef-properly-run-a-resource-as-alternate-user.
|
77
|
+
#
|
78
|
+
user_home = user == 'root' ? '/root' : "/home/#{user}"
|
79
|
+
env = env.merge(user: user, home: user_home)
|
80
|
+
|
81
|
+
# Sshkit::Command#user runs commands in a non-login shell, so that variables are not inherited.
|
82
|
+
# We can workaround this by setting them in the env, which is `export`ed, however, only the
|
83
|
+
# specified ones are. Since `RAILS_ENV` is common, we pass it.
|
84
|
+
#
|
85
|
+
env = env.merge(rails_env: fetch(:rails_env))
|
86
|
+
|
87
|
+
SSHKit::Command.new(*args, options.merge(
|
88
|
+
{
|
89
|
+
in: pwd_path,
|
90
|
+
env: env,
|
91
|
+
host: @host,
|
92
|
+
user: user,
|
93
|
+
group: @group,
|
94
|
+
}
|
95
|
+
))
|
96
|
+
end
|
97
|
+
|
98
|
+
def execute_command(cmd)
|
99
|
+
output.log_command_start(cmd)
|
100
|
+
cmd.started = true
|
101
|
+
exit_status = nil
|
102
|
+
with_ssh do |ssh|
|
103
|
+
ssh.open_channel do |chan|
|
104
|
+
chan.request_pty
|
105
|
+
prepared_command = cmd.to_command
|
106
|
+
|
107
|
+
if self.class.config.commands_log
|
108
|
+
IO.write(self.class.config.commands_log, prepared_command + "\n", mode: 'a')
|
109
|
+
end
|
110
|
+
|
111
|
+
chan.exec prepared_command do |_ch, _success|
|
112
|
+
chan.on_data do |ch, data|
|
113
|
+
cmd.on_stdout(ch, data)
|
114
|
+
skip_stdout_logging = SKIP_STDOUT_LOGGING_PATTERNS.any? { |pattern| data =~ pattern }
|
115
|
+
output.log_command_data(cmd, :stdout, data) unless skip_stdout_logging
|
116
|
+
end
|
117
|
+
chan.on_extended_data do |ch, _type, data|
|
118
|
+
cmd.on_stderr(ch, data)
|
119
|
+
output.log_command_data(cmd, :stderr, data)
|
120
|
+
end
|
121
|
+
chan.on_request("exit-status") do |_ch, data|
|
122
|
+
exit_status = data.read_long
|
123
|
+
end
|
124
|
+
#chan.on_request("exit-signal") do |ch, data|
|
125
|
+
# # TODO: This gets called if the program is killed by a signal
|
126
|
+
# # might also be a worthwhile thing to report
|
127
|
+
# exit_signal = data.read_string.to_i
|
128
|
+
# warn ">>> " + exit_signal.inspect
|
129
|
+
# output.log_command_killed(cmd, exit_signal)
|
130
|
+
#end
|
131
|
+
chan.on_open_failed do |_ch|
|
132
|
+
# TODO: What do do here?
|
133
|
+
# I think we should raise something
|
134
|
+
end
|
135
|
+
chan.on_process do |_ch|
|
136
|
+
# TODO: I don't know if this is useful
|
137
|
+
end
|
138
|
+
chan.on_eof do |_ch|
|
139
|
+
# TODO: chan sends EOF before the exit status has been
|
140
|
+
# writtend
|
141
|
+
end
|
142
|
+
end
|
143
|
+
chan.wait
|
144
|
+
end
|
145
|
+
ssh.loop
|
146
|
+
end
|
147
|
+
# Set exit_status and log the result upon completion
|
148
|
+
if exit_status
|
149
|
+
cmd.exit_status = exit_status
|
150
|
+
output.log_command_exit(cmd)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end # class SudoNetssh
|
154
|
+
end # module Backend
|
155
|
+
end # module SSHKit
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'sshkit/sudo/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "sshkit-sudo-next"
|
8
|
+
spec.version = SSHKit::Sudo::VERSION
|
9
|
+
spec.authors = ["Saverio Miroddi"]
|
10
|
+
spec.email = ["saverio.pub2@gmail.com"]
|
11
|
+
spec.summary = %q{SSHKit extension, for sudo operation with password input.}
|
12
|
+
spec.description = %q{SSHKit extension, for sudo operation with password input.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.required_ruby_version = ">= 1.9.3"
|
22
|
+
|
23
|
+
spec.add_dependency "sshkit", "~> 1.20.0"
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sshkit-sudo-next
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Saverio Miroddi
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-04-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sshkit
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.20.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.20.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.7'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.7'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
description: SSHKit extension, for sudo operation with password input.
|
56
|
+
email:
|
57
|
+
- saverio.pub2@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- Gemfile
|
64
|
+
- LICENSE.txt
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- lib/sshkit/sudo.rb
|
68
|
+
- lib/sshkit/sudo/command.rb
|
69
|
+
- lib/sshkit/sudo/password_sending_interaction_handler.rb
|
70
|
+
- lib/sshkit/sudo/sudo_netssh.rb
|
71
|
+
- lib/sshkit/sudo/version.rb
|
72
|
+
- sshkit-sudo-next.gemspec
|
73
|
+
homepage: ''
|
74
|
+
licenses:
|
75
|
+
- MIT
|
76
|
+
metadata: {}
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 1.9.3
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubygems_version: 3.1.4
|
93
|
+
signing_key:
|
94
|
+
specification_version: 4
|
95
|
+
summary: SSHKit extension, for sudo operation with password input.
|
96
|
+
test_files: []
|