UNPKG

@cdklabs/cdk-amazonmq

Version:
449 lines (440 loc) 14.5 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/rabbitmq/custom-resource/handler/index.ts var index_exports = {}; __export(index_exports, { handler: () => handler }); module.exports = __toCommonJS(index_exports); // src/rabbitmq/custom-resource/handler/dynamic-references/secret.ts var import_util_arn_parser = require("@aws-sdk/util-arn-parser"); var SecretsDynamicRefereceParser = class _SecretsDynamicRefereceParser { static get regex() { return /^\{\{(resolve:secretsmanager[a-zA-Z0-9:_-]*?)\}\}$/; } static looksLikeSecretDynamicReference(text) { return _SecretsDynamicRefereceParser.regex.test(text); } static parse(text) { if (!_SecretsDynamicRefereceParser.looksLikeSecretDynamicReference(text)) { return void 0; } const matches = text.match(_SecretsDynamicRefereceParser.regex); if (!matches) { throw new Error(`Invalid secret dynamic reference: ${text}`); } const referenceContents = matches[matches.length === 2 ? 1 : 2]; const arnBased = referenceContents.startsWith( "resolve:secretsmanager:arn:" ); const parts = referenceContents.split(":"); const valueAtOrDefault = (arr, idx, defaultValue) => arr.length > idx && arr[idx] !== "" ? arr[idx] : defaultValue; if (!arnBased) { return { secretId: parts[2], secretString: valueAtOrDefault(parts, 3), jsonKey: valueAtOrDefault(parts, 4), versionStage: valueAtOrDefault(parts, 5), versionId: valueAtOrDefault(parts, 6) }; } return { secretId: (0, import_util_arn_parser.build)({ partition: parts[3], service: parts[4], region: parts[5], accountId: parts[6], resource: `${parts[7]}:${parts[8]}` }), secretString: valueAtOrDefault(parts, 9), jsonKey: valueAtOrDefault(parts, 10), versionStage: valueAtOrDefault(parts, 11), versionId: valueAtOrDefault(parts, 12) }; } }; // src/rabbitmq/custom-resource/handler/dynamic-references/string-parameter.ts var StringParameterDynamicRefereceParser = class _StringParameterDynamicRefereceParser { static get regex() { return /^\{\{(resolve:ssm[a-zA-Z0-9:_-]*?)\}\}$/; } static looksLikeParameterDynamicReference(text) { return _StringParameterDynamicRefereceParser.regex.test(text); } static parse(text) { if (!_StringParameterDynamicRefereceParser.looksLikeParameterDynamicReference( text )) { return void 0; } const matches = text.match(_StringParameterDynamicRefereceParser.regex); if (!matches) { throw new Error(`Invalid secret dynamic reference: ${text}`); } const referenceContents = matches[matches.length === 2 ? 1 : 2]; const parts = referenceContents.split(":"); if (parts.length < 3) { throw new Error(`Invalid secret dynamic reference: ${text}`); } return { isSecure: parts[1] === "ssm-secure", parameterName: parts[2], version: parts.length === 4 ? parseInt(parts[3]) : void 0 }; } }; // src/rabbitmq/custom-resource/handler/dynamic-references/dynamic-reference-extractor.ts var DynamicReferenceExtractor = class _DynamicReferenceExtractor { static get regex() { return /(\{\{resolve:[a-zA-Z0-9:_-]*?\}\})/gm; } static extract(text) { let secrets; let parameters; const matches = text.matchAll(_DynamicReferenceExtractor.regex); if (!matches) { return [secrets, parameters]; } const matchesArray = [...matches]; const secretsArray = matchesArray.filter( (m) => SecretsDynamicRefereceParser.looksLikeSecretDynamicReference(m[0]) ).map((m) => ({ reference: m[0], components: SecretsDynamicRefereceParser.parse(m[0]) })).filter((s) => s.reference !== void 0); if (secretsArray.length > 0) { secrets = secretsArray; } const paramsArray = matchesArray.filter( (m) => StringParameterDynamicRefereceParser.looksLikeParameterDynamicReference( m[0] ) ).map((m) => ({ reference: m[0], components: StringParameterDynamicRefereceParser.parse(m[0]) })).filter((s) => s.reference !== void 0); if (paramsArray.length > 0) { parameters = paramsArray; } return [secrets, parameters]; } }; // src/rabbitmq/custom-resource/handler/dynamic-references/index.ts var import_client_secrets_manager = require("@aws-sdk/client-secrets-manager"); var import_client_ssm = require("@aws-sdk/client-ssm"); var smClient = new import_client_secrets_manager.SecretsManagerClient({}); var ssmClient = new import_client_ssm.SSMClient({}); async function replaceDynamicReferences(text) { if (!text) { return text; } let resultText = text; const [secrets, parameters] = DynamicReferenceExtractor.extract(text); resultText = await processSecrets(secrets, resultText); resultText = await processParameters(parameters, resultText); return resultText; } async function processParameters(parameters, resultText) { if (parameters) { for (const { reference, components } of parameters) { const { Parameter } = await ssmClient.send( new import_client_ssm.GetParameterCommand({ Name: components.version ? `${components.parameterName}:${components.version}` : components.parameterName, WithDecryption: components.isSecure }) ); if (Parameter && Parameter.Value) { if (components.parameterName) { resultText = resultText.replace( reference, encodeURIComponent(Parameter.Value) ); } } } } return resultText; } async function processSecrets(secrets, resultText) { if (secrets) { for (const { reference, components } of secrets) { const { SecretString } = await smClient.send( new import_client_secrets_manager.GetSecretValueCommand({ SecretId: components.secretId, VersionId: components.versionId, VersionStage: components.versionStage }) ); if (!SecretString) { continue; } if (components.jsonKey) { resultText = resultText.replace( reference, encodeURIComponent(JSON.parse(SecretString)[components.jsonKey]) ); } else { resultText = resultText.replace( reference, encodeURIComponent(SecretString) ); } } } return resultText; } // src/rabbitmq/custom-resource/handler/rabbitmq-management-api.ts var https = __toESM(require("https")); var import_client_secrets_manager2 = require("@aws-sdk/client-secrets-manager"); var smClient2 = new import_client_secrets_manager2.SecretsManagerClient({}); var request2 = async (options) => { validateOptions(options); const { credentials } = options; if (credentials === void 0) { throw new Error("CREDENTIALS"); } const { SecretString } = await smClient2.send( new import_client_secrets_manager2.GetSecretValueCommand({ SecretId: credentials }) ); if (SecretString === void 0) { throw new Error("SecretString"); } const { username, password } = JSON.parse(SecretString); const url = new URL(options.url); const input = { rabbitUrl: url, username, password, path: options.path, method: options.method ?? "GET", payload: options.payload }; const { payload } = await httpsRequest(input); return payload; }; function validateOptions(options) { if (!options.path.startsWith("/api")) { throw new Error( `There is no RabbitMQ Management HTTP API call that does not start with '/api'. Received ${options.url}` ); } } var httpsRequest = (input) => { const { rabbitUrl, path, method, username, password, payload } = input; const options = { hostname: rabbitUrl.hostname, port: 443, path, method, headers: { "Content-Type": "application/json", Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString( "base64" )}` } }; return new Promise((resolve, reject) => { const req = https.request({ ...options }, (res) => { res.setEncoding("utf8"); let rawData = ""; res.on("data", (chunk) => { rawData += chunk; }); res.on("end", () => { try { if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { resolve({ headers: res.headers, statusCode: res.statusCode, payload: rawData !== "" ? JSON.parse(rawData) : void 0 }); } else { reject( new Error( `Request failed with status code ${res.statusCode}: ${rawData}` ) ); } } catch (e) { reject(e); } }); }); req.on("error", (e) => { reject(e); }); if (payload !== void 0) { req.write(JSON.stringify(payload)); } req.end(); }); }; // src/rabbitmq/custom-resource/handler/utils.ts var https2 = __toESM(require("https")); function decodeCall(call) { if (!call) { return void 0; } return JSON.parse(call); } function respond(event, responseStatus, reason, physicalResourceId, data) { const responseBody = JSON.stringify({ Status: responseStatus, Reason: reason, PhysicalResourceId: physicalResourceId, StackId: event.StackId, RequestId: event.RequestId, LogicalResourceId: event.LogicalResourceId, NoEcho: false, Data: data }); const parsedUrl = new URL(event.ResponseURL); const requestOptions = { hostname: parsedUrl.hostname, path: `${parsedUrl.pathname}${parsedUrl.search}`, port: parsedUrl.port || 443, method: "PUT", headers: { "content-type": "", "content-length": Buffer.byteLength(responseBody, "utf8") } }; return new Promise((resolve, reject) => { try { const request4 = https2.request(requestOptions, (response) => { response.setEncoding("utf8"); const responseData = []; response.on("data", (chunk) => responseData.push(chunk)); response.on("end", () => { resolve(responseData.join("")); }); response.on("error", (e) => { reject(e); }); }); request4.on("error", (e) => { reject(e); }); request4.write(responseBody); request4.end(); } catch (e) { reject(e); } }); } function filterKeys(object, pred) { return Object.entries(object).reduce( (acc, [k, v]) => pred(k) ? { ...acc, [k]: v } : acc, {} ); } function startsWithOneOf(searchStrings) { return function(string) { for (const searchString of searchStrings) { if (string.startsWith(searchString)) { return true; } } return false; }; } function flattenObj(ob) { let result = {}; for (const i in ob) { if (typeof ob[i] === "object") { const temp = flattenObj(ob[i]); for (const j in temp) { result[i + "." + j] = temp[j]; } } else { result[i] = ob[i]; } } return result; } // src/rabbitmq/custom-resource/handler/index.ts async function handler(event, context) { try { event.ResourceProperties[event.RequestType] = await replaceDynamicReferences( event.ResourceProperties[event.RequestType] ); event.ResourceProperties.Create = decodeCall( event.ResourceProperties.Create ); event.ResourceProperties.Update = decodeCall( event.ResourceProperties.Update ); event.ResourceProperties.Delete = decodeCall( event.ResourceProperties.Delete ); let physicalResourceId; switch (event.RequestType) { case "Create": physicalResourceId = event.ResourceProperties.Create?.physicalResourceId?.id ?? event.ResourceProperties.Update?.physicalResourceId?.id ?? event.ResourceProperties.Delete?.physicalResourceId?.id ?? event.LogicalResourceId; break; case "Update": case "Delete": physicalResourceId = event.ResourceProperties[event.RequestType]?.physicalResourceId?.id ?? event.PhysicalResourceId; break; } let data = void 0; const apiCallProps = event.ResourceProperties[event.RequestType]; if (apiCallProps !== void 0) { const { Url: url, Credentials: credentials } = event.ResourceProperties; const options = { url, credentials, ...apiCallProps }; const response = await request2(options); if (response) { const flatData = flattenObj(response); data = options.outputPaths ? filterKeys(flatData, startsWithOneOf(options.outputPaths)) : flatData; if (apiCallProps.physicalResourceId?.responsePath) { physicalResourceId = flatData[apiCallProps.physicalResourceId.responsePath]; } } } await respond(event, "SUCCESS", "OK", physicalResourceId, data); } catch (e) { await respond( event, "FAILED", e.message || "Internal Error", context.logStreamName, {} ); } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { handler });