-
Notifications
You must be signed in to change notification settings - Fork 1
Review Comments #1
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,16 +13,45 @@ | |
|
|
||
| #define MAX_KEY_COUNT 10 | ||
|
|
||
| /* | ||
| * processing argv and providing a minimal usage / --help message describing | ||
| * what this program does would be helpful for any production level tool. | ||
| */ | ||
| int main(int argc, char *argv[]) | ||
| { | ||
| ssh_bind bind = ssh_bind_new(); | ||
|
|
||
| // If a file doesn't exist it's ignored. If no host keys are available, | ||
| // ssh_bind_listen will fail immediately with a helpful error. | ||
| /* | ||
| * this mimics the current default host key settings for sshd. | ||
| * | ||
| * if these should ever change then we get an inconsistency. would it | ||
| * be possible to query these paths from libssh? | ||
| * | ||
| * furthermore, what if in sshd_config different settings are found? I | ||
| * understand this tool is focused on setting up new installs, but | ||
| * even custom images could have custom sshd settings, in which case | ||
| * this might produce bad results. | ||
| */ | ||
| ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY, "/etc/ssh/ssh_host_ecdsa_key"); | ||
| ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY, "/etc/ssh/ssh_host_ed25519_key"); | ||
| ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY, "/etc/ssh/ssh_host_rsa_key"); | ||
|
|
||
| /* | ||
| * the default settings from libssh result in sshd listening on IPv4 | ||
| * only on the wildcard address 0.0.0.0. | ||
| * | ||
| * custom settings in sshd_config are for some reason not honored | ||
| * (tested on Tumbleweed with sshd_config in | ||
| * /usr/etc/ssh/sshd_config). | ||
| * | ||
| * also why does it listen only on IPv4, not on IPv6? | ||
| * | ||
| * even in new image installations there might be different network | ||
| * security domains, so listening on all interfaces might be | ||
| * undesirable. | ||
| */ | ||
| if (ssh_bind_listen(bind) < 0) { | ||
| fprintf(stderr, "Failed to listen: %s\n", ssh_get_error(bind)); | ||
| return 1; | ||
|
|
@@ -37,6 +66,16 @@ int main(int argc, char *argv[]) | |
|
|
||
| // Client identifier | ||
| char clientname[INET6_ADDRSTRLEN]; | ||
| /* | ||
| * what is the purpose of this "ssh-pairing" default value of | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's for the comment part of the authorized_keys line. Normally that's the hostname of the client system, but I wanted to avoid the potential issues of a reverse DNS lookup
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Obtaining the numerical IP address of the peer should practically never fail on a connected socket, aside from out of memory situations and alike. It might be better to exit with an error code if no numerical IP address can be obtained. Then no default name is required. It could be worth a thought to add a command line option to pass the desired hostname for the authorized_keys file. This would make the file more descriptive than numerical IP addresses or a default name. If
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Can be done, but that terminates the connection which is a bit annoying. It seemed easier for me to just add a fallback.
Could be done, but IMO the caller could simply replace the whole "comment" column of the output in that case. Initially I targeted ssh-pairing for the initial enrollment for a server, which should require as little input as possible, but it might also be a good usecase for subsequent enrollments from other clients later.
Ok, will do. |
||
| * `clientname`. I find this spot confusing: | ||
| * | ||
| * - it's not really a `clientname`, it's a client's IP address. | ||
| * - INET6_ADDRSTRLEN is the maximum string length for an IPv6 | ||
| * numerical address, but you are copying an arbitrary string label | ||
| * into it. As luck has it, it fits in there, but semantically it | ||
| * doesn't really make any sense. | ||
| */ | ||
| strlcpy(clientname, "ssh-pairing", sizeof(clientname)); | ||
|
|
||
| // Get the client IP if possible, avoid a potentially slow reverse lookup. | ||
|
|
@@ -59,13 +98,21 @@ int main(int argc, char *argv[]) | |
| // the callback-based pubkey handling is not possible: | ||
| // https://gitlab.com/libssh/libssh-mirror/-/issues/146 | ||
|
|
||
| /* | ||
| * I don't really get why the INTERACTIVE auth method is offered in | ||
| * the first place. It looks like the only purpose is to send the | ||
| * "Received %d public keys" message to the connecting client. This | ||
| * should be documented better. | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Almost. Without offering keyboard-interactive, the client shows a
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. A short comment in the code about this wouldn't hurt. Also, at least in my mind (as an expert only, maybe), the message
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Can do and will also try to clarify the section in the README.
Correct. It's basically a trick to turn a simple ssh command which does a "login attempt" into something which looks more like pairing to the user. "Permission denied" is rather misleading and I'd assume that users treat this as an error. Ignoring errors during a security sensitive operation isn't something we should expect or ask for.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But there still seems to be the problem what happens if the client does not accept keyboard-interactive, right? Then there will neither be the "improved error message" nor the verification string visible on the client side.
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct. In that case it's not distinguishable from the client whether the connection got accepted by ssh-pairing-server or a full sshd. In the README it's meanwhile documented that the message on the client is mandatory, so without keyboard-interactive this doesn't work (securely).
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Technically that's not established in the server component at all, that's only after the user confirms that in a later step. From my PoV, the server executable is just a component of a complete system and not by itself complete. The README mentions explicitly that the output must not be used directly.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's true. But at this stage you know that something is off, so why risk continue processing the data?
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It doesn't really process it though.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All right, it offers it for processing by another component. But why?
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Less complexity in the server component. I don't see any downside to offer it. Validation happens only after the process exits anyway. |
||
| */ | ||
| ssh_set_auth_methods(session, SSH_AUTH_METHOD_PUBLICKEY | SSH_AUTH_METHOD_INTERACTIVE); | ||
|
|
||
| char *authorized_keys = strdup(""); | ||
| int keycount = 0; | ||
|
|
||
| ssh_message message; | ||
| while ((message = ssh_message_get(session))) { | ||
| /* I would move the processing done in the while loop into a | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then I'd either need global variables or have to pass multiple pointers...
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could introduce a |
||
| * separate function to get more digestible code portions */ | ||
| int msg_type = ssh_message_type(message), | ||
| msg_subtype = ssh_message_subtype(message); | ||
|
|
||
|
|
@@ -75,6 +122,25 @@ int main(int argc, char *argv[]) | |
| if(ssh_pki_export_pubkey_base64(pubkey, &key_fp) == 0) { | ||
| const char *key_type = ssh_key_type_to_char(ssh_key_type(pubkey)); | ||
| char *new_authorized_keys; | ||
| /* | ||
| * displaying IP addresses could give a false | ||
| * sense of security. On this level the IP | ||
| * address / the message is not verified in | ||
| * any way, except if there would be a client | ||
| * verification, but the client is not | ||
| * authenticated, it just presents its random | ||
| * public keys to the server. | ||
| * | ||
| * a network level attacker could spoof IP | ||
| * addresses and this way the output might | ||
| * look authentic, although it really isn't. | ||
| */ | ||
| /* | ||
| * instead of concatenating all keys into one | ||
| * big `authorized_keys` string I would simply | ||
| * format and print each key as it is | ||
| * encountered. | ||
| **/ | ||
| if (asprintf(&new_authorized_keys, "%s%s %s %s@%s\n", | ||
| authorized_keys, key_type, key_fp, ssh_message_auth_user(message), clientname) > 0) { | ||
| free(authorized_keys); | ||
|
|
@@ -85,6 +151,34 @@ int main(int argc, char *argv[]) | |
| } | ||
| } else if (msg_type == SSH_REQUEST_AUTH && msg_subtype == SSH_AUTH_METHOD_INTERACTIVE) { | ||
| char *msg = NULL; | ||
| /* | ||
| * I nearly overlooked that this message goes out to | ||
| * the connecting client, not to stdout. | ||
| * | ||
| * Providing any information to a yet unauthenticated | ||
| * client can quickly become problematic. It looks | ||
| * like we are only reporting back what the client | ||
| * already knows: the amount of public keys it sent to | ||
| * us, the username it used and the IP address it | ||
| * uses. | ||
| * | ||
| * At least the latter (the IP address) could be a | ||
| * small information leak in some setups where some | ||
| * form of proxy / forwarding / NAT whatever is used. | ||
| * In this case the IP address would leak some network | ||
| * topology detail to unauthenticated clients. | ||
| * | ||
| * I'm not sure if there is much value in sending this | ||
| * out to the unauthenticated client. The user that | ||
| * rightfully uses this tool has two interaction | ||
| * points: The server side and the client side. It | ||
| * could be more clean and more secure to restrict | ||
| * any kind of informational output messages to the | ||
| * server side. | ||
| * | ||
| * Then you could also drop this INTERACTIVE auth path | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some kind of response that indicates a successful reception is IMO important, as it tells the client that it was the "chosen one" for the enrollment. I can remove the
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So you see this as part of the verification process. It could even work out ... the client verifies the server's host key, so at this point the messages returned from the server should be trusted. No man-in-the-middle or IP-spoofer should be able to get to this point. In that case this is a vital part of the verification process and the user needs to be clearly instructed to look out for this message. It might even make sense to report back the fingerprints of the individual keys seen by the server. The
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yep!
I'll try to make that more clear.
That's what I did initially (before 26751d9). It's why
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, so repurposing this channel for public key exchange is a bit hairy after all. A condensed message will have to suffice then I guess. |
||
| * and make the code simpler. | ||
| */ | ||
| if (asprintf(&msg, "Received %d public keys from %s@%s", keycount, ssh_message_auth_user(message), clientname) > 0) { | ||
| ssh_message_auth_interactive_request(message, msg, "", 0, NULL, 0); | ||
| free(msg); | ||
|
|
||
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.
There's some support for
sshd_configparsing like OpenSSH does, but there are differences. libssh needs explicitHostKeyoptions in the config, while sshd uses the ones listed here if noHostKeyappears in the config. I don't see any way to work around that. Worth a feature request upstream I suppose.As a workaround it would be possible to pass a libssh compatible config file explicitly, either as argument to
ssh-pairing-serveror by using the library default location of/etc/libssh/libssh_server.config.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.
That's a pitty. So the pairing-server is not a full drop-in replacement for sshd. And this can have undesirable side effects for non-default configurations.
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.
Yeah, but those are rare, especially when the system is still being set up and there was no opportunity for the user to change anything yet.
I've got to file the libssh issue upstream still, once it's fixed it'll "just work" (tm)
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.
https://gitlab.com/libssh/libssh-mirror/-/issues/234