diff --git a/CHANGELOG.yaml b/CHANGELOG.yaml index f06408add..53933f362 100644 --- a/CHANGELOG.yaml +++ b/CHANGELOG.yaml @@ -1,3 +1,12 @@ +7.43.0: + date: 2024-10-30 + new features: + - GH-1478 Added support for lazily fetching vault access status + fixed bugs: + - >- + GH-1480 Fixed a bug where vault domain matching did not work with port in + URL + 7.42.0: date: 2024-09-04 new features: diff --git a/lib/runner/extensions/event.command.js b/lib/runner/extensions/event.command.js index 1b61378e1..5ccd36279 100644 --- a/lib/runner/extensions/event.command.js +++ b/lib/runner/extensions/event.command.js @@ -243,16 +243,12 @@ module.exports = { packageResolver = _.get(this, 'options.script.packageResolver'), vaultSecrets = payload.context.vaultSecrets, - allowVaultAccess = _.get(vaultSecrets, '_.allowScriptAccess'), + // Do not assign any initial value here as it will be used + // to determine if the vault access check was done or not + hasVaultAccess, events; - // Explicitly enable tracking for vault secrets here as this will - // not be sent to sandbox who otherwise takes care of mutation tracking - if (allowVaultAccess) { - vaultSecrets.enableTracking({ autoCompact: true }); - } - // @todo: find a better place to code this so that event is not aware of such options if (abortOnFailure) { abortOnError = true; @@ -398,12 +394,23 @@ module.exports = { } }.bind(this)); - this.host.on(EXECUTION_VAULT_BASE + executionId, function (id, cmd, ...args) { + this.host.on(EXECUTION_VAULT_BASE + executionId, async function (id, cmd, ...args) { + if (hasVaultAccess === undefined) { + try { + // eslint-disable-next-line require-atomic-updates + hasVaultAccess = Boolean(await vaultSecrets?._?.allowScriptAccess(item.id)); + } + catch (_) { + // eslint-disable-next-line require-atomic-updates + hasVaultAccess = false; + } + } + // Ensure error is string // TODO identify why error objects are not being serialized correctly const dispatch = (e, r) => { this.host.dispatch(EXECUTION_VAULT_BASE + executionId, id, e, r); }; - if (!allowVaultAccess) { + if (!hasVaultAccess) { return dispatch('Vault access denied'); } @@ -411,6 +418,10 @@ module.exports = { return dispatch(`Invalid vault command: ${cmd}`); } + // Explicitly enable tracking for vault secrets here as this will + // not be sent to sandbox who otherwise takes care of mutation tracking + vaultSecrets.enableTracking({ autoCompact: true }); + dispatch(null, vaultSecrets[cmd](...args)); }.bind(this)); @@ -556,7 +567,7 @@ module.exports = { result && result.request && (result.request = new sdk.Request(result.request)); // vault secrets are not sent to sandbox, thus using the scope from run context. - if (allowVaultAccess && vaultSecrets) { + if (hasVaultAccess && vaultSecrets) { result.vaultSecrets = vaultSecrets; // Prevent mutations from being carry-forwarded to subsequent events diff --git a/lib/runner/util.js b/lib/runner/util.js index 471a8b35b..9ee4573bb 100644 --- a/lib/runner/util.js +++ b/lib/runner/util.js @@ -201,7 +201,7 @@ module.exports = { const url = new Url(domain); // @note URL path is ignored - return `${url.protocol || 'https'}://${url.getRemote()}/*`; + return `${url.protocol || 'https'}://${url.getRemote()}:*/*`; })); }); diff --git a/package-lock.json b/package-lock.json index 52cc11a98..17f9d53d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "postman-runtime", - "version": "7.42.0", + "version": "7.43.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "postman-runtime", - "version": "7.42.0", + "version": "7.43.0", "license": "Apache-2.0", "dependencies": { "@postman/tough-cookie": "4.1.3-postman.1", diff --git a/package.json b/package.json index ecaaa120e..e182d0081 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postman-runtime", - "version": "7.42.0", + "version": "7.43.0", "description": "Underlying library of executing Postman Collections", "author": "Postman Inc.", "license": "Apache-2.0", diff --git a/test/integration/sanity/variable-changes.test.js b/test/integration/sanity/variable-changes.test.js index f7488512a..7aaa720ef 100644 --- a/test/integration/sanity/variable-changes.test.js +++ b/test/integration/sanity/variable-changes.test.js @@ -9,7 +9,7 @@ describe('variable changes', function () { requester: { followRedirects: false }, vaultSecrets: { id: 'vault', - _allowScriptAccess: true, + _allowScriptAccess: function () { return true; }, values: [ { key: 'vault:key5', value: 'vault-value-5', enabled: true }, { key: 'vault:key6', value: 'vault-value-6', enabled: true } diff --git a/test/integration/sanity/vaultSecrets.test.js b/test/integration/sanity/vaultSecrets.test.js index 6687d69bc..0bb949475 100644 --- a/test/integration/sanity/vaultSecrets.test.js +++ b/test/integration/sanity/vaultSecrets.test.js @@ -619,7 +619,7 @@ describe('vaultSecrets', function () { vaultSecrets: { id: 'vault', prefix: 'vault:', - _allowScriptAccess: true, + _allowScriptAccess: function () { return true; }, values: [ { key: 'vault:var1', @@ -705,6 +705,87 @@ describe('vaultSecrets', function () { }); }); + describe('should resolve secrets when port is part of url', function () { + var testrun; + + before(function (done) { + this.run({ + vaultSecrets: { + id: 'vault', + prefix: 'vault:', + _allowScriptAccess: true, + values: [ + { + key: 'vault:var1', + value: 'basic-auth', + _domains: ['https://postman-echo.com'] + }, + { + key: 'vault:var2', + value: 'postman', + _domains: ['https://postman-echo.com'] + }, + { + key: 'vault:var3', + value: 'password' + } + ] + }, + collection: { + item: [{ + event: [ + { + listen: 'prerequest', + script: { + exec: 'pm.vault.set(\'var4\', \'http://postman-echo.com\')' + } + } + ], + request: { + url: 'https://postman-echo.com:80/{{vault:var1}}', + method: 'GET', + auth: { + type: 'basic', + basic: [ + { key: 'username', value: '{{vault:var2}}' }, + { key: 'password', value: '{{vault:var3}}' } + ] + } + } + }] + } + }, function (err, results) { + testrun = results; + done(err); + }); + }); + + it('should have completed the run', function () { + expect(testrun).to.be.ok; + expect(testrun.done.getCall(0).args[0]).to.be.null; + expect(testrun).to.nested.include({ + 'done.calledOnce': true, + 'start.calledOnce': true + }); + }); + + it('should handle protocol for a resolved domain', function () { + var url = testrun.request.getCall(0).args[3].url.toString(); + + expect(url).to.equal('https://postman-echo.com:80/basic-auth'); + }); + + it('should resolve vault secrets in auth', function () { + var request = testrun.response.getCall(0).args[3], + auth = request.auth.parameters().toObject(); + + expect(auth).to.deep.include({ + username: 'postman', + password: 'password' + }); + }); + }); + describe('scripts', function () { describe('should be able to get vault secrets using pm.vault.get', function () { var testrun; @@ -714,7 +795,7 @@ describe('vaultSecrets', function () { vaultSecrets: { id: 'vault', prefix: 'vault:', - _allowScriptAccess: true, + _allowScriptAccess: function () { return true; }, values: [ { key: 'vault:var1', @@ -766,7 +847,7 @@ describe('vaultSecrets', function () { vaultSecrets: { id: 'vault', prefix: 'vault:', - _allowScriptAccess: true, + _allowScriptAccess: function () { return true; }, values: [ { key: 'vault:var1', @@ -819,7 +900,7 @@ describe('vaultSecrets', function () { vaultSecrets: { id: 'vault', prefix: 'vault:', - _allowScriptAccess: true, + _allowScriptAccess: function () { return true; }, values: [ { key: 'vault:var1', @@ -876,7 +957,7 @@ describe('vaultSecrets', function () { vaultSecrets: { id: 'vault', prefix: 'vault:', - _allowScriptAccess: true, + _allowScriptAccess: function () { return true; }, values: [ { key: 'vault:var1', @@ -992,5 +1073,173 @@ describe('vaultSecrets', function () { expect(prerequest.vaultSecrets).to.deep.equal(undefined); }); }); + + describe('should handle _allowScriptAccess as a function', function () { + var testrun; + + before(function (done) { + this.run({ + vaultSecrets: { + id: 'vault', + prefix: 'vault:', + _allowScriptAccess: function (itemId) { + return itemId === 'item1'; + }, + values: [ + { + key: 'vault:var1', + value: 'value1' + } + ] + }, + collection: { + item: [{ + id: 'item1', + event: [{ + listen: 'prerequest', + script: { + exec: ` + const v = await pm.vault.get('var1'); + console.log(v); + ` + } + }], + request: 'https://postman-echo.com/get' + }, { + id: 'item2', + event: [{ + listen: 'prerequest', + script: { + exec: ` + try { + const v = await pm.vault.get('var1'); + console.log(v); + } catch (error) { + console.error(error.message); + } + ` + } + }], + request: 'https://postman-echo.com/get' + }] + } + }, function (err, results) { + testrun = results; + done(err); + }); + }); + + it('should allow vault access for item1', function () { + var prConsoleArgs = testrun.console.getCall(0).args.slice(2); + + expect(prConsoleArgs).to.deep.equal(['value1']); + }); + + it('should deny vault access for item2', function () { + var prConsoleArgs = testrun.console.getCall(1).args.slice(2); + + expect(prConsoleArgs).to.deep.equal(['Vault access denied']); + }); + }); + + describe('should fail if _allowScriptAccess is not a function', function () { + var testrun; + + before(function (done) { + this.run({ + vaultSecrets: { + id: 'vault', + prefix: 'vault:', + _allowScriptAccess: true, + values: [ + { + key: 'vault:var1', + value: 'value1' + } + ] + }, + collection: { + item: { + event: [{ + listen: 'prerequest', + script: { + exec: ` + try { + const v = await pm.vault.get('var1'); + console.log('Vault value:', v); + } catch (error) { + console.error('Vault error:', error.message); + } + ` + } + }], + request: 'https://postman-echo.com/get' + } + } + }, function (err, results) { + testrun = results; + done(err); + }); + }); + + it('should deny vault access when _allowScriptAccess is not a function', function () { + expect(testrun.console.called).to.be.true; + + var consoleArgs = testrun.console.getCall(0).args.slice(2); + + expect(consoleArgs[0]).to.equal('Vault error:'); + expect(consoleArgs[1]).to.equal('Vault access denied'); + }); + }); + + describe('should handle when _allowScriptAccess function throws', function () { + var testrun; + + before(function (done) { + this.run({ + vaultSecrets: { + id: 'vault', + prefix: 'vault:', + _allowScriptAccess: function () { throw new Error('Custom error'); }, + values: [ + { + key: 'vault:var1', + value: 'value1' + } + ] + }, + collection: { + item: { + event: [{ + listen: 'prerequest', + script: { + exec: ` + try { + const v = await pm.vault.get('var1'); + console.log('Vault value:', v); + } catch (error) { + console.error('Vault error:', error.message); + } + ` + } + }], + request: 'https://postman-echo.com/get' + } + } + }, function (err, results) { + testrun = results; + done(err); + }); + }); + + it('should deny vault access when _allowScriptAccess function throws', function () { + expect(testrun.console.called).to.be.true; + + var consoleArgs = testrun.console.getCall(0).args.slice(2); + + expect(consoleArgs[0]).to.equal('Vault error:'); + expect(consoleArgs[1]).to.equal('Vault access denied'); + }); + }); }); });