-
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?
Changes from 21 commits
a1bab38
28b570b
143a9eb
1ed9bd5
e6cec75
960bad5
5af1982
a2d3ffd
f2c0c60
8016d83
bae44da
8dd60cb
e1a6008
692531b
c970f67
78778ca
5316a81
14ce0b6
7ac5912
fa6c123
67b85b2
5c4329c
473c982
5f81021
3974e2d
2472f39
e174392
0908706
dfa1d88
7ee282c
28aac41
6713757
0bc60bf
850ea55
2c2b093
b937878
48614fb
ad24af3
a56700f
be7759a
c4f36b7
79b6f7c
021e901
bc8eedc
977158b
0c1b077
a28ce83
e828482
d559cab
a08379a
a1e62d3
3392dd0
097d2ff
cd14cec
f885211
7fb692e
ea19387
9bb8059
a685438
730b5d1
363e3e1
ec728a0
186c984
fb025a0
9e0ceec
2105335
dd291b0
96d1dd3
bd82eaa
f938c42
23b69c8
25277e3
54293b7
5b992bd
019c8a6
a177b01
3872b14
0a25231
0448190
28b337c
53c9353
f0478ac
1bda10c
bfa0e72
2d0a092
b9b684a
887a669
f3b9e4e
cd90d1b
8606a21
5445793
2949e9e
95d2a8d
0c76acd
5a58db5
1e272b4
2f654fb
bd985e2
a3b612d
9b8bfed
c880780
d33b3ab
6d26ccf
2afedcc
39fe246
3c43652
698e39f
fd151da
7b504f5
6706274
8f95c5c
ac353de
9fa39cf
3fcbc58
da95d85
5f77ec5
0d378ec
5e9adf1
ca6114c
24d1a59
c6a578e
9e287a1
308d747
492ce79
dbd797b
3c68dd9
90df884
adf28a1
6a2cefe
b2126db
bab1881
6f2d840
cdd9335
ccef965
1c4ba16
2dfb917
93c5bb6
c37cbab
7486aaa
833543e
4b7d295
2ee4f68
c3bd14e
5e2d0a9
a4f12d4
c9a8bb3
a94864a
17dc7f2
1c6c541
35adcb6
93286e8
b744a20
9f148a4
7a1ca00
d65c9b8
cd42eeb
c6c703e
864559e
f52760d
cc5e5f3
264223d
072563b
142223e
e75310d
6459080
a13f35c
758b2db
ec1a77d
f957ef8
868652d
4fa50d3
00b6a06
ec0a993
244686a
7bbce24
a635ae4
68bf3db
6ed9a5e
6f9a16d
06bbe90
fca94af
c0ba816
2cdd607
f097a06
7d61115
212ab9d
0cf32c2
aecfa3d
d48e3e0
8eb40f1
e2bfd5f
50f88d7
8089983
0332a30
232980a
42e5131
01c544f
4e2eea8
6529fc3
e788ab7
579345b
1c57272
6ecdfc0
4b47144
5e81660
3328267
1d41221
084a77a
95110da
a7936c6
fc085d4
1c45104
6900045
330a12a
c0b4a8d
d4a2c38
848746e
fda6e3c
8b606df
064a955
b9bf5c2
0e42bd4
1b3e695
e389ced
f2a6118
103b4f1
488b22b
5783c6c
5696ebf
4d337c4
ba4f08e
0821c36
25d1239
ab44342
70d42d5
b8f0d0b
a827aa3
2019faf
53c9fc3
bec60df
1dbb249
e4715f2
4469ed7
6981427
53a3f3a
6056c34
fac846d
2452a1e
ccf8b63
d9fffe3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,8 @@ | |
| import * as fs from 'fs'; | ||
| import * as path from 'path'; | ||
| import axios from 'axios'; | ||
| import { utils } from 'ssh2'; | ||
| import * as crypto from 'crypto'; | ||
|
|
||
| const API_BASE_URL = process.env.GIT_PROXY_API_URL || 'http://localhost:3000'; | ||
| const GIT_PROXY_COOKIE_FILE = path.join( | ||
|
|
@@ -23,6 +25,23 @@ interface ErrorWithResponse { | |
| message: string; | ||
| } | ||
|
|
||
| // 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 commentThe 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 |
||
| function calculateFingerprint(publicKeyStr: string): string | null { | ||
| try { | ||
| const parsed = utils.parseKey(publicKeyStr); | ||
| if (!parsed || parsed instanceof Error) { | ||
| return null; | ||
| } | ||
| const pubKey = parsed.getPublicSSH(); | ||
| const hash = crypto.createHash('sha256').update(pubKey).digest('base64'); | ||
| return `SHA256:${hash}`; | ||
| } catch (err) { | ||
| console.error('Error calculating fingerprint:', err); | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| async function addSSHKey(username: string, keyPath: string): Promise<void> { | ||
| try { | ||
| // Check for authentication | ||
|
|
@@ -83,15 +102,28 @@ async function removeSSHKey(username: string, keyPath: string): Promise<void> { | |
| // Read the public key file | ||
| const publicKey = fs.readFileSync(keyPath, 'utf8').trim(); | ||
|
|
||
| // Make the API request | ||
| await axios.delete(`${API_BASE_URL}/api/v1/user/${username}/ssh-keys`, { | ||
| data: { publicKey }, | ||
| withCredentials: true, | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| Cookie: cookies, | ||
| // Strip the comment from the key (everything after the last space) | ||
| const keyWithoutComment = publicKey.split(' ').slice(0, 2).join(' '); | ||
|
|
||
| // Calculate fingerprint | ||
| const fingerprint = calculateFingerprint(keyWithoutComment); | ||
| if (!fingerprint) { | ||
| console.error('Invalid SSH key format. Unable to calculate fingerprint.'); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| console.log(`Removing SSH key with fingerprint: ${fingerprint}`); | ||
|
|
||
| // Make the API request using fingerprint in path | ||
| await axios.delete( | ||
| `${API_BASE_URL}/api/v1/user/${username}/ssh-keys/${encodeURIComponent(fingerprint)}`, | ||
| { | ||
| withCredentials: true, | ||
| headers: { | ||
| Cookie: cookies, | ||
| }, | ||
| }, | ||
| }); | ||
| ); | ||
|
|
||
| console.log('SSH key removed successfully!'); | ||
| } catch (error) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,4 +31,5 @@ export const { | |
| updateUser, | ||
| addPublicKey, | ||
| removePublicKey, | ||
| getPublicKeys, | ||
| } = users; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| import { AuthorisedRepo } from '../config/generated/config'; | ||
| import { PushQuery, Repo, RepoQuery, Sink, User, UserQuery } from './types'; | ||
| import { PushQuery, Repo, RepoQuery, Sink, User, UserQuery, PublicKeyRecord } from './types'; | ||
| import * as bcrypt from 'bcryptjs'; | ||
| import * as config from '../config'; | ||
| import * as mongo from './mongo'; | ||
|
|
@@ -171,9 +171,11 @@ export const findUserBySSHKey = (sshKey: string): Promise<User | null> => | |
| 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 commentThe reason will be displayed to describe this comment to others. Learn more. Why is the We could alternatively fix the usages, but it makes the most sense for a DB |
||
| export const addPublicKey = (username: string, publicKey: string): Promise<void> => | ||
| export const updateUser = (user: User): Promise<void> => sink.updateUser(user); | ||
| export const addPublicKey = (username: string, publicKey: PublicKeyRecord): Promise<void> => | ||
| sink.addPublicKey(username, publicKey); | ||
| export const removePublicKey = (username: string, publicKey: string): Promise<void> => | ||
| sink.removePublicKey(username, publicKey); | ||
| export type { PushQuery, Repo, Sink, User } from './types'; | ||
| export const removePublicKey = (username: string, fingerprint: string): Promise<void> => | ||
| sink.removePublicKey(username, fingerprint); | ||
| export const getPublicKeys = (username: string): Promise<PublicKeyRecord[]> => | ||
| sink.getPublicKeys(username); | ||
| export type { PushQuery, Repo, Sink, User, PublicKeyRecord } from './types'; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,4 +31,5 @@ export const { | |
| updateUser, | ||
| addPublicKey, | ||
| removePublicKey, | ||
| getPublicKeys, | ||
| } = users; | ||
|
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. Might want to rename this to 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. Alternatively, we could rename this to This might make the |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| import { PACKET_SIZE } from './constants'; | ||
|
|
||
| /** | ||
| * Parses the packet lines from a buffer into an array of strings. | ||
| * Also returns the offset immediately following the parsed lines (including the flush packet). | ||
| * @param {Buffer} buffer - The buffer containing the packet data. | ||
| * @return {[string[], number]} An array containing the parsed lines and the offset after the last parsed line/flush packet. | ||
| */ | ||
| export const parsePacketLines = (buffer: Buffer): [string[], number] => { | ||
| const lines: string[] = []; | ||
| let offset = 0; | ||
|
|
||
| while (offset + PACKET_SIZE <= buffer.length) { | ||
| const lengthHex = buffer.toString('utf8', offset, offset + PACKET_SIZE); | ||
| const length = Number(`0x${lengthHex}`); | ||
|
|
||
| // Prevent non-hex characters from causing issues | ||
| if (isNaN(length) || length < 0) { | ||
| throw new Error(`Invalid packet line length ${lengthHex} at offset ${offset}`); | ||
| } | ||
|
|
||
| // length of 0 indicates flush packet (0000) | ||
| if (length === 0) { | ||
| offset += PACKET_SIZE; // Include length of the flush packet | ||
| break; | ||
| } | ||
|
|
||
| // Make sure we don't read past the end of the buffer | ||
| if (offset + length > buffer.length) { | ||
| throw new Error(`Invalid packet line length ${lengthHex} at offset ${offset}`); | ||
| } | ||
|
|
||
| const line = buffer.toString('utf8', offset + PACKET_SIZE, offset + length); | ||
| lines.push(line); | ||
| offset += length; // Move offset to the start of the next line's length prefix | ||
| } | ||
| return [lines, offset]; | ||
| }; |
Uh oh!
There was an error while loading. Please reload this page.