Skip to content

Commit 2e1ae35

Browse files
committed
for issue #3
1 parent 292b61d commit 2e1ae35

File tree

4 files changed

+86
-72
lines changed

4 files changed

+86
-72
lines changed

README.md

Lines changed: 42 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ It is important to note, that despite the use-case described below for this proj
1313
* [Security](#security)
1414
* [Related Tools](#related)
1515

16-
###<a id="Origin"></a> Origin
16+
###<a id="Origin"></a> Origin
1717

18-
This project originated out of the need to execute various Powershell commands (at fairly high volume and frequency) against services within Office365/Azure bridged via a custom node.js implemented REST API; this was due to the lack of certain features in the REST GraphAPI for Azure/o365, that are available only in Powershell.
18+
This project originated out of the need to execute various Powershell commands (at fairly high volume and frequency) against services within Office365/Azure bridged via a custom node.js implemented REST API; this was due to the lack of certain features in the REST GraphAPI for Azure/o365, that are available only in Powershell.
1919

2020
If you have done any work with Powershell and o365, then you know that there is considerable overhead in both establishing a remote session and importing and downloading various needed cmdlets. This is an expensive operation and there is a lot of value in being able to keep this remote session open for longer periods of time rather than repeating this entire process for every single command that needs to be executed and then tearing everything down.
2121

2222
Simply doing an child_process.**exec** per command to launch an external process, run the command, and then killing the process is not really an option under such scenarios, as it is expensive and very singular in nature; no state can be maintained if need be. We also tried using [edge.js with powershell](https://github.com/tjanczuk/edge#how-to-script-powershell-in-a-nodejs-application) and this simply would not work with o365 exchange commands and heavy session cmdlet imports (the entire node.js process would crash). Using this module gives you full un-fettered access to the externally connected child_process, with no restrictions other than what uid/gid (permissions) the spawned process is running under (which you **really** have to consider from security standpoint!)
2323

24-
The diagram below should conceptually give you an idea of what this module does.
24+
The diagram below should conceptually give you an idea of what this module does.
2525

2626
**The local user that the node process runs as should have virtually zero rights! Also be sure to properly configure a restricted UID/GID when instatiating a new instance of this. See security notes below.**
2727

@@ -42,75 +42,76 @@ mocha test/all.js
4242

4343
```
4444
v1.0-beta.3 - 2014-01-26
45-
- New options for command blacklisting regex matching and interval
45+
- New options for command blacklisting regex matching and interval
4646
based self auto-invalidation of ProcessProxy instances
47-
47+
4848
v1.0-beta.2 - 2014-01-21
4949
- New return types for executeCommands - is now an array
50-
50+
5151
v1.0-beta.1 - 2014-01-17
5252
- Initial version
5353
```
5454

55-
###<a id="usage"></a> Usage
55+
###<a id="usage"></a> Usage
5656

5757
To use StatefulProcessCommandProxy the constructor takes one parameter which is a configuration object who's properties are described below. Please refer to the example (following) and the unit-test for more details.
5858

5959
```
6060
name: The name of this instance, arbitrary
61-
61+
6262
max: maximum number of processes to maintain
63-
63+
6464
min: minimum number of processes to maintain
65-
65+
6666
idleTimeoutMS: idle in milliseconds by which a process will be destroyed
67-
67+
6868
processCommand: full path to the actual process to be spawned (i.e. /bin/bash)
69-
69+
7070
processArgs: arguments to pass to the process command
71-
71+
7272
processRetainMaxCmdHistory: for each process spawned, the maximum number
7373
of command history objects to retain in memory
7474
(useful for debugging), default 0
75-
75+
7676
processInvalidateOnRegex: optional config of regex patterns who if match
7777
their respective type, will flag the process as invalid
7878
{
79-
'any' : ['regex1', ....],
80-
'stdout' : ['regex1', ....],
81-
'stderr' : ['regex1', ....]
79+
'any' : [ {regex:'regex1',flags:'ig'}, ....],
80+
'stdout' : [ {regex:'regex1',flags:'m'}, ....],
81+
'stderr' : [ {regex:'regex1',flags:'ig'}, ....]
8282
}
83-
83+
8484
processCmdBlacklistRegex: optional config array regex patterns who if match the
8585
command requested to be executed will be rejected
8686
with an error
87-
[ 'regex1', 'regex2'...]
88-
87+
[ {regex:'regex1',flags:'ig'},
88+
{regex:'regex2',flags:'ig'}...]
89+
8990
processCwd: optional current working directory for the processes to be spawned
90-
91+
9192
processEnvMap: optional hash/object of key-value pairs for environment variables
9293
to set for the spawned processes
93-
94+
9495
processUid: optional uid to launch the processes as
95-
96+
9697
processGid: optional gid to launch the processes as
97-
98+
9899
logFunction: optional function that should have the signature
99100
(severity,origin,message), where log messages will
100101
be sent to. If null, logs will just go to console
101-
102+
102103
initCommands: optional array of actual commands to execute on each newly
103104
spawned ProcessProxy in the pool before it is made available
104-
105+
105106
preDestroyCommands: optional array of actual commands to execute on a process
106107
before it is killed/destroyed on shutdown or being invalid
107-
108+
108109
validateFunction: optional function that should have the signature to accept
109110
a ProcessProxy object, and should return true/false if the
110111
process is valid or not, at a minimum this should call
111112
ProcessProxy.isValid(). If the function is not provided
112113
the default behavior is to only check ProcessProxy.isValid()
113-
114+
114115
autoInvalidationConfig optional configuration that will run the specified
115116
commands in the background on the given interval,
116117
and if the given regexes match/do-not-match for each command the
@@ -122,22 +123,22 @@ To use StatefulProcessCommandProxy the constructor takes one parameter which is
122123
commands:
123124
[
124125
{ command:'cmd1toRun',
125-
126+
126127
// OPTIONAL: because you can configure multiple commands
127128
// where the first ones doe some prep, then the last one's
128129
// output needs to be evaluated hence 'regexes' may not
129130
// always be present, (but your LAST command must have a
130131
// regexes config to eval prior work, otherwise whats the point
131-
132+
132133
regexes: {
133134
// at least one key must be specified
134135
// 'any' means either stdout or stderr
135136
// for each regex, the 'on' property dictates
136137
// if the process will be flagged invalid based
137138
// on the results of the regex evaluation
138-
'any' : [ {regex:'regex1', invalidOn:'match | noMatch'}, ....],
139-
'stdout' : [ {regex:'regex1', invalidOn:'match | noMatch'}, ....],
140-
'stderr' : [ {regex:'regex1', invalidOn:'match | noMatch'}, ....]
139+
'any' : [ {regex:'regex1', flags:'m', invalidOn:'match | noMatch'}, ....],
140+
'stdout' : [ {regex:'regex1', flags:'ig', invalidOn:'match | noMatch'}, ....],
141+
'stderr' : [ {regex:'regex1', flags:'i', invalidOn:'match | noMatch'}, ....]
141142
}
142143
},...
143144
]
@@ -146,9 +147,9 @@ To use StatefulProcessCommandProxy the constructor takes one parameter which is
146147

147148
Its highly recommended you check out the unit-tests for some examples in addition to the below:
148149

149-
###<a id="example"></a> Example
150+
###<a id="example"></a> Example
150151

151-
Note this example is for a machine w/ bash in the typical location. Windows (or other) can adjust the below as necessary to run).
152+
Note this example is for a machine w/ bash in the typical location. Windows (or other) can adjust the below as necessary to run).
152153

153154
```
154155
var Promise = require('promise');
@@ -205,7 +206,7 @@ statefulProcessCommandProxy.executeCommand('echo testInitVar')
205206
}).catch(function(error) {
206207
console.log("Error: " + error);
207208
});
208-
209+
209210
// test that our invalidation regex above traps and destroys this process instance
210211
statefulProcessCommandProxy.executeCommand('echo "this command has an error and will be '+
211212
' destroyed after check-in because it matches our invalidation regex"')
@@ -239,19 +240,19 @@ setTimeout(function() {
239240
},10000);
240241
241242
```
242-
243-
###<a id="security"></a> Security
243+
244+
###<a id="security"></a> Security
244245

245246
Obviously this module can expose you to some insecure situations depending on how you use it... you are providing a gateway to an external process via Node on your host os! (likely a shell in most use-cases). Here are some tips; ultimately its your responsibility to secure your system.
246247

247248
* Ensure that the node process is running as a user with very limited rights
248249
* Make use of the uid/gid configuration appropriately to further limit the processes
249-
* Never expose calls to this module directly, instead you should write a wrapper layer around StatefulProcessCommandProxy that protects, analyzes and sanitizes external input that can materialize in a `command` statement.
250+
* Never expose calls to this module directly, instead you should write a wrapper layer around StatefulProcessCommandProxy that protects, analyzes and sanitizes external input that can materialize in a `command` statement.
250251
* All commands you pass to `execute` should be sanitized to protect from injection attacks
251-
252+
252253
###<a id="related"></a> Related Tools
253254

254255
Have a look at these related projects which build on top of this module to provide some higher level functionality
255256

256-
* https://github.com/bitsofinfo/powershell-command-executor - Introduces a higher level "registry" of powershell commands which can be generated, have arguments applied to them (and sanitized), then executed.
257+
* https://github.com/bitsofinfo/powershell-command-executor - Introduces a higher level "registry" of powershell commands which can be generated, have arguments applied to them (and sanitized), then executed.
257258
* https://github.com/bitsofinfo/powershell-command-executor-ui - Builds on top of powershell-command-executor to provide a simple Node REST API and AngularJS interface for testing the execution of commands in the registry

processProxy.js

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ fifo.prototype.toArray = function () {
4343
* @param invalidateOnRegex optional regex pattern config object in the format:
4444
*
4545
* {
46-
* 'any' : ['regex1', ....],
47-
* 'stdout' : ['regex1', ....],
48-
* 'stderr' : ['regex1', ....]
46+
* 'any' : [ {regex:'regex1',flags:'ig'}, ....],
47+
* 'stdout' : [ {regex:'regex1',flags:'ig'}, ....],
48+
* 'stderr' : [ {regex:'regex1',flags:'m'}, ....]
4949
* }
5050
*
5151
* where on Command.finish() if the regex matches the
@@ -68,7 +68,8 @@ fifo.prototype.toArray = function () {
6868
* command requested to be executed will be rejected
6969
* with an error.
7070
*
71-
* [ 'regex1', 'regex2'...]
71+
* [ '{regex:'regex1',flags:'ig'},
72+
* {regex:'regex2',flags:'m'}...]
7273
*
7374
*
7475
* @param autoInvalidationConfig optional configuration that will run the specified
@@ -96,9 +97,9 @@ fifo.prototype.toArray = function () {
9697
* // for each regex, the 'on' property dictates
9798
* // if the process will be flagged invalid based
9899
* // on the results of the regex evaluation
99-
* 'any' : [ {regex:'regex1', invalidOn:'match | noMatch'}, ....],
100-
* 'stdout' : [ {regex:'regex1', invalidOn:'match | noMatch'}, ....],
101-
* 'stderr' : [ {regex:'regex1', invalidOn:'match | noMatch'}, ....]
100+
* 'any' : [ {regex:'regex1', flags:'i', invalidOn:'match | noMatch'}, ....],
101+
* 'stdout' : [ {regex:'regex1', flags:'i', invalidOn:'match | noMatch'}, ....],
102+
* 'stderr' : [ {regex:'regex1', flags:'i', invalidOn:'match | noMatch'}, ....]
102103
* }
103104
* },...
104105
* ]
@@ -221,7 +222,6 @@ ProcessProxy.prototype._buildAutoInvalidationConfig = function(autoInvalidationC
221222

222223
// this is optional...
223224
if (typeof(cmdConf.regexes)!=='undefined') {
224-
225225
this._parseRegexConfigs(cmdConf.regexes['any']);
226226
this._parseRegexConfigs(cmdConf.regexes['stdout']);
227227
this._parseRegexConfigs(cmdConf.regexes['stderr']);
@@ -273,16 +273,23 @@ ProcessProxy.prototype._parseRegexes = function(regexesToParse, regexpsToAppendT
273273
// parse all 'any' regexes to RegExp objects
274274
for (var i=0; i<regexesToParse.length; i++) {
275275

276-
var regexStr = regexesToParse[i];
276+
var regexConf = regexesToParse[i];
277277
try {
278-
var parsed = new RegExp(regexStr);
278+
var parsed = null;
279+
280+
if (typeof(regexConf.flags) != 'undefined') {
281+
parsed = new RegExp(regexConf.regex,regexConf.flags);
282+
} else {
283+
parsed = new RegExp(regexConf.regex);
284+
}
285+
279286
for (var j=0; j<regexpsToAppendTo.length; j++) {
280287
regexpsToAppendTo[j].push(parsed);
281288
}
282289

283290
} catch(exception) {
284291
this._log('error',"Error parsing invalidation regex: "
285-
+ regexStr + " err:"+exception + ' ' + exception.stack);
292+
+ JSON.stringify(regexConf) + " err:"+exception + ' ' + exception.stack);
286293
}
287294
}
288295
}
@@ -307,14 +314,19 @@ ProcessProxy.prototype._parseRegexConfigs = function(regexConfigsToConvert) {
307314
var regexConf = regexConfigsToConvert[j];
308315

309316
try {
310-
regexConf.regexStr = regexConf.regex; // retain as string
311-
var parsed = new RegExp(regexConf.regex);
312-
regexConf.regex = parsed; // replace as obj
317+
318+
if (typeof(regexConf.flags) != 'undefined') {
319+
console.log(regexConf.regex);
320+
parsed = new RegExp(regexConf.regex,regexConf.flags);
321+
} else {
322+
parsed = new RegExp(regexConf.regex);
323+
}
324+
325+
regexConf.regExpObj = parsed; // set as obj
313326

314327
} catch(exception) {
315-
delete regexConf.regex;
316328
this._log('error',"Error parsing regex: "
317-
+ regexStr + " err:"+exception + ' ' + exception.stack);
329+
+ JSON.stringify(regexConf) + " err:"+exception + ' ' + exception.stack);
318330
}
319331
}
320332
}
@@ -696,8 +708,8 @@ ProcessProxy.prototype._evalRegexConfigs = function(regexConfs, dataToEval) {
696708
for (var i=0; i<regexConfs.length; i++) {
697709
var regexConf = regexConfs[i];
698710

699-
if (regexConf.hasOwnProperty('regex')) {
700-
var matches = regexConf.regex.exec(dataToEval);
711+
if (regexConf.hasOwnProperty('regExpObj')) {
712+
var matches = regexConf.regExpObj.exec(dataToEval);
701713

702714
if (matches && regexConf.invalidOn == 'match' ||
703715
!matches && regexConf.invalidOn == 'noMatch') {

statefulProcessCommandProxy.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,17 @@ var Promise = require('promise');
3131
their respective type, will flag the process as invalid
3232
3333
{
34-
'any' : ['regex1', ....],
35-
'stdout' : ['regex1', ....],
36-
'stderr' : ['regex1', ....]
34+
'any' : [ {regex:'regex1',flags:'ig'}, ....],
35+
'stdout' : [ {regex:'regex1',flags:'ig'}, ....],
36+
'stderr' : [ {regex:'regex1',flags:'ig'}, ....]
3737
}
3838
3939
processCmdBlacklistRegex: optional config array regex patterns who if match the
4040
command requested to be executed will be rejected
4141
with an error
4242
43-
[ 'regex1', 'regex2'...]
43+
[ {regex:'regex1',flags:'ig'},
44+
{regex:'regex2',flags:'m'}...]
4445
4546
4647
processCwd: optional current working directory for the processes to be spawned
@@ -96,9 +97,9 @@ var Promise = require('promise');
9697
// if the process will be flagged invalid based
9798
// on the results of the regex evaluation
9899
99-
'any' : [ {regex:'regex1', invalidOn:'match | noMatch'}, ....],
100-
'stdout' : [ {regex:'regex1', invalidOn:'match | noMatch'}, ....],
101-
'stderr' : [ {regex:'regex1', invalidOn:'match | noMatch'}, ....]
100+
'any' : [ {regex:'regex1', flags:'im', invalidOn:'match | noMatch'}, ....],
101+
'stdout' : [ {regex:'regex1', flags:'g', invalidOn:'match | noMatch'}, ....],
102+
'stderr' : [ {regex:'regex1', flags:'i', invalidOn:'match | noMatch'}, ....]
102103
}
103104
},...
104105
]

test/all.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ var configs = {
2727
{ 'command': '$INVALIDATION_VAR="iShouldSetupInvalidation"'},
2828
{ 'command': 'echo $INVALIDATION_VAR',
2929
'regexes': {
30-
'any' : [ {'regex':'.*Invalid.*', 'invalidOn':'match'}]
30+
'any' : [ {'regex':'.*Invalid.*', 'flags':'i', 'invalidOn':'match'}]
3131
}
3232
}
3333
]
@@ -58,7 +58,7 @@ var configs = {
5858
{ 'command': 'INVALIDATION_VAR=iShouldSetupInvalidation'},
5959
{ 'command': 'echo $INVALIDATION_VAR',
6060
'regexes': {
61-
'any' : [ {'regex':'.*Invalid.*', 'invalidOn':'match'}]
61+
'any' : [ {'regex':'.*Invalid.*', 'flags':'i', 'invalidOn':'match'}]
6262
}
6363
}
6464
]
@@ -115,12 +115,12 @@ var getStatefulProcessCommandProxyForTests = function(config,max,min,setAutoVali
115115

116116
processRetainMaxCmdHistory : 10,
117117
processInvalidateOnRegex : {
118-
'any':['.*nomatch.*'],
119-
'stdout':['.*nomatch.*'],
120-
'stderr':['.*nomatch.*']
118+
'any':[{'regex':'.*nomatch.*'}],
119+
'stdout':[{'regex':'.*nomatch.*'}],
120+
'stderr':[{'regex':'.*nomatch.*', 'flags':'i'}]
121121
},
122122

123-
processCmdBlacklistRegex: ['.*blacklisted.*'],
123+
processCmdBlacklistRegex: [ {'regex':'.*blacklisted.*'} ],
124124

125125
processCwd : null,
126126
processEnvMap : {"testenvvar":"value1"},

0 commit comments

Comments
 (0)