Skip to content

Commit 91e8b44

Browse files
committed
Pre-resolve secrets within runtime using secret resolvers
1 parent 3c78a97 commit 91e8b44

File tree

5 files changed

+611
-24
lines changed

5 files changed

+611
-24
lines changed

lib/runner/extensions/item.command.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,8 @@ module.exports = {
198198
_variables: ctxTemplate._variables,
199199
data: ctxTemplate.data,
200200
coords: coords,
201-
source: 'collection'
201+
source: 'collection',
202+
secretResolver: this.state.secretResolver
202203
}).done(function (result, requestError) {
203204
!result && (result = {});
204205

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
var _ = require('lodash'),
22
createItemContext = require('../create-item-context'),
3-
{ resolveVariables } = require('../util');
3+
{ resolveVariables } = require('../util'),
4+
{ resolveSecrets } = require('../resolve-secrets');
45

56

67
module.exports = {
@@ -12,38 +13,57 @@ module.exports = {
1213

1314
process: {
1415
request (payload, next) {
15-
var abortOnError = _.has(payload, 'abortOnError') ? payload.abortOnError : this.options.abortOnError,
16+
var self = this,
17+
abortOnError = _.has(payload, 'abortOnError') ? payload.abortOnError : this.options.abortOnError,
18+
secretResolver = payload.secretResolver,
1619

17-
// helper function to trigger `response` callback anc complete the command
20+
// helper function to trigger `response` callback and complete the command
1821
complete = function (err, nextPayload) {
1922
// nextPayload will be empty for unhandled errors
2023
// trigger `response` callback
2124
// nextPayload.response will be empty for error flows
2225
// the `item` argument is resolved and mutated here
23-
nextPayload && this.triggers.response(err, nextPayload.coords, nextPayload.response,
26+
nextPayload && self.triggers.response(err, nextPayload.coords, nextPayload.response,
2427
nextPayload.request, nextPayload.item, nextPayload.cookies, nextPayload.history);
2528

2629
// the error is passed twice to allow control between aborting the error vs just
2730
// bubbling it up
2831
return next(err && abortOnError ? err : null, nextPayload, err);
29-
}.bind(this),
30-
context = createItemContext(payload);
31-
32-
// resolve variables in item and auth
33-
resolveVariables(context, payload);
34-
35-
// add context for use, after resolution
36-
payload.context = context;
37-
38-
// we do not queue `httprequest` instruction here,
39-
// queueing will unblock the item command to prepare for the next `event` instruction
40-
// at this moment request is not fulfilled, and we want to block it
41-
this.immediate('httprequest', payload)
42-
.done(function (nextPayload, err) {
43-
// change signature to error first
44-
complete(err, nextPayload);
45-
})
46-
.catch(complete);
32+
},
33+
34+
continueWithRequest = function () {
35+
var context = createItemContext(payload);
36+
37+
// resolve variables in item and auth
38+
resolveVariables(context, payload);
39+
40+
// add context for use, after resolution
41+
payload.context = context;
42+
43+
// we do not queue `httprequest` instruction here,
44+
// queueing will unblock the item command to prepare for the next `event` instruction
45+
// at this moment request is not fulfilled, and we want to block it
46+
self.immediate('httprequest', payload)
47+
.done(function (nextPayload, err) {
48+
// change signature to error first
49+
complete(err, nextPayload);
50+
})
51+
.catch(complete);
52+
};
53+
54+
// resolve secrets first before variable substitution
55+
if (secretResolver) {
56+
resolveSecrets(payload, secretResolver, function (err) {
57+
if (err) {
58+
console.warn('Secret resolution failed with an error:', err.message);
59+
}
60+
61+
continueWithRequest();
62+
});
63+
}
64+
else {
65+
continueWithRequest();
66+
}
4767
}
4868
}
4969
};

lib/runner/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ _.assign(Runner.prototype, {
9797
* @param {String} [options.entrypoint.lookupStrategy=idOrName] strategy to lookup the entrypoint [idOrName, path]
9898
* @param {Array<String>} [options.entrypoint.path] path to lookup
9999
* @param {Object} [options.run] Run-specific options, such as options related to the host
100+
* @param {Function} [options.secretResolver] - Async function to resolve variables with `ref` property.
100101
*
101102
* @param {Function} callback -
102103
*/
@@ -147,7 +148,8 @@ _.assign(Runner.prototype, {
147148
collectionVariables: collection.variables,
148149
localVariables: options.localVariables,
149150
certificates: options.certificates,
150-
proxies: options.proxies
151+
proxies: options.proxies,
152+
secretResolver: options.secretResolver
151153
}, runOptions)));
152154
});
153155
}

lib/runner/resolve-secrets.js

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
var _ = require('lodash'),
2+
async = require('async');
3+
4+
/**
5+
* Extract all variables that have a `ref` property from a variable scope.
6+
*
7+
* @param {VariableScope|Object} scope - scope
8+
* @returns {Array} - variables
9+
*/
10+
function extractRefVariables (scope) {
11+
if (!scope) {
12+
return [];
13+
}
14+
15+
var values = _.get(scope, 'values'),
16+
refVariables = [];
17+
18+
if (!values) {
19+
return [];
20+
}
21+
22+
if (values.members && Array.isArray(values.members)) {
23+
values.members.forEach(function (variable) {
24+
if (variable && variable.ref && variable.type === 'secret') {
25+
refVariables.push(variable);
26+
}
27+
});
28+
}
29+
else if (Array.isArray(values)) {
30+
values.forEach(function (variable) {
31+
if (variable && variable.ref && variable.type === 'secret') {
32+
refVariables.push(variable);
33+
}
34+
});
35+
}
36+
37+
return refVariables;
38+
}
39+
40+
/**
41+
* Scans variable scopes for `ref` property and resolves them using resolver.
42+
*
43+
* @param {Object} payload - Request payload
44+
* @param {Function} secretResolver - Async resolver func
45+
* @param {Function} callback - callback
46+
*/
47+
function resolveSecrets (payload, secretResolver, callback) {
48+
if (!secretResolver || typeof secretResolver !== 'function') {
49+
return callback();
50+
}
51+
52+
var scopesToScan = [
53+
{ name: 'environment', scope: payload.environment },
54+
{ name: 'globals', scope: payload.globals },
55+
{ name: 'collectionVariables', scope: payload.collectionVariables },
56+
{ name: '_variables', scope: payload._variables }
57+
],
58+
variablesToResolve = [];
59+
60+
scopesToScan.forEach(function (scopeInfo) {
61+
var refVars = extractRefVariables(scopeInfo.scope);
62+
63+
refVars.forEach(function (variable) {
64+
variablesToResolve.push({
65+
scopeName: scopeInfo.name,
66+
scope: scopeInfo.scope,
67+
variable: variable
68+
});
69+
});
70+
});
71+
72+
if (variablesToResolve.length === 0) {
73+
return callback();
74+
}
75+
76+
async.each(variablesToResolve, function (item, next) {
77+
var variable = item.variable,
78+
ref = variable.ref,
79+
resolved = false,
80+
result;
81+
82+
/**
83+
* Handle the resolved value and call next accordingly
84+
*
85+
* @param {Error} err - error
86+
* @param {*} resolvedValue - resolved secret value
87+
*/
88+
function handleResolution (err, resolvedValue) {
89+
if (resolved) {
90+
return;
91+
}
92+
resolved = true;
93+
94+
if (err) {
95+
console.warn('Secret resolution failed for variable:', variable.key, err.message);
96+
97+
return next();
98+
}
99+
100+
if (!_.isNil(resolvedValue)) {
101+
if (typeof variable.set === 'function') {
102+
variable.set(resolvedValue);
103+
}
104+
else {
105+
variable.value = resolvedValue;
106+
}
107+
}
108+
109+
return next();
110+
}
111+
112+
try {
113+
result = secretResolver(ref, handleResolution);
114+
115+
if (result && typeof result.then === 'function') {
116+
result
117+
.then(function (resolvedValue) {
118+
handleResolution(null, resolvedValue);
119+
})
120+
.catch(function (err) {
121+
handleResolution(err);
122+
});
123+
}
124+
}
125+
catch (err) {
126+
handleResolution(err);
127+
}
128+
}, function (err) {
129+
callback(err);
130+
});
131+
}
132+
133+
module.exports = {
134+
resolveSecrets,
135+
extractRefVariables
136+
};

0 commit comments

Comments
 (0)