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

Fix to raise specific error when ssh user is not provided and root is used as default user. #466

Conversation

Vasu1105
Copy link
Contributor

@Vasu1105 Vasu1105 commented Jun 7, 2019

Signed-off-by: Vasu1105 vasundhara.jagdale@msystechnologies.com

Description

When ssh user is not provided while doing Train connection it throws following error

Train::PlatformDetectionFailed (Sorry, we are unable to detect your platform)

This issue we come across when we trying to bootstrap the node using ssh connection in Chef as mentioned here chef/chef#8550

And while debugging realized that the fix should go in the Train

Following are the way to reproduce the issue using train.

t = Train.create('ssh', host: "host-name", port: 22, user: "root", key_files: "/home/msys/.ssh/id_rsa", verify_host_key: :accept_new, sudo: true)
conn = t.connection

Below is the trackback of the error

Traceback (most recent call last):
       12: from /opt/chef/embedded/bin/irb:23:in `<main>'
       11: from /opt/chef/embedded/bin/irb:23:in `load'
       10: from /opt/chef/embedded/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
        9: from (irb):2
        8: from /home/msys/workspace/train/lib/train/transports/ssh.rb:82:in `connection'
        7: from /home/msys/workspace/train/lib/train/transports/ssh.rb:238:in `create_new_connection'
        6: from /home/msys/workspace/train/lib/train/transports/ssh.rb:238:in `new'
        5: from /home/msys/workspace/train/lib/train/transports/ssh_connection.rb:53:in `initialize'
        4: from /home/msys/workspace/train/lib/train/extras/command_wrapper.rb:169:in `load'
        3: from /home/msys/workspace/train/lib/train/plugins/base_connection.rb:110:in `platform'
        2: from /home/msys/workspace/train/lib/train/platforms/detect.rb:9:in `scan'
        1: from /home/msys/workspace/train/lib/train/platforms/detect/scanner.rb:41:in `scan'
Train::PlatformDetectionFailed (Sorry, we are unable to detect your platform)

After giving the fix the output of the same on console:

Traceback (most recent call last):
        Traceback (most recent call last):
       16: from /home/msys/workspace/train/lib/train/transports/ssh.rb:238:in `create_new_connection'
       15: from /home/msys/workspace/train/lib/train/transports/ssh.rb:238:in `new'
       14: from /home/msys/workspace/train/lib/train/transports/ssh_connection.rb:53:in `initialize'
       13: from /home/msys/workspace/train/lib/train/extras/command_wrapper.rb:165:in `load'
       12: from /home/msys/workspace/train/lib/train/plugins/base_connection.rb:110:in `platform'
       11: from /home/msys/workspace/train/lib/train/platforms/detect.rb:9:in `scan'
       10: from /home/msys/workspace/train/lib/train/platforms/detect/scanner.rb:27:in `scan'
        9: from /home/msys/workspace/train/lib/train/platforms/detect/scanner.rb:27:in `each'
        8: from /home/msys/workspace/train/lib/train/platforms/detect/scanner.rb:33:in `block in scan'
        7: from /home/msys/workspace/train/lib/train/platforms/detect/scanner.rb:45:in `scan_children'
        6: from /home/msys/workspace/train/lib/train/platforms/detect/scanner.rb:45:in `each'
        5: from /home/msys/workspace/train/lib/train/platforms/detect/scanner.rb:46:in `block in scan_children'
        4: from /home/msys/workspace/train/lib/train/platforms/detect/scanner.rb:46:in `instance_eval'
        3: from /home/msys/workspace/train/lib/train/platforms/detect/specifications/os.rb:46:in `block in load'
        2: from /home/msys/workspace/train/lib/train/platforms/detect/helpers/os_common.rb:46:in `unix_uname_s'
        1: from /home/msys/workspace/train/lib/train/platforms/detect/helpers/os_common.rb:38:in `command_output'
Train::UserError (SSH failed: Please login as the user "azure" rather than the user "root".)

And this is the output of fix when using with knife bootstrap

bundle exec knife bootstrap <host-name> -N vj-test-ssh -c ~/workspace/chef-repo/.chef/knife.rb --ssh-identity-file ~/.ssh/id_rsa --sudo --yes
Connecting to 104.211.52.56
WARN: [SSH] PTY requested: stderr will be merged into stdout
WARN: [SSH] PTY requested: stderr will be merged into stdout
ERROR: Train::UserError: SSH failed: Please login as the user "azure" rather than the user "root".

Related Issue

chef/chef#8550

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New content (non-breaking change)
  • Breaking change (a content change which would break existing functionality or processes)

Checklist:

  • I have read the CONTRIBUTING document.

@Vasu1105 Vasu1105 changed the title Fix wrong error message when root is used as default user when ssh user is not provided and if the root user don't have ssh permissions Fix wrong error message when root is used as default ssh user when not provided and if the root user don't have ssh permissions Jun 7, 2019
@Vasu1105 Vasu1105 changed the title Fix wrong error message when root is used as default ssh user when not provided and if the root user don't have ssh permissions [Do not merge]Fix wrong error message when root is used as default ssh user when not provided and if the root user don't have ssh permissions Jun 10, 2019
Copy link
Contributor

@zenspider zenspider left a comment

Choose a reason for hiding this comment

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

Thank you for this PR!

I'm not sure this is the right place to put this check (but I'm new, so...). I've also made some other recommendation to make the ruby more idiomatic. Finally, I think this needs tests to show how it works in both the positive and exceptional cases.

@@ -162,7 +162,13 @@ class CommandWrapper
include_options WindowsCommand

def self.load(transport, options)
if transport.os.unix?
if transport.class == Train::Transports::SSH::Connection && !transport.connected?
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm pretty new so I'm not sure my analysis of train itself is all that accurate... That said:

It looks to me like it is transport.os.unix? itself that is the method call that causes an actual connection to be established, so having this code before that call is ensuring that transport.connected? is false.

Copy link
Contributor Author

@Vasu1105 Vasu1105 Jun 11, 2019

Choose a reason for hiding this comment

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

Thank you for this PR!

I'm not sure this is the right place to put this check (but I'm new, so...). I've also made some other recommendation to make the ruby more idiomatic. Finally, I think this needs tests to show how it works in both the positive and exceptional cases.

@zenspider I was also not sure whether this the right place to do this. So I was debugging into the code more and also wanted some input so that I get right direction. I have removed this now and fixed in the os_common.rb file where the run_command is executing when we call transport.os.unix? in command_wrapper.rb. So as this is the fix for ssh connection I am checking the class of the connection. Second as the command execution with root user for ssh connection results in standard output Please login as the user "azure" rather than the user "root" I am doing string match. This looks to be a right fix as it raises appropriate error to user on root level. But Ia m open for suggestions. I am trying to understand the existing test suite so that I can add code coverage to this.

if transport.os.unix?
if transport.class == Train::Transports::SSH::Connection && !transport.connected?
# Need to call run_command here to get the message and show it to user while raising exception
msg = transport.run_command("whoami").stdout
Copy link
Contributor

Choose a reason for hiding this comment

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

Presuming that we are in an error state, the use of whoami is arbitrary (we might be trying to connect to windows or other systems). It might be clearer to use a command like "does_not_exist".

msg = transport.run_command("whoami").stdout
# this will throw the error as
# Train::UserError: Ssh failed: Please login as the user "username" rather than the user "root".
raise Train::UserError.new("Ssh failed: #{msg}")
Copy link
Contributor

Choose a reason for hiding this comment

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

A better (more rubyish) way to say this is: raise Train::UserError, "Ssh failed: #{msg}"

# this will throw the error as
# Train::UserError: Ssh failed: Please login as the user "username" rather than the user "root".
raise Train::UserError.new("Ssh failed: #{msg}")
elsif transport.os.unix?
Copy link
Contributor

Choose a reason for hiding this comment

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

So, my understanding of what this code is trying to do is to take the situation where the OS detection failed (because of auth/connection issues) but provide an error message that makes that more clear. I think it makes more sense from a design / logical flow perspective to push that down to a rescue on this method. IOW, react to the bad condition, rather than make it part of the normal checks:

def self.load(transport, options)
  if transport.os.unix?
    # ...
rescue Train::PlatformDetectionFailed => e
  transport.valididate_connection # either raises or:
  raise e
end

This would require valididate_connection to be an abstract method (stubbed out in the superclass and raises if not overridden) on Transport::BaseConnection and on all supported Transports, which might be out of scope for this PR (not my call).

@@ -101,6 +101,11 @@ def login_command
LoginCommand.new("ssh", args)
end

def connected?
return false if session.nil?
session.exec!("whoami").match?(/Please login as the user/) ? false : true
Copy link
Contributor

Choose a reason for hiding this comment

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

Any time you have a conditional whose branches evaluate to straight booleans is an opportunity to remove the conditional entirely. For example:

# original

return false if session.nil?
session.exec!("whoami").match?(/Please login as the user/) ? false : true

# abstract:

return false if x.nil?
x.y ? false : true

# avoid `nil?`:

return false if !x
x.y ? false : true

# combine the logic:

!x || x.y ? false : true

# flip the logic to put true first:

!(!x || x.y) ? true : false

# at this point: we don't need a conditional or the true/false:

!(!x || x.y)

# apply DeMorgan's Law to clean it up: !(a||b) -> !a&&!b

x && !x.y

# unabstract:

session && !session.exec!("whoami").match?(/Please login as the user/)

The final version actually reads like what it is trying to do: "If there is a session and running whoami doesn't match 'blah'".

I'd take it one step further and not use match:

session && !session.exec!("whoami") =~ /Please login as the user/

# then push `!` down:

session && session.exec!("whoami") !~ /Please login as the user/

(but that is personal preference (but also bears out in benchmarks))

@zenspider
Copy link
Contributor

Also, instead of "Do not merge" there's a new PR type for drafts! It's in the pop-down in the button as you're submitting the PR.

@Vasu1105
Copy link
Contributor Author

Thank you for this PR!

I'm not sure this is the right place to put this check (but I'm new, so...). I've also made some other recommendation to make the ruby more idiomatic. Finally, I think this needs tests to show how it works in both the positive and exceptional cases.

Thank you @zenspider for your valuable review comments. I am also new to the train connection code so definitely will have a look at all your suggestions and if require will connect with you if there are any blockers.

@Vasu1105
Copy link
Contributor Author

Vasu1105 commented Jun 11, 2019

Also, instead of "Do not merge" there's a new PR type for drafts! It's in the pop-down in the button as you're submitting the PR.

Good to know 👍

@zenspider
Copy link
Contributor

@Vasu1105 btw... I'm in UTC-8 but I'm also a nocturne, so we can arrange to chat live if it is needed / helps.

@Vasu1105
Copy link
Contributor Author

@Vasu1105 btw... I'm in UTC-8 but I'm also a nocturne, so we can arrange to chat live if it is needed / helps.

Sure. Thank you.

@Vasu1105 Vasu1105 changed the title [Do not merge]Fix wrong error message when root is used as default ssh user when not provided and if the root user don't have ssh permissions Fix wrong error message when root is used as default ssh user when not provided and if the root user don't have ssh permissions Jun 12, 2019
@Vasu1105 Vasu1105 changed the title Fix wrong error message when root is used as default ssh user when not provided and if the root user don't have ssh permissions Fix to raise specific error when ssh user is not provided and root is used as default user. Jun 13, 2019
@Vasu1105 Vasu1105 force-pushed the vasundhara/Fix_for_giving_right_login_user_error__to_user branch 2 times, most recently from 5fec477 to 6b271fc Compare June 14, 2019 06:19
… ssh connection and it don't have access previlages

Signed-off-by: Vasu1105 <vasundhara.jagdale@msystechnologies.com>
@Vasu1105 Vasu1105 force-pushed the vasundhara/Fix_for_giving_right_login_user_error__to_user branch from 6b271fc to cb36939 Compare June 14, 2019 08:35
@tas50 tas50 merged commit 1306b0a into inspec:master Jun 26, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants