Skip to content

Conversation

@smorrisj
Copy link
Contributor

@smorrisj smorrisj commented Aug 1, 2024

Summary

  • Adds support for username and password to the 9.4 Remote (SSH) connection type.
  • Refactor SSH auth process to use a handler that can support use of agent, manually specifying a private keyfile (with or without a passphrase) in the connection profile, using a traditional username password, or using keyboard-interactive mode, depending on how the ssh server is configured.
  • Run initialization code to set working dir to the WORK lib path to autocleanup ODS results on session close
  • Add keepalive configuration to ssh client to prevent idle disconnects for long-running programs

Todos:

  • Use authHandler to handle authentication requests. See ssh2 docs for authHandler support. See prototype by @chunky here. We need to give some thought on how we define what supported auth methods should be attempted. I think it would be great to let the server tell us what the supported methods are, and then react to them. Kerberos methods are going to be a problem because the ssh2 library cant support those auth methods currently. For each supported method, the auth should be attempted, moving onto the next one during a failure to avoid issues like this.
  • Username/Password Support. Prompt for password "Just in time" on session establishment. Store password in Secrets API so that the user is not prompted for a password on each session establishment after initial profile creation. Use masked input field like we do for IOM on the text input prompt.
  • Keyboard Interactive support. Similar to, but not the same as password. MFA scenarios will request this auth method. We need to detect the incoming requests for input and prompt the user for answers, before sending on the answers to the auth process.
  • Maintain support for agent based authentication for users that want passwordless or agent based setup. Users should still be able to specify an identify file for keys that have a passphrase.
  • The setTimeout logic will need to be adjusted so that we dont errantly timeout while a user is typing in a password or passphrase.
  • Add username to password prompt text
  • Add vscode metadata for new privateKeyFilePath profile field
  • Add support for work directory detection for [SSH Connection] Better results file handling #1176
  • Add keepalives and max session time config options for Extremely long analytic jobs, and quiescent use, time out SSH connections #1156
  • Unit tests
  • Doc updates

Testing

Connection Profile Prompts:

  • Create new connection profile should offer private key path as an optional prompt and create the profile with the value that the user specifies

Auth Refactor:
Users can successfully authenticate to the server to run sas programs using the following auth methods:

  • SSH Agent (See current doc)
  • Private key specified in privateKeyFilePath field in connection profile (no passphrase)
  • Public key specified in privateKeyFilePath field in connection profile (with a passphrase)
  • Using a traditional username/password
  • Using Keyboard Interactive (RHEL disables this by default in modern versions, will need to enable it in sshd config on the server and set the AuthenticationMethods field such that this method is used instead of password)

Work Directory Detection

  • ODS SAS result html files are no longer written out to the users home directory on the SSH Server
  • Verify that the SAS Log Output contains an output entry for the working dir getting set to the WORK dir generated.
  • User can change the work directory by passing -WORK [path] option as a sasOption on the connection profile options array

Keepalive Changes:

  • Verify that long running program (using sleep function) does not result in the ssh server disconnecting the client

@smorrisj smorrisj linked an issue Aug 1, 2024 that may be closed by this pull request
@smorrisj
Copy link
Contributor Author

smorrisj commented Aug 1, 2024

Pushed a super rough draft of auth handler logic this morning. Very rough around the edges but wanted to get thoughts down in some (semi) organized manner. Will further test and refine as time allows.

@chunky
Copy link

chunky commented Aug 2, 2024

First up, thank you for working on this. It'll be a big improvement for us.

A few random thoughts:

  1. You can find a conversation I had with a maintainer of ssh2 about authhandlers here: Request: Delay client password entry until password actually needed mscdex/ssh2#1400 . Things that might be relevant from your comments and code so far:
    1. The authhandler is only called with a list of methods that would work for that server
    2. "agent" and "private key" are different, in the authhandler callback.
    3. Note this from the ssh2 doc you link: "Valid method names are: ''none', 'password', 'publickey', 'agent', 'keyboard-interactive', 'hostbased'"
  2. I feel pretty strongly that attempting all non-interactive options on the table first, and only then trying the interactive ones is the right choice. If agent would be accepted, and I have a running agent, then obviously that should be tried before asking users for a password, for example.
  3. Don't forget keyboard-interactive, as separate from password. Some server configurations ask for "keyboard-interactive" authentication and ask you for a password in a message, rather than asking for "password" authentication. [set sshd config "ChallengeResponseAuthentication yes" to see this]
  4. Affording use of an unshielded private key as a separate config item, and proffering it if "private key" method is available, would really help
  5. I have no strong feelings about password storage in secrets. The main concern is, users have to rotate passwords regularly; what happens if the password in storage is no longer the same one that works for the server? Honestly for the first pass, asking for it each time doesn't seem unreasonable.
    1. users can set up public key auth if they don't like it

From your top message: "Users should still be able to specify an identify file for keys that have a passphrase." ; do you mean "for keys that don't have a passphrase"? For keys that do, you would need to develop the ability to de-shield them after loading them from disk, to pass to the private key method. Probably safer to just expect users to set up ssh-agent, if that's where they're at. [although it's true that that is unfortunate on windows]

this._config = c;
this.conn = new Client();
this._conn = new Client();
this._authMethods = ["publickey", "password", "keyboard-interactive"];
Copy link
Contributor Author

@smorrisj smorrisj Aug 2, 2024

Choose a reason for hiding this comment

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

Attempt non-interactive methods first, falling back to interactive ones.
@chunky in a few test scenarios in house, I'm seeing the auth method for agent and public key coming back from the server simply as "publickey". I'm wondering if the library is adding in agent based on set config options?

Essentially, these strings are well known auth methods that are supported by ssh itself. I think it would be great to get these from the server by initially sending "none" up to prompt for a response. We need to then roll through the list of methods that we can support in the extension (which should be most of them except for the kerberos methods) and then make sure that before we try it, that it's in the "server supported list".

parsedKeyResult instanceof Error &&
parsedKeyResult.message ===
"Encrypted OpenSSH private key detected, but no passphrase given"
) {
Copy link
Contributor Author

@smorrisj smorrisj Aug 2, 2024

Choose a reason for hiding this comment

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

The ssh2 library has a function that can assist with detecting that the key file specified on the config property is encrypted, which is a nice help to allow users to specify shielded and unshielded key paths on the connection profile (if they dont want to / cant use use agent). Right now with the code below if we detect a passphrase we'd prompt for it before sending it on. Generally I'm against storing these kinds of passphrases in a secret for reasons mentioned above. If a user wants passphrase persistence then perhaps agent is the ideal solution.

});
}
}
} else if (process.env.SSH_AUTH_SOCK) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Pretty straightforward, this is the agent method that we supported in the before state. Users can still elect not to specify a key file on the connection profile and have the agent do all of the work (if they have set it up).

});
}
break;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Support both password and keyboard-interactive. Prompt user "just in time".

@smorrisj
Copy link
Contributor Author

smorrisj commented Aug 2, 2024

First up, thank you for working on this. It'll be a big improvement for us.

A few random thoughts:

  1. You can find a conversation I had with a maintainer of ssh2 about authhandlers here: Request: Delay client password entry until password actually needed mscdex/ssh2#1400 . Things that might be relevant from your comments and code so far:

    1. The authhandler is only called with a list of methods that would work for that server
    2. "agent" and "private key" are different, in the authhandler callback.
    3. Note this from the ssh2 doc you link: "Valid method names are: ''none', 'password', 'publickey', 'agent', 'keyboard-interactive', 'hostbased'"
  2. I feel pretty strongly that attempting all non-interactive options on the table first, and only then trying the interactive ones is the right choice. If agent would be accepted, and I have a running agent, then obviously that should be tried before asking users for a password, for example.

  3. Don't forget keyboard-interactive, as separate from password. Some server configurations ask for "keyboard-interactive" authentication and ask you for a password in a message, rather than asking for "password" authentication. [set sshd config "ChallengeResponseAuthentication yes" to see this]

  4. Affording use of an unshielded private key as a separate config item, and proffering it if "private key" method is available, would really help

  5. I have no strong feelings about password storage in secrets. The main concern is, users have to rotate passwords regularly; what happens if the password in storage is no longer the same one that works for the server? Honestly for the first pass, asking for it each time doesn't seem unreasonable.

    1. users can set up public key auth if they don't like it

From your top message: "Users should still be able to specify an identify file for keys that have a passphrase." ; do you mean "for keys that don't have a passphrase"? For keys that do, you would need to develop the ability to de-shield them after loading them from disk, to pass to the private key method. Probably safer to just expect users to set up ssh-agent, if that's where they're at. [although it's true that that is unfortunate on windows]

All good feedback. I generally agree with thoughts above. I've put comments around changes that I think support these items. On the issue of whether or not to store the passwords in secrets, we have some precedent for doing this in other connection types. I do agree though that generally for ssh interaction with other solutions, anytime passwordless or passphraseless interactions are intended, that I've seen docs push users towards agent or public key.

@jbreitman
Copy link

I would like to see the authentication method gssapi-with-mic added. This will be beneficial to those in corporate environments where Kerberos Tickets enable you to access other resources such as file systems, databases, APIs, etc ... while eliminating the need for the person to enter their credentials.

@chunky
Copy link

chunky commented Aug 2, 2024

If you can figure it out, yes, the kerberos SSO stuff is great where available. That was another thing I couldn't figure out. It works on some of our servers. Another of those "if it works it's magical and absolutely preferred".

I think the distinction between "agent" and "public key" is a manifestation from the ssh2 library, not the server. In practice it might mean taking two bites at the key-based apple, which is explicitly afforded by the Auth handler mechanism

@smorrisj
Copy link
Contributor Author

smorrisj commented Aug 2, 2024

I think the distinction between "agent" and "public key" is a manifestation from the ssh2 library, not the server. In practice it might mean taking two bites at the key-based apple, which is explicitly afforded by the Auth handler mechanism

Yea that's what the current changeset has, inside of "publickey" we'd look for whether or not the user has set their keyfile on the connection profile, if so we'd use that file, otherwise we'd defer to agent.

I would like to see the authentication method gssapi-with-mic added. This will be beneficial to those in corporate environments where Kerberos Tickets enable you to access other resources such as file systems, databases, APIs, etc ... while eliminating the need for the person to enter their credentials.

One challenge is that the ssh2 library doesnt support GSSAPI methods yet:
mscdex/ssh2#333

@chunky
Copy link

chunky commented Aug 13, 2024

@smorrisj I missed this comment from earlier: "I'm seeing the auth method for agent and public key coming back from the server simply as "publickey". I'm wondering if the library is adding in agent based on set config options?"

From the server's perspective, these are the same thing; it just wants to do a key-based handshake. But on the client, they're different beasts. With ssh2's 'agent' they go get it from a daemon running on a socket, while 'publickey' is just a thing where you hand ssh2 the ready-to-use bytes.

// SPDX-License-Identifier: Apache-2.0

const SECOND = 1000;
export const KEEPALIVE_INTERVAL = 60 * SECOND; //How often (in milliseconds) to send SSH-level keepalive packets to the server. Set to 0 to disable.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@chunky any thoughts on going with these values for keepalive and max unanswered?

Copy link

Choose a reason for hiding this comment

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

@smorrisj 60 seconds is a reasonable figure - I think I usually set it to 30, but I have no strong feelings as long as it's well inside 90.
The unanswered_threshold, I confess to be unsure about appropriate behaviour. If the comment is right, it's measured in "pings" not in "time". So if you're aiming for 12 hours, it should be (12 * HOUR / KEEPALIVE_INTERVAL). Either way, because this is happening at the SSH/networking level, then 12 hours is probably way too long. Maybe 15 minutes?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks @chunky. Must have forgot my morning coffee when making the first pass on that value =) See 04444a7.

@Zhirong2022
Copy link

Private key specified in privateKeyFilePath field in connection profile (with a passphrase)
1.Remove the public key information from ~/.ssh/authorized_keys file for the ssh server.
2.Run some code
Results:

  1. It keeps asking for passphrase (3 times at least)
  2. It keeps showing 'Connecting to SAS session...' without further response

@scnwwu scnwwu modified the milestones: 1.11.0, 1.12.0 Oct 9, 2024
@Zhirong2022
Copy link

There are two SSH connection profiles and both do not work properly. One was configured as an invalid private key file path, the other one was not reachable with error like 'getaddrinfo ENOTFOUND...', switch between the two profiles, run some code, it did not use the specified one.

I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: 482f2f2
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: bda97f1
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: 86840c7
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: 7404025
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: 69b6664
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: cc667a4
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: 38a575a
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: f0016cb
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: 21284e4
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: 8aa0c23
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: 91a6d0f
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: 0607d64
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: 6ac98e8
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: a092524
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: debafaf
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: e0e26dd
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: 51ce78a
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: ea17b75
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: 3e1f267
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: 76a271f
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: 7a2c0b4
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: 430f80b
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: 04444a7
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: 5f9ec57
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: d3c9cd2
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: 6d28a43
I, Joe Morris <[email protected]>, hereby add my Signed-off-by to this commit: a0b7f88

Signed-off-by: Joe Morris <[email protected]>
@smorrisj
Copy link
Contributor Author

There are two SSH connection profiles and both do not work properly. One was configured as an invalid private key file path, the other one was not reachable with error like 'getaddrinfo ENOTFOUND...', switch between the two profiles, run some code, it did not use the specified one.

Fixed in a0b7f88

@smorrisj
Copy link
Contributor Author

Private key specified in privateKeyFilePath field in connection profile (with a passphrase) 1.Remove the public key information from ~/.ssh/authorized_keys file for the ssh server. 2.Run some code Results:

  1. It keeps asking for passphrase (3 times at least)
  2. It keeps showing 'Connecting to SAS session...' without further response

Refactored error handling to fix issues like this in a0b7f88

@Zhirong2022
Copy link

  1. Input an invalid password or cancel password typing, it will show message 'Encrypted private OpenSSH key detected, but no passphrase given'.
  2. Run the code again it will show message 'Could not connect to the SAS server.' and passphrase input dialog will be opened at the same time.
  3. Now input the correct passphrase, but, there is no corresponding result. Run the code again and type the passphrase, it will work as normal.
proc print data=sashelp.cars(obs=1);run;

@Zhirong2022
Copy link

Private key specified in privateKeyFilePath field in connection profile (with a passphrase)
1.Remove the public key information from ~/.ssh/authorized_keys file for the ssh server.
2.Run some code
Result:
It kept asking for passphrase if the user typed the correct password until the user got message 'Too many authentication failures'.

@smorrisj
Copy link
Contributor Author

The error handling code still wasnt quite right. Made another pass to refactor it to clean it up some: 6e46440

@smorrisj
Copy link
Contributor Author

Private key specified in privateKeyFilePath field in connection profile (with a passphrase) 1.Remove the public key information from ~/.ssh/authorized_keys file for the ssh server. 2.Run some code Result: It kept asking for passphrase if the user typed the correct password until the user got message 'Too many authentication failures'.

There was a lifecycle issue where the ssh socket was dangling open after the run errored out. Fixed in 6e46440

@smorrisj
Copy link
Contributor Author

  1. Input an invalid password or cancel password typing, it will show message 'Encrypted private OpenSSH key detected, but no passphrase given'.
  2. Run the code again it will show message 'Could not connect to the SAS server.' and passphrase input dialog will be opened at the same time.
  3. Now input the correct passphrase, but, there is no corresponding result. Run the code again and type the passphrase, it will work as normal.
proc print data=sashelp.cars(obs=1);run;

Fixed in 6e46440. We now properly close the ssh connection when the user cancels out of the passphrase dialog. Entering blank on password will be treated as an invalid password attempt. Retries allowed are governed by the ssh server settings. So for that it's possible that the user will continue to be prompted until the server responds with "Too many authentication failures", where it will then close the connection.

@Zhirong2022
Copy link

There is no result output if specify a long workdir and a specific pagesize in sasOptions.

proc print data=sashelp.cars(obs=1);run;

@smorrisj
Copy link
Contributor Author

There is no result output if specify a long workdir and a specific pagesize in sasOptions.

proc print data=sashelp.cars(obs=1);run;

The work dir code was setting up the value over multiple lines. In this approach, for long directory values we were getting log output intermixed in between the start element, end element, and directory value. Fixed in 2195eb9

@Zhirong2022
Copy link

There is no result output if specify a long workdir and a specific pagesize in sasOptions.

proc print data=sashelp.cars(obs=1);run;

The work dir code was setting up the value over multiple lines. In this approach, for long directory values we were getting log output intermixed in between the start element, end element, and directory value. Fixed in 2195eb9

@smorrisj I checked the issue in the latest PR build. I can still reproduce it if specify the page size as new value '23'.

@smorrisj
Copy link
Contributor Author

Thanks @Zhirong2022. The log parsing for detecting work dir still was not robust enough. Refactored the code that sets work dir to use a more structured logging output. See fix in cde43f0.

@Zhirong2022
Copy link

All raised issues have been fixed.
Error validation works as following:

Feature: Public key specified in privateKeyFilePath field in connection profile (with a passphrase)
1.Type Enter and close the passphrase dialog
Error message: Failed to generate information to decrypt
2.Type ESC to cancel the passphrase typing
Error message: Encrypted private OpenSSH key detected, but no passphrase given key
3.Input invalid passphrase
Error message: OpenSSH key integrity check failed -- bad passphrase?
4.Remove the public key from the SSH sever with passphrase and type the correct passphrase several times
Error message: Too many authentication failures
5.Leave the passphrase dialog there and waiting
Error: Could not connect to the SAS server.

Feature: Traditional username/password
1.Type Enter to close the password typing or Input invalid password
It attempts to ask the user to type the password for several times
Error message: Too many authentication failures
2.Type ESC to cancel the passphrase typing
Error message: All configured authentication methods failed
3.Leave the password dialog there and waiting
Error: Could not connect to the SAS server.

@Zhirong2022 Zhirong2022 added testing-complete Complete the pull requests testing and removed testing Test the pull requests labels Oct 23, 2024
@smorrisj smorrisj merged commit 4444abc into main Oct 23, 2024
2 checks passed
@smorrisj smorrisj deleted the feat/ssh-userpass branch October 23, 2024 12:58
@Zhirong2022 Zhirong2022 added ready for release Code pushed, but not released in VS code marketplace yet and removed testing-complete Complete the pull requests testing labels Nov 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready for release Code pushed, but not released in VS code marketplace yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[SSH connection] Add password authentication

8 participants