chore: 🤖 sanitize and validate input to CLI

This commit is contained in:
Randall Morey committed 2021-02-09 18:52:25 -05:00
1 parent 64dc359f34
commit e5d991a17b
7 files changed
+48 -73

No files matched your search

@@ -101,21 +101,25 @@ export default class ScopesScopeProjectsTargetsRoute extends Route {
/**
* Establish a session to current target.
* @param {TargetModel} model
* @param {HostModel} host
*/
@action
async connect(model) {
async connect(model, host) {
// TODO: Connect: Refactor into an addon
try {
// Check for CLI
const cliExists = await this.ipc.invoke('cliExists');
if (!cliExists) throw new Error('Cannot find Boundary CLI.');
const options = {
target_id: model.id,
token: this.session.data.authenticated.token
};
if (host) options.host_id = host.id;
// Create target session
const connectionDetails = await this.ipc.invoke('connect', {
target_id: model.target.id,
token: this.session.data.authenticated.token,
addr: this.origin.rendererOrigin
});
const connectionDetails = await this.ipc.invoke('connect', options);
// Associate the connection details with the session
const { session_id, address, port } = connectionDetails;
@@ -1,7 +1,5 @@
import Route from '@ember/routing/route';
import { all, hash } from 'rsvp';
import { later } from '@ember/runloop';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
export default class ScopesScopeProjectsTargetsTargetHostsRoute extends Route {
@@ -23,7 +21,7 @@ export default class ScopesScopeProjectsTargetsTargetHostsRoute extends Route {
const { target } = this.modelFor('scopes.scope.projects.targets.target');
// Load all host-sets
const hostSets = await all(
target.host_sets.map(({ host_set_id }) =>
target.host_sets.map(({ host_set_id }) =>
this.store.findRecord('host-set', host_set_id)
)
);
@@ -32,7 +30,7 @@ export default class ScopesScopeProjectsTargetsTargetHostsRoute extends Route {
const hostIds = hostSets.map(({host_ids}) =>
host_ids.content.map((hostId) =>
hostId.value
)
)
)
// Load unique hosts
@@ -41,65 +39,10 @@ export default class ScopesScopeProjectsTargetsTargetHostsRoute extends Route {
return hash({
target,
hosts: all(
[...uniqueHostIds].map((hostId) =>
[...uniqueHostIds].map((hostId) =>
this.store.findRecord('host', hostId)
)
)
})
}
/**
* Establish a session to host within target.
* @param {TargetModel} target
* @param {HostModel} host
*/
@action
async connect(target, host) {
// TODO: Connect: Refactor into an addon
try {
// Check for CLI
const cliExists = await this.ipc.invoke('cliExists');
if (!cliExists) throw new Error('Cannot find Boundary CLI.');
// Create target session
const connectionDetails = await this.ipc.invoke('connect', {
host_id: host.id,
target_id: target.id,
token: this.session.data.authenticated.token,
addr: this.origin.rendererOrigin
});
// Associate the connection details with the session
const { session_id, address, port } = connectionDetails;
await this.store.findRecord('session', session_id);
// Update the session record with proxy information from the CLI
// This is read-only information that shouldn't dirty the session,
// so we use store.push here. In the future, it may make sense to push
// this off to the API so that we don't have to manually persist the
// proxy details.
later(() => {
this.store.push({
data: {
id: session_id,
type: 'session',
attributes: {
proxy_address: address,
proxy_port: port
}
}
});
}, 150);
// Show the user a modal with basic connection info.
// We don't await because this modal is purely informational.
this.confirm.confirm(connectionDetails, { isConnectSuccess: true });
} catch(e) {
this.confirm.confirm(e.message, { isConnectError: true })
// Retry
.then(() => this.connect(target, host))
.catch(() => null /* no op */);
}
}
}
@@ -66,7 +66,7 @@
</Rose::Badge>
</row.cell>
<row.cell>
<Rose::Button @style="secondary" {{on "click" (route-action "connect" model)}}>
<Rose::Button @style="secondary" {{on "click" (route-action "connect" model.target)}}>
{{t "resources.session.actions.connect"}}
</Rose::Button>
</row.cell>
+2 -1
View File
@@ -25,7 +25,8 @@
"electron-devtools-installer": "^3.1.1",
"electron-is-dev": "^1.2.0",
"electron-squirrel-startup": "^1.0.0",
"lookpath": "^1.1.0"
"lookpath": "^1.1.0",
"shell-quote": "^1.7.2"
},
"devDependencies": {
"@electron-forge/cli": "6.0.0-beta.53",
+24 -4
View File
@@ -1,23 +1,43 @@
const path = require('path');
const { spawnAsyncJSONPromise, spawnSync } = require('./spawn-promise');
const origin = require('./origin.js');
const cliPath = async () => path.resolve(__dirname, '..', 'cli', 'boundary');
/**
* Super paranoid shell quote/escape and validation. Input must be base62.
* @param {string} str
*/
const escapeAndValidateBase62 = (str) => {
const candidate = str.toString();
if (candidate.match(/^[A-Za-z0-9_]*$/)) return candidate;
throw new Error(`
Could not invoke command:
input contained unsafe characters.
`);
};
module.exports = {
// Check boundary cli existence
exists: () => Boolean(cliPath()),
// Initiate connection and return output
connect: (target_id, token, addr, host_id) => {
connect: (target_id, token, host_id) => {
const addr = origin.origin;
const sanitized = {
target_id: escapeAndValidateBase62(target_id),
token: escapeAndValidateBase62(token)
};
if (host_id) sanitized.host_id = escapeAndValidateBase62(host_id);
const command = [
'connect',
`-target-id=${target_id}`,
`-token=${token}`,
`-target-id=${sanitized.target_id}`,
`-token=${sanitized.token}`,
`-addr=${addr}`,
'-format=json',
'--output-json-errors'
];
if(host_id) command.push(`-host-id=${host_id}`)
if (host_id) command.push(`-host-id=${sanitized.host_id}`)
return spawnAsyncJSONPromise(command);
},
// Returns JSON-formatted version information from the CLI
+3 -1
View File
@@ -25,4 +25,6 @@ handle('cliExists', () => boundaryCli.exists());
/**
* Establishes a boundary session and returns session details.
*/
handle('connect', ({ target_id, token, addr, host_id }) => boundaryCli.connect(target_id, token, addr, host_id));
handle('connect', ({ target_id, token, host_id }) =>
boundaryCli.connect(target_id, token, host_id)
);
+5
View File
@@ -3108,6 +3108,11 @@ shebang-regex@^3.0.0:
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
shell-quote@^1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2"
integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==
signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.3"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"