Skip to content
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

Merged
merged 1 commit into from May 4, 2018
Merged

Fixes #21731 - sudo can be used with passwords now #342

merged 1 commit into from May 4, 2018

Conversation

dmitri-d
Copy link
Member

@dmitri-d dmitri-d commented Apr 4, 2018

No description provided.

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')

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|

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__)

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))

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.

@@ -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),

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}),

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

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.

@dmitri-d
Copy link
Member Author

dmitri-d commented Apr 5, 2018

  • disabled Rubocop on Setting.load_defaults method

@dmitri-d
Copy link
Member Author

dmitri-d commented Apr 5, 2018

  • rebased

@@ -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) }
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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.

OT: why do you prefer loop(0.1) over process(0)? Performance?

Copy link
Member Author

@dmitri-d dmitri-d Apr 6, 2018

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).

@adamruzicka
Copy link
Contributor

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?

@dmitri-d
Copy link
Member Author

dmitri-d commented Apr 9, 2018

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?

@iNecas
Copy link
Member

iNecas commented Apr 11, 2018

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)

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]

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.

@dmitri-d
Copy link
Member Author

Have yet to update tests for the these changes.

@dmitri-d
Copy link
Member Author

  • updated tests

@iNecas
Copy link
Member

iNecas commented Apr 23, 2018

Rubocop is not happy

@dmitri-d
Copy link
Member Author

  • rebased
  • fixed rubocop warning

options.fetch(:secrets, {}).fetch(:sudo_password, nil))
elsif effective_user_method == 'su'
SuUserMethod.new(options.fetch(:effective_user, nil),
options.fetch(:ssh_user, 'root'))
Copy link
Contributor

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?

Copy link
Member Author

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.') %>
Copy link
Contributor

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

Copy link
Member Author

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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double single quotes?

Copy link
Member Author

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)
Copy link
Contributor

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?

Copy link
Member Author

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

Copy link
Contributor

@adamruzicka adamruzicka left a 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.

@dmitri-d
Copy link
Member Author

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.

@adamruzicka
Copy link
Contributor

Needs a rebase

@dmitri-d
Copy link
Member Author

*rebased

@iNecas
Copy link
Member

iNecas commented May 3, 2018

@adamruzicka any other comments?

Copy link
Contributor

@adamruzicka adamruzicka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@adamruzicka
Copy link
Contributor

Needs another rebase

@dmitri-d
Copy link
Member Author

dmitri-d commented May 3, 2018

*rebased

@iNecas
Copy link
Member

iNecas commented May 4, 2018

Test failures unrealated, Re-tested and works well. Thanks @witlessbird

@iNecas iNecas merged commit e65c15a into theforeman:master May 4, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
5 participants