diff --git a/package.json b/package.json index 691bc6cd..d268e78d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adobe/aio-cli-plugin-api-mesh", - "version": "5.3.0", + "version": "5.3.2", "description": "Adobe I/O CLI plugin to develop and manage API mesh sources", "keywords": [ "oclif-plugin" diff --git a/src/commands/api-mesh/__tests__/set-log-forwarding.test.js b/src/commands/api-mesh/__tests__/set-log-forwarding.test.js index 52973194..fc322c2e 100644 --- a/src/commands/api-mesh/__tests__/set-log-forwarding.test.js +++ b/src/commands/api-mesh/__tests__/set-log-forwarding.test.js @@ -11,6 +11,7 @@ governing permissions and limitations under the License. */ const SetLogForwardingCommand = require('../config/set/log-forwarding'); +const crypto = require('crypto'); const { initSdk, promptConfirm, @@ -18,7 +19,7 @@ const { promptInput, promptInputSecret, } = require('../../../helpers'); -const { getMeshId, setLogForwarding } = require('../../../lib/smsClient'); +const { getMeshId, setLogForwarding, getPublicEncryptionKey } = require('../../../lib/smsClient'); jest.mock('../../../helpers', () => ({ initSdk: jest.fn().mockResolvedValue({}), @@ -31,6 +32,21 @@ jest.mock('../../../helpers', () => ({ jest.mock('../../../lib/smsClient'); jest.mock('../../../classes/logger'); +jest.mock('crypto'); +// Mock randomBytes for aesKey and iv +const mockAesKey = Buffer.from('mockAesKey'); +const mockIv = Buffer.from('mockIv'); +const mockEncryptedAesKey = Buffer.from('mockEncryptedAesKey'); +const mockCipher = { + update: jest.fn().mockReturnValueOnce('mockEncryptedData'), + final: jest.fn().mockReturnValueOnce(''), +}; +const mockEncryptedLicenseKey = { + iv: 'bW9ja0l2', + key: 'bW9ja0VuY3J5cHRlZEFlc0tleQ==', + data: 'mockEncryptedData', +}; + describe('SetLogForwardingCommand', () => { let parseSpy; let logSpy; @@ -60,8 +76,13 @@ describe('SetLogForwardingCommand', () => { workspaceName: 'workspaceName', }); getMeshId.mockResolvedValue('meshId'); + getPublicEncryptionKey.mockResolvedValue('dummy_public_key'); setLogForwarding.mockResolvedValue({ success: true, result: true }); global.requestId = 'dummy_request_id'; + + // Reset mockCipher methods + mockCipher.update.mockReset().mockReturnValueOnce('mockEncryptedData'); + mockCipher.final.mockReset().mockReturnValueOnce(''); }); afterEach(() => { @@ -71,6 +92,10 @@ describe('SetLogForwardingCommand', () => { describe('Test New Relic destination', () => { /** Success Scenario */ test('sets log forwarding with valid parameters', async () => { + crypto.randomBytes.mockReturnValueOnce(mockAesKey).mockReturnValueOnce(mockIv); + crypto.createCipheriv.mockReturnValueOnce(mockCipher); + crypto.publicEncrypt.mockReturnValueOnce(mockEncryptedAesKey); + const command = new SetLogForwardingCommand([], {}); await command.run(); @@ -88,7 +113,7 @@ describe('SetLogForwardingCommand', () => { destination: 'newrelic', config: { baseUri: 'https://log-api.newrelic.com/log/v1', - licenseKey: 'abcdef0123456789abcdef0123456789abcdef01', + licenseKey: JSON.stringify(mockEncryptedLicenseKey), // Expect the encrypted value }, }, ); @@ -115,15 +140,6 @@ describe('SetLogForwardingCommand', () => { ); }); - test('throws an error if license key has wrong format', async () => { - promptInputSecret.mockResolvedValueOnce('wrongformat'); // Too short - - const command = new SetLogForwardingCommand([], {}); - await expect(command.run()).rejects.toThrow( - `The license key is in the wrong format. Expected: 40 characters (received: ${11})`, - ); - }); - test('prompts for missing destination', async () => { parseSpy.mockResolvedValueOnce({ flags: { @@ -135,6 +151,10 @@ describe('SetLogForwardingCommand', () => { args: [], }); + crypto.randomBytes.mockReturnValueOnce(mockAesKey).mockReturnValueOnce(mockIv); + crypto.createCipheriv.mockReturnValueOnce(mockCipher); + crypto.publicEncrypt.mockReturnValueOnce(mockEncryptedAesKey); + const command = new SetLogForwardingCommand([], {}); await command.run(); @@ -195,6 +215,10 @@ describe('SetLogForwardingCommand', () => { args: [], }); + crypto.randomBytes.mockReturnValueOnce(mockAesKey).mockReturnValueOnce(mockIv); + crypto.createCipheriv.mockReturnValueOnce(mockCipher); + crypto.publicEncrypt.mockReturnValueOnce(mockEncryptedAesKey); + const command = new SetLogForwardingCommand([], {}); await command.run(); @@ -203,6 +227,10 @@ describe('SetLogForwardingCommand', () => { }); test('sets log forwarding with auto-confirmation', async () => { + crypto.randomBytes.mockReturnValueOnce(mockAesKey).mockReturnValueOnce(mockIv); + crypto.createCipheriv.mockReturnValueOnce(mockCipher); + crypto.publicEncrypt.mockReturnValueOnce(mockEncryptedAesKey); + parseSpy.mockResolvedValueOnce({ flags: { ignoreCache: false, @@ -225,7 +253,7 @@ describe('SetLogForwardingCommand', () => { destination: 'newrelic', config: { baseUri: 'https://log-api.newrelic.com/log/v1', - licenseKey: 'abcdef0123456789abcdef0123456789abcdef01', + licenseKey: JSON.stringify(mockEncryptedLicenseKey), // Expect the encrypted value }, }, ); @@ -236,6 +264,10 @@ describe('SetLogForwardingCommand', () => { const errorMessage = 'Unable to set log forwarding details'; setLogForwarding.mockRejectedValueOnce(new Error(errorMessage)); + crypto.randomBytes.mockReturnValueOnce(mockAesKey).mockReturnValueOnce(mockIv); + crypto.createCipheriv.mockReturnValueOnce(mockCipher); + crypto.publicEncrypt.mockReturnValueOnce(mockEncryptedAesKey); + const command = new SetLogForwardingCommand([], {}); await expect(command.run()).rejects.toThrow( 'Failed to set log forwarding details. Try again. RequestId: dummy_request_id', diff --git a/src/commands/api-mesh/config/set/log-forwarding.js b/src/commands/api-mesh/config/set/log-forwarding.js index 6c0db009..d6d3b91f 100644 --- a/src/commands/api-mesh/config/set/log-forwarding.js +++ b/src/commands/api-mesh/config/set/log-forwarding.js @@ -24,8 +24,14 @@ const { autoConfirmActionFlag, jsonFlag, destinations, + LogForwardingKeys, + encryptSecrets, } = require('../../../../utils'); -const { setLogForwarding, getMeshId } = require('../../../../lib/smsClient'); +const { + setLogForwarding, + getMeshId, + getPublicEncryptionKey, +} = require('../../../../lib/smsClient'); class SetLogForwardingCommand extends Command { static flags = { @@ -85,6 +91,30 @@ class SetLogForwardingCommand extends Command { if (shouldContinue) { try { + // Get publicKey for encryption + const publicKey = await getPublicEncryptionKey(imsOrgCode); + if (!publicKey) { + this.error( + `Unable to set log forwarding details. Unable to get public key. Try again. RequestId: ${global.requestId}`, + ); + } + // Get the key to encrypt from config + const getEncryptableKey = config => { + if (LogForwardingKeys.LICENSE_KEY in config) return LogForwardingKeys.LICENSE_KEY; + if (LogForwardingKeys.HEC_TOKEN in config) return LogForwardingKeys.HEC_TOKEN; + return null; + }; + const keyToEncrypt = getEncryptableKey(destinationConfig.config); + if (!keyToEncrypt) { + this.error( + `Unable to set log forwarding details. No valid key to encrypt found in the configuration. Try again. RequestId: ${global.requestId}`, + ); + } + // Encrypt the key + destinationConfig.config[keyToEncrypt] = await encryptSecrets( + publicKey, + destinationConfig.config[keyToEncrypt], + ); const response = await setLogForwarding( imsOrgCode, projectId, diff --git a/src/constants.js b/src/constants.js index adee4e23..09bf8dd6 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,5 +1,7 @@ const { getCliEnv } = require('@adobe/aio-lib-env'); +const dotenv = require('dotenv'); +dotenv.config(); const clientEnv = getCliEnv(); const StageConstants = { @@ -24,4 +26,16 @@ const ProdConstants = { }; const envConstants = clientEnv === 'stage' ? StageConstants : ProdConstants; -module.exports = { ...envConstants }; + +// Export environment variables/constants +module.exports = { + DEV_CONSOLE_BASE_URL: process.env.DEV_CONSOLE_BASE_URL || envConstants.DEV_CONSOLE_BASE_URL, + DEV_CONSOLE_API_KEY: process.env.DEV_CONSOLE_API_KEY || envConstants.DEV_CONSOLE_API_KEY, + DEV_CONSOLE_TRANSPORTER_API_KEY: + process.env.DEV_CONSOLE_TRANSPORTER_API_KEY || envConstants.DEV_CONSOLE_TRANSPORTER_API_KEY, + AIO_CLI_API_KEY: process.env.AIO_CLI_API_KEY || envConstants.AIO_CLI_API_KEY, + SMS_BASE_URL: process.env.SMS_BASE_URL || envConstants.SMS_BASE_URL, + MESH_BASE_URL: process.env.MESH_BASE_URL || envConstants.MESH_BASE_URL, + MESH_SANDBOX_BASE_URL: process.env.MESH_SANDBOX_BASE_URL || envConstants.MESH_SANDBOX_BASE_URL, + SMS_API_KEY: process.env.SMS_API_KEY || envConstants.SMS_API_KEY, +}; diff --git a/src/helpers.js b/src/helpers.js index 04be1d33..9979ff5c 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -452,15 +452,13 @@ function initMetadata(config) { const { version, plugins, userAgent, platform, arch } = config; const currentIntalledVersion = getCurrentInstalledPluginVersion(plugins); - const metadataHeaders = { + global.metadataHeaders = { 'x-aio-cli-version': version, 'x-aio-cli-user-agent': userAgent, 'x-aio-cli-platform': platform, 'x-aio-cli-arch': arch, 'x-aio-cli-plugin-api-mesh-version': currentIntalledVersion, }; - - global.metadataHeaders = metadataHeaders; } catch (error) { logger.error('Unable to initialize metadata headers'); logger.error(error.message); diff --git a/src/utils.js b/src/utils.js index 0946777f..a4f17b6c 100644 --- a/src/utils.js +++ b/src/utils.js @@ -101,6 +101,11 @@ const logFilenameFlag = Flags.string({ required: true, }); +const LogForwardingKeys = { + LICENSE_KEY: 'licenseKey', + HEC_TOKEN: 'hecToken', +}; + // The `destinations` object to hold the configuration for log forwarding destinations. // It prompts for the required inputs for the destination. // Each destination can have different key/value pairs of configuration credentials. @@ -132,11 +137,6 @@ const destinations = { if (!value) { throw new Error('License key is required'); } - if (value.length !== 40) { - throw new Error( - `The license key is in the wrong format. Expected: 40 characters (received: ${value.length})`, - ); - } }, }, ], @@ -777,7 +777,7 @@ async function localToUTCTime(timeString) { try { //Get the local timezone // takes the timezone where the javascript runtime is running - // reference https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/resolvedOptions#browser_compatibility:~:text=The%20value%20provided%20for%20this%20property%20in%20the%20options%20argument%2C%20with%20default%20filled%20in%20as%20needed.%20It%20is%20an%20IANA%20time%20zone%20name.%20The%20default%20is%20the%20runtime%27s%20default%20time%20zone. + // reference https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/resolvedOptions#browser_compatibility:~:text=The%20value%20provided%20for%20this%20property%20in%20the%20options%20argument%2C%20with%20default%20filled%20in%20as%20needed.%20It%20is%20an%20IANA%20time%20zone.%20The%20default%20is%20the%20runtime%27s%20default%20time%20zone. const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; // Create a Date object from the formatted time string @@ -824,4 +824,5 @@ module.exports = { localToUTCTime, cachePurgeAllActionFlag, destinations, + LogForwardingKeys, }; diff --git a/yarn.lock b/yarn.lock index 10b8c1b6..bc7133be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8208,7 +8208,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.1.1, ms@^2.1.3: +ms@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==