-
Notifications
You must be signed in to change notification settings - Fork 9
Bastion #34
base: master
Are you sure you want to change the base?
Bastion #34
Conversation
This should be ready for review. Could likely use another cleanup pass, but the logic is stable in my limited use case at least. |
Thanks! Will take a look when I get some time. |
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.
Thanks for this! It looks pretty good, but I have a few recommendations to help keep the code easy to understand.
|
||
conn.on('x11', handleX11); | ||
if (preferConfig) { |
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.
Would it be possible to pull this SSH config handling out into a separate function that returns destinationOptions and jumpOptions? createForwardedDisplay is getting rather long, and this seems like it's mostly about parsing the file rather than creating the connection.
It's also currently difficult to understand the error handling behavior here since the try
block is so long. Something like this would be much easier to read:
try {
const parsedConfig = parseSshConfig(/* whatever args it needs */);
destinationOptions = parsedConfig.destinationOptions;
jumpOptions = parsedConfig.jumpOptions;
} catch (err) {
logger.log(`Failed to process SSH config. ...`);
}
@@ -27,15 +31,18 @@ interface ConnectOptions { | |||
const BASE_PORT = 6000; | |||
|
|||
const logger = new Logger('Remote X11 (SSH)'); | |||
let conn: Client; | |||
let destination: Client; | |||
let jump: Client; |
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.
These should probably be typed as Client | undefined
since they're not immediately initialized. (This was an error in my original code too.)
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.
Typescript should handle that already (maybe needs strict), but fine by me
// display. | ||
createForwardingShell(jump, false); | ||
|
||
jump.forwardOut('127.0.0.1', 12345, destinationOptions.host, destinationOptions.port, (err, stream) => { |
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 any specific meaning to the port 12345 (other than the fact that it was listed in ssh2's documentation)? If this is a specific port reserved for forwarding connections, I'd like to have it defined as a constant so it's not a magic number.
If it doesn't matter what port is used here, then it would be better to pick an arbitrary, unused port instead of using a fixed number that might already be in use. Based on https://stackoverflow.com/questions/28050171/nodejs-random-free-tcp-ports, you might be able to use port 0 for that. If that doesn't work with ssh2, there are libraries like https://www.npmjs.com/package/get-port which might work, or you could just make a setting so you can change it if it conflicts with something.
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.
Nope, copied as you mentioned...forgot to TODO that.
} | ||
|
||
export function getSshConfig(): string { | ||
return getConfig('SSH.sshConfig', DefaultSshConfig); |
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.
If you just wrap the result of this with resolveHome
(as is done in getPrivateKey
), then resolveHome
doesn't need to be exported, and you can simplify calls to getSshConfig
.
logger.log(`Attempting to parse ${sshConfig} for ssh configuration of ${host}`); | ||
|
||
try { | ||
const data = fs.readFileSync(resolveHome(getSshConfig())) |
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 think this can just be simplified to
const data = fs.readFileSync(sshConfig);
Also, it probably doesn't matter because there isn't much else the code could be doing while waiting on IO here, and you can't easily use await
inside a promise callback like this, but consider using fs.promises.readFile
.
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.
Pretty sure fs can throw for a number of reasons and the goal was zero side effects, so I basically wanted the logic to branch off "try to load config" and if that failed operate reliably as before.
const configProxy = hostConfig.find((line:any) => line.param === 'ProxyCommand'); | ||
|
||
destinationOptions = { | ||
username: configUser ? configUser.value : options.username, |
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.
Can simplify these using optional chaining and null coalescing.
{
username: configUser?.value ?? options.username
}
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.
Mostly stuck in older versions of TS, haven't had a chance to leverage the chaining yet.
|
||
const hostAndPort = args[forwardArg + 1].split(':'); | ||
// %h is the config host, %p is the config port | ||
destinationOptions.host = hostAndPort[0] === '%h' ? destinationOptions.host : hostAndPort[0]; |
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.
Would be nice to include a check like
const hostAndPortStr = args[forwardArg + 1];
const hostAndPort = hostAndPortStr.split(':');
if (hostAndPort.length < 2) {
throw new Error(`Invalid destination "${hostAndPortStr}". Expected a string of the form "host:port"`);
}
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.
Yah, good point. Since that is a strict argument I skipped the check, but we're the ones parsing so we'll need to be explicit.
const jumpHostBlock = config.find((line:any) => line.param === 'Host' && line.value === jumpHostArg); | ||
if (jumpHostBlock) { | ||
const jumpHostNameNode = jumpHostBlock.config.find((line:any) => line.param === 'HostName'); | ||
const jumpHost: string = jumpHostNameNode ? jumpHostNameNode.value : options.host; |
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.
Can also simplify these with optional chaining and null coalescing.
} | ||
} | ||
|
||
function createForwardingShell(conn: Client, shouldGetDisplay: boolean): Promise<string> { |
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.
In places that call this, it's not really obvious what the second parameter does:
createForwardingShell(conn, true);
Something like this would make it much more clear:
function createForwardingShell(conn: client, { getDisplay }: { getDisplay: boolean }) {
//...
}
createForwardingShell(conn, { getDisplay: true });
}); | ||
}); | ||
} | ||
|
||
function createForwardingShell(conn: Client): Promise<string> { | ||
function createForwardingShellNoDisplay(conn: Client): Promise<string> { |
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 appears to be unused now?
Other than dropping the try, I think that all makes sense. It may be some time before I can gather all those. Feel free to tweak as desired if you'd like to merge in the meantime |
Did I leave in a comment about getting rid of the |
Ironically, we just switched to 2fa, rendering all this useless for us. I can't really justify finishing the PR now, sorry. I'll answer any questions you may have. |
I want to change the lazy
jumpHost
to three values and just properly check them, but the basics should hold.