New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixes #21731 - sudo can be used with passwords now #342
Conversation
describe 'sudo password' do | ||
it 'uses the remote_execution_sudo_password on the host param' do | ||
host.params['remote_execution_sudo_password'] = 'mypassword' | ||
host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_sudo_password', :value => 'mypassword') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style/HashSyntax: Use the new Ruby 1.9 hash syntax.
Metrics/LineLength: Line is too long. [148/80]
|
||
channel = session.open_channel do |ch| | ||
ch.on_data { |_, data| stdout.concat(data) } | ||
ch.on_data do |ch, data| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lint/ShadowingOuterLocalVariable: Shadowing outer local variable - ch.
require_dependency File.expand_path('../../../app/models/setting/remote_execution.rb', __FILE__) if (Setting.table_exists? rescue(false)) | ||
if (Setting.table_exists? rescue(false)) | ||
::Setting::BLANK_ATTRS << 'remote_execution_sudo_password' | ||
require_dependency File.expand_path('../../../app/models/setting/remote_execution.rb', __FILE__) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style/ExpandPathArguments: Use expand_path('../../app/models/setting/remote_execution.rb', dir) instead of expand_path('../../../app/models/setting/remote_execution.rb', FILE).
Metrics/LineLength: Line is too long. [104/80]
@@ -10,7 +10,10 @@ class Engine < ::Rails::Engine | |||
|
|||
|
|||
initializer 'foreman_remote_execution.load_default_settings', :before => :load_config_initializers do | |||
require_dependency File.expand_path('../../../app/models/setting/remote_execution.rb', __FILE__) if (Setting.table_exists? rescue(false)) | |||
if (Setting.table_exists? rescue(false)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style/RescueModifier: Avoid using rescue in its modifier form.
app/models/ssh_execution_provider.rb
Outdated
@@ -4,6 +4,7 @@ def proxy_command_options(template_invocation, host) | |||
super.merge(:ssh_user => ssh_user(host), | |||
:effective_user => effective_user(template_invocation), | |||
:effective_user_method => effective_user_method(host), | |||
:sudo_password => sudo_password(host), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style/HashSyntax: Use the new Ruby 1.9 hash syntax.
@@ -31,6 +31,7 @@ def self.load_defaults | |||
'remote_execution_effective_user_method', | |||
nil, | |||
{ :collection => proc { Hash[SSHExecutionProvider::EFFECTIVE_USER_METHODS.map { |method| [method, method] }] } }), | |||
self.set('remote_execution_sudo_password', N_("Sudo password"), '', N_("Sudo password"), nil, {:encrypted => true}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style/RedundantSelf: Redundant self detected.
Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.
Metrics/LineLength: Line is too long. [124/80]
Layout/SpaceInsideHashLiteralBraces: Space inside { missing.
Style/BracesAroundHashParameters: Redundant curly braces around a hash parameter.
Style/HashSyntax: Use the new Ruby 1.9 hash syntax.
Layout/SpaceInsideHashLiteralBraces: Space inside } missing.
@@ -1,5 +1,6 @@ | |||
class Setting::RemoteExecution < Setting | |||
|
|||
# rubocop:disable Metrics/AbcSize |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lint/MissingCopEnableDirective: Re-enable Metrics/AbcSize cop with # rubocop:enable after disabling it.
|
|
@@ -213,7 +237,7 @@ def run_sync(command, stdin = nil) | |||
started = true | |||
end | |||
end | |||
session.process(0) until started | |||
session.loop(0.1) { !started || (use_sudo_password? && !password_sent) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When testing, I got the script stuck here, because some commands were actually not using sudo
(the run_sync is in rex used mostly for preparing the execution environment, while the sudo is being used just to run the actual user script.
What was the reason to actually change the original line here (as well as in run_async
method?). It seems that when I reverted this particular changes, the things started working.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't sure when run_sync is used and mirrored the changes in run_async. I suppose calling session.process
in a loop is sufficient, but I'm curious now why session.loop
gets stuck, as it is almost the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's not about the loop, but about the actual condition
(use_sudo_password? && !password_sent)as when
sudo` is not used in the command, it would never be true.
OT: why do you prefer loop(0.1)
over process(0)
? Performance?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's not about the loop, but about the actual condition(use_sudo_password? && !password_sent)as whensudo` is not used in the command, it would never be true.
Hmm. The whole expression !started || (use_sudo_password? && !password_sent)
will evaluate to false (which is when the loop is terminated) once the started
flag switched to true
though?
OT: why do you prefer loop(0.1) over process(0)? Performance?
It looks to me that loop makes process(0)
call one more time after the loop terminates. I'm not sure about exact reasoning for this, but my guess is that this eliminates/lowers the chance of losing incoming/outgoing data (which is only received from/sent onto the wire when process
is called).
Would it make sense to have this also exposed in the job invocation form so the user can set the sudo password on a per-job basis the same way as we do for ssh password and key passphrase? |
I think it would be consistent to do so. I am curious however, why ssh password/key passphrase are exposed -- they probably don't change much (if at all) and are tied to the host? |
Although I agree that this is more of an corner case, still I think it would make sense to provide this option for some cases, where one would for some reason needed to have non-unified infrastructure. |
@@ -3,8 +3,8 @@ class PollingScriptRunner < ScriptRunner | |||
|
|||
DEFAULT_REFRESH_INTERVAL = 60 | |||
|
|||
def initialize(options = {}) | |||
super(options) | |||
def initialize(options = {}, user_method) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style/OptionalArguments: Optional arguments should appear at the end of the argument list.
@@ -0,0 +1,5 @@ | |||
class AddSudoPasswordToJobInvocation < ActiveRecord::Migration[4.2] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style/FrozenStringLiteralComment: Missing magic comment # frozen_string_literal: true.
Have yet to update tests for the these changes. |
|
Rubocop is not happy |
|
options.fetch(:secrets, {}).fetch(:sudo_password, nil)) | ||
elsif effective_user_method == 'su' | ||
SuUserMethod.new(options.fetch(:effective_user, nil), | ||
options.fetch(:ssh_user, 'root')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not reuse the effective_user
and ssh_user
extracted on lines 102 and 103?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No reason. Fixed.
@@ -93,6 +93,7 @@ | |||
<div class="advanced hidden"> | |||
<%= password_f f, :password, :placeholder => '*****', :label => _('Password'), :label_help => N_('Password is stored encrypted in DB until the job finishes. For future or recurring executions, it is removed after the last execution.') %> | |||
<%= password_f f, :key_passphrase, :placeholder => '*****', :label => _('Private key passphrase'), :label_help => N_('Key passhprase is only applicable for SSH provider. Other providers ignore this field. <br> Passphrase is stored encrypted in DB until the job finishes. For future or recurring executions, it is removed after the last execution.') %> | |||
<%= password_f f, :sudo_password, :placeholder => '*****', :label => _('Sudo password'), :label_help => N_('Key passhprase is only applicable for SSH provider. Other providers ignore this field. <br> Passphrase is stored encrypted in DB until the job finishes. For future or recurring executions, it is removed after the last execution.') %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The label help should be different from the one for private key passphrase
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the label.
SuUserMethod.new(options.fetch(:effective_user, nil), | ||
options.fetch(:ssh_user, 'root')) | ||
else | ||
raise "effective_user_method ''#{@effective_user_method}'' not supported" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Double single quotes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
channel.on_data { |ch, data| publish_data(data, 'stdout') } | ||
channel.on_data do |ch, data| | ||
publish_data(data, 'stdout') | ||
@user_method.on_data(data, ch) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With this change the password prompt shows up in the jobs output. Maybe it would be nice if this method consumed the output if it matches?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added filtering out of passwords in job output
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The happy path works for me, left some comments inline. Once I somehow got into situation where no password was provided but it still prompted for it and the whole process hang.
It now sends the password through even if it's empty; in your case instead of hanging the script should error out almost immediately. |
Needs a rebase |
*rebased |
@adamruzicka any other comments? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Needs another rebase |
*rebased |
Test failures unrealated, Re-tested and works well. Thanks @witlessbird |
No description provided.