Skip to content
This repository has been archived by the owner on Apr 19, 2023. It is now read-only.

Bastion #34

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open

Bastion #34

wants to merge 8 commits into from

Conversation

rcketscientist
Copy link

I want to change the lazy jumpHost to three values and just properly check them, but the basics should hold.

@rcketscientist
Copy link
Author

This should be ready for review. Could likely use another cleanup pass, but the logic is stable in my limited use case at least.

@joelspadin
Copy link
Owner

Thanks! Will take a look when I get some time.

Copy link
Owner

@joelspadin joelspadin left a 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) {
Copy link
Owner

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;
Copy link
Owner

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.)

Copy link
Author

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) => {
Copy link
Owner

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.

Copy link
Author

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);
Copy link
Owner

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()))
Copy link
Owner

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.

Copy link
Author

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,
Copy link
Owner

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
}

Copy link
Author

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];
Copy link
Owner

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"`);
}

Copy link
Author

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;
Copy link
Owner

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> {
Copy link
Owner

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> {
Copy link
Owner

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?

@rcketscientist
Copy link
Author

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

@joelspadin
Copy link
Owner

Other than dropping the try, I think that all makes sense.

Did I leave in a comment about getting rid of the try? If so, ignore that. I had originally written something like that but then realized you were intentionally falling back to regular settings if parsing SSH config failed. I intended to replace that with my comment about splitting the parsing out so the try block is short and easy to follow.

@rcketscientist
Copy link
Author

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.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants