125 lines
3.5 KiB
TypeScript
125 lines
3.5 KiB
TypeScript
import * as plugins from './smartssh.plugins.js';
|
|
import * as helpers from './smartssh.classes.helpers.js';
|
|
import { SshKey } from './smartssh.classes.sshkey.js';
|
|
|
|
export type TSshStrictHostKeyChecking = boolean | 'accept-new';
|
|
|
|
export interface ISshConfigOptions {
|
|
strictHostKeyChecking?: TSshStrictHostKeyChecking;
|
|
}
|
|
|
|
export interface ISshConfigHostBlock {
|
|
host: string;
|
|
values: Record<string, string>;
|
|
}
|
|
|
|
export class SshConfig {
|
|
private _sshKeyArray: SshKey[];
|
|
private _options: ISshConfigOptions;
|
|
constructor(sshKeyArrayArg: SshKey[], optionsArg: ISshConfigOptions = {}) {
|
|
this._sshKeyArray = sshKeyArrayArg;
|
|
this._options = {
|
|
...optionsArg,
|
|
strictHostKeyChecking: optionsArg.strictHostKeyChecking ?? true,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* stores a config file
|
|
*/
|
|
store(dirPathArg: string) {
|
|
const resolvedDir = helpers.resolveSshDirPath(dirPathArg);
|
|
helpers.ensureSshDirSync(resolvedDir);
|
|
const configArray: configObject[] = [];
|
|
for (const sshKey of this._sshKeyArray) {
|
|
let configString = '';
|
|
if (sshKey.host) {
|
|
helpers.assertSafeHost(sshKey.host);
|
|
const identityFilePath = plugins.path.join(resolvedDir, sshKey.host);
|
|
configString =
|
|
'Host ' +
|
|
sshKey.host +
|
|
'\n' +
|
|
' HostName ' +
|
|
sshKey.host +
|
|
'\n' +
|
|
' IdentityFile ' +
|
|
helpers.quoteSshConfigValue(identityFilePath) +
|
|
'\n';
|
|
if (this._options.strictHostKeyChecking !== undefined) {
|
|
const strictHostKeyChecking = this._options.strictHostKeyChecking;
|
|
configString +=
|
|
' StrictHostKeyChecking ' +
|
|
(strictHostKeyChecking === 'accept-new'
|
|
? 'accept-new'
|
|
: strictHostKeyChecking
|
|
? 'yes'
|
|
: 'no') +
|
|
'\n';
|
|
}
|
|
}
|
|
configArray.push({
|
|
configString: configString,
|
|
authorized: sshKey.authorized,
|
|
sshKey: sshKey,
|
|
});
|
|
}
|
|
let configFile: string = '';
|
|
for (const config of configArray) {
|
|
configFile = configFile + config.configString + '\n';
|
|
}
|
|
plugins.fs.writeFileSync(plugins.path.join(resolvedDir, 'config'), configFile);
|
|
plugins.fs.chmodSync(plugins.path.join(resolvedDir, 'config'), 0o600);
|
|
}
|
|
read(dirPathArg: string) {
|
|
const configPath = plugins.path.join(helpers.resolveSshDirPath(dirPathArg), 'config');
|
|
return plugins.fs.readFileSync(configPath, 'utf8');
|
|
}
|
|
|
|
parse(dirPathArg: string) {
|
|
return SshConfig.parse(this.read(dirPathArg));
|
|
}
|
|
|
|
static parse(configStringArg: string): ISshConfigHostBlock[] {
|
|
const blocks: ISshConfigHostBlock[] = [];
|
|
let currentBlock: ISshConfigHostBlock | undefined;
|
|
|
|
for (const rawLine of configStringArg.split(/\r?\n/)) {
|
|
const line = rawLine.trim();
|
|
if (!line || line.startsWith('#')) {
|
|
continue;
|
|
}
|
|
|
|
const [keyword, ...valueParts] = line.split(/\s+/);
|
|
const value = valueParts.join(' ');
|
|
if (!keyword || !value) {
|
|
continue;
|
|
}
|
|
|
|
if (keyword.toLowerCase() === 'host') {
|
|
if (!helpers.isSafeHost(value)) {
|
|
continue;
|
|
}
|
|
currentBlock = {
|
|
host: value,
|
|
values: {},
|
|
};
|
|
blocks.push(currentBlock);
|
|
continue;
|
|
}
|
|
|
|
if (currentBlock) {
|
|
currentBlock.values[keyword.toLowerCase()] = value;
|
|
}
|
|
}
|
|
|
|
return blocks;
|
|
}
|
|
}
|
|
|
|
export interface configObject {
|
|
configString: string;
|
|
authorized: boolean;
|
|
sshKey: SshKey;
|
|
}
|