-
Notifications
You must be signed in to change notification settings - Fork 2
SSH agent forwarding #65
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: denis-coric/ssh-flow
Are you sure you want to change the base?
SSH agent forwarding #65
Conversation
…duplication in GitProtocol
dcoric
left a comment
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.
I like how you improved the code organization and type safety!
dgl
left a comment
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.
A few security minded comments. I put all the comments here, even if there's some overlap with the PR this is based on.
src/proxy/ssh/sshHelpers.ts
Outdated
| tryKeyboard: false, | ||
| readyTimeout: 30000, | ||
| agent: customAgent, | ||
| algorithms: { |
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.
Is there a reason to set these algorithms? It looks like ssh2 will use somewhat sensible defaults (which will at least include things like ed25519 host keys, which isn't included here). (I see this is mostly moved from the other PR, but it looks like the server isn't specifying algorithms, I'd do the same for the client side.)
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.
Done. Removed explicit algorithm configuration to use ssh2's defaults
src/proxy/ssh/GitProtocol.ts
Outdated
| */ | ||
| hasFlushPacket(): boolean { | ||
| const bufStr = this.buffer.toString('utf8'); | ||
| return bufStr.includes('0000'); |
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.
It feels like this is too rudimentary, take the Linux kernel repo:
$ git log 0000
error: short object ID 0000 is ambiguous
hint: The candidates are:
hint: 000006155029 commit 2025-06-30 - arm64: defconfig: Enable STM32 Octo Memory Manager and OcstoSPI driver
[about 26 more omitted]
and that's just prefixes, in this case a 0000 anywhere in a head's commit ID would collide. There's not a security problem here I don't think, just breaks the protocol, but we implemented a better parser before (38915e7) so it would be good to use it (move it somewhere common?).
(https://github.com/not-an-aardvark/lucky-commit could be useful to test this, if needed.)
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.
Fixed. Moved the proper pkt-line parser from parsePush.ts to a shared module (src/proxy/processors/pktLineParser.ts)
src/proxy/ssh/sshHelpers.ts
Outdated
| if (!client.agentForwardingEnabled) { | ||
| throw new Error( | ||
| 'SSH agent forwarding is required. Please connect with: ssh -A\n' + | ||
| 'Or configure ~/.ssh/config with: ForwardAgent yes', |
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.
Saying ssh -A isn't really actionable in the context most users will see this error. It would be nicer to give people the one liner of:
git config --global core.sshCommand "ssh -A"
In particular configuring this on the git level is far less dangerous than fully enabling agent forwarding.
We also need to be careful with this message, suggesting users enable agent forwarding globally isn't desirable (e.g. https://attack.mitre.org/techniques/T1563/001/) -- it might be useful to let this message be overridden through config too as local policies on how to configure this may vary.
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.
Addressed. Updated error message. Also made the message configurable via ssh.agentForwardingErrorMessage in config.
src/proxy/ssh/server.ts
Outdated
| if (repoPath.startsWith('/')) { | ||
| repoPath = repoPath.substring(1); | ||
| } | ||
| const isReceivePack = command.includes('git-receive-pack'); |
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.
Use startsWith not include to match the check above. (I doubt anyone would make a repo with git-receive-pack in it, but if they did, this could result in some surprises.)
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.
Fixed
| cipher: ['aes128-gcm' as any, 'aes256-gcm' as any, 'aes128-ctr' as any, 'aes256-ctr' as any], | ||
| hmac: ['hmac-sha2-256' as any, 'hmac-sha2-512' as any], | ||
| }, | ||
| }; |
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.
This should also set hostVerifier too and verify the key is as expected.
It would be good to include github.com and gitlab.com host keys by default so this just works for installations, with a way to configure additional keys.
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.
Implemented. Added hostVerifier with default host keys for github.com and gitlab.com. Additional hosts can be configured via ssh.knownHosts in config.
|
|
||
| ### 3. Security Chain Validation Uses HTTPS | ||
|
|
||
| **Important**: Even though the client uses SSH to connect to the proxy, the **security chain validation** (pullRemote action) clones the repository using **HTTPS**. |
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.
I'm wondering about this caveat here - wouldn't there be any issues if the GitProxy instance running internally were to make HTTPS calls to GitHub? I think it's odd that we enforce SSH communication to GitHub when the user makes a push, but we don't enforce it when going through the various push actions...
jescalada
left a comment
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.
Looks good so far! 🚀 I just have some general comments about the code.
| } | ||
|
|
||
| // Calculate SHA-256 fingerprint from SSH public key | ||
| // Note: This function is duplicated in src/service/routes/users.js to keep CLI and server independent |
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.
Do we really need to keep the CLI and server independent? Since the CLI is already importing a few things from the parent package.
Perhaps we could extract this function to src/service/routes/utils.ts for better testing and dealing with potential bugs. 🤔
| sink.findUserBySSHKey(sshKey); | ||
| export const getUsers = (query?: Partial<UserQuery>): Promise<User[]> => sink.getUsers(query); | ||
| export const deleteUser = (username: string): Promise<void> => sink.deleteUser(username); | ||
| export const updateUser = (user: Partial<User>): Promise<void> => sink.updateUser(user); |
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.
Why is the Partial here being removed? I think this was changed at some point to allow proper typing for activeDirectory.ts and some test files.
We could alternatively fix the usages, but it makes the most sense for a DB update function (triggered through a PATCH endpoint) to take a partial version of the related entity.
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.
Might want to rename this to packetLineParser.ts for better searchability!
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.
Alternatively, we could rename this to parsePushUtils and include the other helper functions from parsePush such as getCommitData, getPackData, etc.
This might make the parsePush action a bit easier to understand.
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.
I wonder if we should add checks before accessing private fields. If ssh2 renames any of those, we might end up with a silent or non-descriptive error.
|
|
||
| if (chanMgr && chanMgr._channels) { | ||
| // Find first available channel ID | ||
| while (chanMgr._channels[localChan] !== undefined) { |
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.
Wonder if this wouldn't cause a race condition or clashing of some sort.. Since it's an async function that's relying on and modifying SSH client internals. 🤔
Are we sure that openTemporaryAgentChannel won't get called multiple times in a short period of time?
| // But ssh2 expects only the raw signature bytes (without the algorithm wrapper) | ||
| // because Protocol.authPK will add the algorithm wrapper itself | ||
|
|
||
| // Parse the blob to extract just the signature bytes |
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.
Are we 100% sure that the SSH signature format will always be the same? Just wondering if support for alternative algorithms is necessary.
If SSH connections to GH always use the same algorithm, maybe this isn't needed, but I'm not sure if connections between the user and GitProxy might require additional support.
| * Git uses pkt-line format: [4 byte hex length][payload] | ||
| * Special packet "0000" (flush packet) indicates end of section | ||
| */ | ||
| class PktLineParser { |
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.
Nit: rename to PacketLineParser for searchability/consistency
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.
Seems this file is duplicated (users.ts). We should keep the TS version only!
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.
Same as the previous comment, these changes should be added to /src/service/routes/config.ts 🙂
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.
The UserProfile is starting to get big... Perhaps we could extract the SSH-related stuff into its own component(s)?
No description provided.