8000 [WIP] libssh backend by tiennou · Pull Request #5253 · libgit2/libgit2 · GitHub
[go: up one dir, main page]

Skip to content

[WIP] libssh backend #5253

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

Closed
wants to merge 9 commits into from
Closed

Conversation

tiennou
Copy link
Contributor
@tiennou tiennou commented Oct 2, 2019

This adds support for libssh as a possible backend for the SSH transport. The code is untested — the test suite runs fine locally, but I want to see how CI reacts to it as I don't expect everything SSH to be covered by it (keyboard-interactive 🙄).

@tiennou tiennou mentioned this pull request Nov 5, 2019
2 tasks
@tiennou tiennou force-pushed the feature/libssh-backend branch from 8fd3847 to 33ea3d1 Compare November 5, 2019 12:36
@tiennou tiennou force-pushed the feature/libssh-backend branch from 33ea3d1 to 57e1f8e Compare November 14, 2019 23:06
@tiennou
Copy link
Contributor Author
tiennou commented Nov 20, 2019

I forgot to tag the crux of the issue in the PR's cover, but please see #5225 for context.

To clarify my perspective on #5307, ideally git_cert could have a git_cert_ssh_cred opaque object, or something like it, which would allow the user's code to just use the capabilities of their chosen backend, to access key material, auth-methods, or SSH channels directly.

This is not currently handled here, 8000 because I hit one case where the interface we already provide cannot be ported to libssh (the custom signing mechanism), so here's a summary of "currently known issues caused by libssh2": apparently it's gone from Debian (#5225), some requested features are missing (#5258, #5219, #4452), or depend on the libssh2's backend and key types supported (#4289, OpenSSL/libgcrypt only), and IIRC there's another issue…

Copy link
@cuviper cuviper left a comment

Choose a reason for hiding this comment

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

I have just a few notes to fix the libssh2 build.

After that, the testsuite passes for me with -DUSE_SSH=libssh2, but the online test fails with -DUSE_SSH=libssh:

3/6 Testing: online
3/6 Test: online
Command: "/home/jistone/src/libgit2/build-libssh/libgit2_clar" "-v" "-sonline"
Directory: /home/jistone/src/libgit2/build-libssh/tests
"online" start time: Feb 19 13:41 PST
Output:
----------------------------------------------------------
Loaded 383 suites:
Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')

online::badssl...S
online::clone.........S..S.FS.SSF..SSS.S.
online::fetch...........
online::fetchhead......
online::pushSSSSSSSSSSSSSSSSSSSSS
online::remotes......

  1) Failure:
online::clone::ssh_auth_methods [../tests/online/clone.c:505]
  Function call failed: (git_clone(&g_repo, "ssh://github.com/libgit2/TestGitRepository", "./foo", &g_options))
  error -1 (expected -7) - failed to start SSH session: Failed to process system configuration files

  2) Failure:
online::clone::certificate_invalid [../tests/online/clone.c:703]
  Function call failed: (GIT_ECERTIFICATE)
  error -17 (expected -1) - failed to start SSH session: Failed to process system configuration files

<end of output>
Test time =  19.19 sec
----------------------------------------------------------
Test Failed.
"online" end time: Feb 19 13:41 PST
"online" time elapsed: 00:00:19

This is on Fedora 31, libssh-0.9.3-1.fc31.x86_64, if that makes a difference.

@@ -52,7 +52,7 @@ OPTION(TAGS "Generate tags" OFF)
OPTION(PROFILE "Generate profiling information" OFF)
OPTION(ENABLE_TRACE "Enables tracing support" OFF)
OPTION(LIBGIT2_FILENAME "Name of the produced binary" OFF)
OPTION(USE_SSH "Link with libssh2 to enable SSH support" ON)
OPTION(USE_SSH "Enable SSH support. Can be set to a specific backend (libssh2)" ON)
Copy link
8000

Choose a reason for hiding this comment

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

I think this needs to be SET to properly cache the backend value, like:

Suggested change
OPTION(USE_SSH "Enable SSH support. Can be set to a specific backend (libssh2)" ON)
SET(USE_SSH "ON" CACHE STRING "Enable SSH support. Can be set to a specific backend (libssh2)")

@extrawurst
Copy link
8000 extrawurst commented Dec 28, 2020

@tiennou is this still worked on?

Base automatically changed from master to main January 7, 2021 10:09
@tiennou tiennou force-pushed the feature/libssh-backend branch 7 times, most recently from 6d5a883 to d5ef696 Compare January 18, 2021 18:17
@tiennou
Copy link
Contributor Author
tiennou commented Jan 24, 2021

@extrawurst I've rebased it, updated CI, and the only showstopper now is that the libssh backend seem to require some system-level configuration files to exist.

@cuviper Thanks for the review, much appreciated ❤️. Issues (apart from the runtime config ones, and unforeseen bugs 😉) should be fixed.

@tiennou tiennou force-pushed the feature/libssh-backend branch 2 times, most recently from 0dce737 to 02e7c42 Compare January 25, 2021 17:33
@carlosmn
Copy link
Member
carlosmn commented Apr 2, 2021

This is the API endpoint I'm having issues with — the public callback with the libssh2 types — with the libssh API.

There's a fundamental conflict between the goals of the library and the realities of how different all these libraries and network protocols are as we both want to

  1. have the same interface for performing Git operations over an arbitrary network protocol; and
  2. allow the caller to react in whichever is the best way in their environment (language, user interaction)

The former goal means things like auth can get awkward as most HTTP auth doesn't look like SSH auth (and some HTTP auth doesn't look like a lot of other HTTP auth) but the latter means that we want to avoid processing the data too much when the end-user application (or whatever ends up calling the library) needs to interact with the library implementing the network protocol. This is made even more important by the fact that we want to allow callers of the library to plug in their own network layer and then they can devise an arbitrarily complex authentication method as long as both agree.

The wish to have the caller in control but also make it easy to perform the most common operations leads to weird things in the API like libssh2- and curl-specific which try to give the caller all the info they might need short of handing over a pointer to whatever context/handle each library has. At the same time it's rather disappointing how the information is presented by some libraries (it seems libcurl does the data processing we're trying not to do here), but that's a different discussion.

As far as your question about the callback types having specific libssh2 structures, I remember doing this due to the methods needed for the more complex interactions like keyboard-interactive, seem to be tied enough to libssh2 that did not seem realistic that we could provide some generic method for this. And from what I can tell, libssh has a rather different mechanism for doing this. Maybe it's not very sensible for this library to support this.

The same goes for certs where we only got two hashes from libssh2. It looks like the issue described in #5258 and #4452 has been fixed now, though it's not super helpful now that libssh2 isn't in some distros, I suppose. But we do have a place to put it so the caller can decide for themselves how they want to hash the cert if they need to. libssh expects you to ask for which hash you're interested in, which makes sense for it, but I don't know how much it makes for us to expose that detail.

For what it's worth, I have come to think that the design with the callbacks for host and user authentication were a mistake, but changing it to work with returning control to the user by returning is not a small task.

Ultimately, some of this probably isn't solvable as part of this library. We can try to keep it working with the features we have (and maybe not bother with keyboard-interactive for libssh?) and with libssh we can even fill in the info as to whether "we" think the ssh server is something you should trust. But we're still just not going to provide functionality for managing your known_hosts file. If an app does want to provide that, they're going to have to get dirty with libssh themselves or perhaps provide their own transport that talks 8000 to their app via some other means.

All this to say that attaching auth logic into libgit2 is always going to leave us with something somewhat annoying and the real solution is probably to provide some simple options in the library and leave an escape hatch for something like ssh where the amount of complexity is high and also all of this should be very much outside of the things we should have to care about.

Maybe at a later date we can have even built-in transports look like things you plug in so you can keep your own pointer to something if you need more than bog standard behaviour. But that's for when many other pressing problems have been solved.

* - GIT_FEATURE_SSH
* Libgit2 supports the SSH protocol for network operations. This requires
* the libssh2 library to be found when compiling libgit2
* @return A combination of GIT_FEATURE_* values.
Copy link
Member

Choose a reason for hiding this comment

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

Are these changes meant to be in this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not really. Arguably, I wanted to hide references to libssh2, it would look better IMHO in docurium to have the enum values documented directly, and that's DRYer.

unsigned char *hash_ptr = (unsigned char *)&hash;
size_t hash_len;

if (ssh_get_server_publickey(s->session, &pkey) == SSH_OK) {
Copy link
Member

Choose a reason for hiding this comment

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

This block should also add the raw key like we do for libssh2

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Noted. IIRC, there's been some underlying changes to the main code while this was in-flight (SHA256 comes to mind).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed. Note that I'm putting the base64-encoded raw data, because that's what libssh provides.

goto done;
}

/* We don't currently trust any hostkeys */
Copy link
Member

Choose a reason for hiding this comment

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

We should ask libssh whether the hostkey is known and fill in that parameter in the callback.

assert(c->username);
assert(c->privatekey);

rc = libssh2_userauth_publickey_frommemory(
Copy link
Member

Choose a reason for hiding this comment

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

There are a series of ssh_pki_import_privkey_ functions provided by libssh which let you specify an in-memory key in different formats. The one that would let us keep the same for both looks like it would be ssh_pki_import_privkey_base64 as it oddly doesn't seem to allow you just pass the raw data, so we'd have to first convert it into base64.

Copy link
Member

Choose a reason for hiding this comment

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

Or it looks like they might mean base64 as in the format you would find on-disk

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, I'm confused because that's the libssh2 backend, and to be fair, Well, silly me, that's an unported #ifdef block 😅. I hadn't worked at all with libssh beforehand, so I'll have to revisit that. There's a bunch of things that can happen which makes key-handling inconsistent — it's mathematically possible to derive the pubkey from its private counterpart, but not all libssh2 backends handle that. I haven't checked if libssh handles key derivation (if my crypto-fu is correct 😉).

Copy link
Contributor Author
@tiennou tiennou left a comment

Choose a reason for hiding this comment

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

A few notes, sadly it A3E2 's been a while so the context-switch is kinda cold on my side but that's what you get when you drop off the face of the earth.

Just to clarify, the libssh backend as it is should now handle "our" keyboard-interactive interface via a trampoline that just collects all challenges and converts them. It's just that actually setting up a test for that requires a harness/configuration which I haven't really looked at, so it's untested.

The thing that's (IIRC) completely missing from libssh is a way to support our custom hashing. Conversely, libssh seems to refer to GSSAPI mic authentication, which is absent from libssh2 (and gave me a good chuckle as a long-time Uplink player — I wasn't aware that voice authenticated SSH was a thing 😅).

I do agree that, if I was designing this from a blank slate, I'd go for a much more simple "try once with what the user gave us (pass, file/memory pkey, whatever, even a blanket this-asked-for-auth void * handle), or even and fail immediately with no retries", but API-wise that's not possible.

Anyway, I'll try to revisit that soonish, thanks for the review @carlosmn !

* - GIT_FEATURE_SSH
* Libgit2 supports the SSH protocol for network operations. This requires
* the libssh2 library to be found when compiling libgit2
* @return A combination of GIT_FEATURE_* values.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not really. Arguably, I wanted to hide references to libssh2, it would look better IMHO in docurium to have the enum values documented directly, and that's DRYer.

unsigned char *hash_ptr = (unsigned char *)&hash;
size_t hash_len;

if (ssh_get_server_publickey(s->session, &pkey) == SSH_OK) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Noted. IIRC, there's been some underlying changes to the main code while this was in-flight (SHA256 comes to mind).

assert(c->username);
assert(c->privatekey);

rc = libssh2_userauth_publickey_frommemory(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this com F438 ment to others. Learn more.

Sorry, I'm confused because that's the libssh2 backend, and to be fair, Well, silly me, that's an unported #ifdef block 😅. I hadn't worked at all with libssh beforehand, so I'll have to revisit that. There's a bunch of things that can happen which makes key-handling inconsistent — it's mathematically possible to derive the pubkey from its private counterpart, but not all libssh2 backends handle that. I haven't checked if libssh handles key derivation (if my crypto-fu is correct 😉).

tiennou added 4 commits May 26, 2021 11:39
Previously, we would have trampled over whatever "another" user of the
session had set. Let's make sure we strive to behave correctly — I'm
glancing over some details of libssh2, for things like timeouts.
This splits the SSH transport in two parts, a generic implementation, and
a lower level interface to better handle implementation differences.
@tiennou tiennou force-pushed the feature/libssh-backend branch from 02e7c42 to 839e36a Compare May 26, 2021 09:40
int git_ssh_session_server_is_known(git_ssh_session *s, int *valid)
{
GIT_UNUSED(s);
*valid = 0;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This ensures that libssh2's behavior doesn't change (default to "can't say it's valid", a.k.a 0), but now libssh checks for known_hosts validity, while libssh2 doesn't (and it looks messy to handle appropri E26B ately). I'm not even sure if it makes sense to update the known_host file afterwards…

@jkaiser-ioki
Copy link

Just to add my 2 cents. GitKraken (https://www.gitkraken.com/) is using libgit2, but due to the underlying usage of libssh2, it is currently not possible to use the client when talking to git remotes on ssh-hardened servers (e.g. only ed25519 host keys, or restricted allowed-cipher-sets, ...). Seeing this go through would probably improve things significantly on that front :-)

@ethomson
Copy link
Member
ethomson commented Mar 9, 2024

We have openssh support now — I really like libssh (and not so much libssh2) but it feels like openssh is the thing to use for "better openssh compatibility"

@ethomson ethomson closed this Mar 9, 2024
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.

6 participants
0