From f0d733ae51a247916df2729f377d428e0fa5c69c Mon Sep 17 00:00:00 2001 From: LEI BAO Date: Tue, 29 Jun 2021 18:23:41 +0800 Subject: [PATCH 01/12] Add the usage guidance --- AkamaiSample/README.md | 48 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 AkamaiSample/README.md diff --git a/AkamaiSample/README.md b/AkamaiSample/README.md new file mode 100644 index 0000000..77fca61 --- /dev/null +++ b/AkamaiSample/README.md @@ -0,0 +1,48 @@ +# Certificate Order - DNS Domain Validation sample for Akamai + +## Prerequisites + +1. An instance of [IBM Cloud Certificate Manager](https://cloud.ibm.com/docs/services/certificate-manager) +2. Account in Akamai + +> **Note:** Before you can work with DNS records in Akamai, make sure to request appropriate access permissions from the account owner. + +## Configuration + +### IBM Cloud Function action + +1. Create a new [IBM Cloud Function action](https://cloud.ibm.com/docs/openwhisk/index.html#openwhisk_start_hello_world) + + * In IBM Cloud Functions, select **Actions** from the sidebar + * Click on **Create** + * Follow the on-screen instructions + +2. Deploy the sample + + Select **Code** from the sidebar, and paste the contents of the **main.js** file of the sample + +3. [Bind parameters to the action](https://cloud.ibm.com/docs/openwhisk/parameters.html#default-params-action) + + Select **Parameters** from the sidebar, and add the following: + + 1. `allowedCertificateManagerCRNs` - a JSON Object containing a list of Certificate Manager instances that are allowed to invoke this function. + Apply it in order to protect your cloud function from being invoked by unauthorized clients. + E.g. `{"crn:v1:bluemix:public:cloudcerts:us-south:a....":true,"crn:v1:bluemix:public:cloudcerts:eu-de:a...":true}` + + * Find your Certificate Manager instance CRN from the Settings sidebar item + * Or from CLI: `ibmcloud resource service-instance [INSTANCE NAME]`, grab the `ID` value + + 2. `cmRegion` - your Certificate Manager service instance region value. Can be one of: `us-south`, `eu-gb`, `eu-de`, `jp-tok` + E.g. `"us-south"` + + 3. `host` - The Akamai API endpoint hostname. (Get from Akamai client credential) + + 4. `client_token` - The client token for Akamai API calling. (Get from Akamai client credential) + + 5. `client_secret` - The client secret for Akamai API calling. (Get from Akamai client credential) + + 6. `access_token` - The access token for Akamai API calling. (Get from Akamai client credential) + + 7. `max-body` - The max body size of the Akamai API calling. (Get from Akamai client credential) + + * Refer to [this guidance](https://developer.akamai.com/api/getting-started#authsetup) to create the Akamai client credential for API calling. From a05e924f3cda16a30dcd47676e2e1a254bcec3c9 Mon Sep 17 00:00:00 2001 From: LEI BAO Date: Tue, 29 Jun 2021 18:23:53 +0800 Subject: [PATCH 02/12] Add sample code to akamai dns call --- AkamaiSample/main.js | 261 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 AkamaiSample/main.js diff --git a/AkamaiSample/main.js b/AkamaiSample/main.js new file mode 100644 index 0000000..b443728 --- /dev/null +++ b/AkamaiSample/main.js @@ -0,0 +1,261 @@ +const {promisify} = require('bluebird'); +let request = promisify(require('request')); +request = request.defaults({json: true}); +const jwtVerify = promisify(require('jsonwebtoken').verify); +const jwtDecode = require('jsonwebtoken').decode; +const akamaiHost = "https://api.softlayer.com/rest/v3"; + +/** + * Get the public key used to verify that the notification payload is generated by your Certificate Manager instance. + * @param body Object + * @param certificateManagerApiUrl + * @returns {Promise} + */ +const getPublicKey = async (body, certificateManagerApiUrl) => { + console.log(`Get public key for instance ${body.instance_crn}`); + const keysOptions = { + method: 'GET', + url: `${certificateManagerApiUrl}/api/v1/instances/${encodeURIComponent(body.instance_crn)}/notifications/publicKey?keyFormat=pem`, + headers: {'cache-control': 'no-cache'} + }; + let response; + try { + response = await request(keysOptions); + } + catch (err) { + console.log(`Couldn't get the public key for instance ${body.instance_crn}. Reason is: ${getErrorString(err)}`); + throw new Error(`Couldn't get the public key for instance ${body.instance_crn}`); + } + if (response.statusCode !== 200) { + console.error(`Couldn't get the public key for instance ${body.instance_crn} . Reason is: status code ${response.statusCode} and body ${JSON.stringify(response.body)}`); + throw new Error(`Couldn't get the public key for instance ${body.instance_crn}`); + } + return response.body.publicKey; +}; + +/** + * Add TXT record to domain + * @param zoneId + * @param payload challenge data + * @param userInfo user credentials + * @returns {Promise} + */ +const addTxtRecord = async (zoneId, payload, userInfo) => { + const recordName = payload.challenge.txt_record_name; + const recordValue = payload.challenge.txt_record_val; + console.log(`Add TXT record "${recordValue}" to zone ${zoneId}`); + const options = { + method: 'POST', + uri: `${akamaiHost}/SoftLayer_Dns_Domain_ResourceRecord`, + auth: {user: userInfo.user, pass: userInfo.apiKey}, + json: {"parameters": [{"host": recordName, "data": recordValue, "ttl": 60, "type": "txt", "domainId": zoneId}]} + }; + let response; + try { + response = await request(options); + } + catch (err) { + console.log(`Couldn't add TXT record "${recordValue}" to zone ${zoneId}. Reason is: ${getErrorString(err)}`); + throw new Error(`Couldn't add TXT record "${recordValue}" to zone ${zoneId}`); + } + if (response.statusCode !== 201) { + console.log(`Couldn't add TXT record "${recordValue}" to zone ${zoneId}. Reason is: status code ${response.statusCode} and body ${JSON.stringify(response.body)}`); + throw new Error(`Couldn't add TXT record "${recordValue}" to zone ${zoneId}`); + } + console.log(`TXT record "${recordValue}" added to zone ${zoneId} successfully.`); +}; + +/** + * Get zone id by domain. + * @param domain + * @param userInfo user credentials + * @returns {Promise} + */ +const getZoneIdByDomain = async (domain, userInfo) => { + console.log(`Get zone id for domain ${domain}`); + const options = { + method: 'GET', + uri: `${akamaiHost}/config-dns/v2/zones?search=${encodeURIComponent(domain)}&showAll=true}`, + auth: {user: userInfo.user, pass: userInfo.apiKey} + }; + let response; + try { + response = await request(options); + } + catch (err) { + console.error(`Couldn't get zone for domain "${domain}". Reason is: ${getErrorString(err)}`); + throw new Error(`Couldn't get zone for domain "${domain}"`); + } + if (response.statusCode !== 200) { + console.error(`Couldn't get zone for domain ${domain}. Reason is: status code "${response.statusCode}" and body ${JSON.stringify(response.body)}`); + throw new Error(`Couldn't get zone for domain "${domain}"`); + } + if (response.body.length === 0) { + console.error(`Couldn't find domain ${domain}.`); + throw new Error(`Couldn't find domain ${domain}`); + } + console.log(`Get zone for domain "${domain}" finished successfully with body: ${JSON.stringify(response.body)}`); + return response.body[0].id; +}; + +/** + * Get challenge DNS TXT record ids + * @param zoneId + * @param payload challenge data + * @param userInfo user credentials + * @returns {Promise<[]>} + */ +const getTxtRecords = async (zoneId, payload, userInfo) => { + const recordName = payload.challenge.txt_record_name; + const recordValue = payload.challenge.txt_record_val; + console.log(`Get records named ${recordName} from zone ${zoneId}`); + const options = { + method: 'GET', + uri: `${akamaiHost}/SoftLayer_Dns_Domain/${zoneId}/getResourceRecords?objectFilter={"resourceRecords":{"host":{"operation": "${recordName}"},"data":{"operation": "${recordValue}"}}}`, + auth: {user: userInfo.user, pass: userInfo.apiKey} + }; + let response; + try { + response = await request(options); + } + catch (err) { + console.error(`Couldn't get record named ${recordName}. Reason is: ${getErrorString(err)}`); + throw new Error(`Couldn't get record named ${recordName}`); + } + if (response.statusCode !== 200) { + console.error(`Couldn't get records named ${recordName}. Reason is: status code ${response.statusCode} and body ${JSON.stringify(response.body)}`); + throw new Error(`Couldn't get record named ${recordName}`); + } + console.log(`Get records named ${recordName} returned ${response.body.length} results.`); + return response.body; +}; + +/** + * Delete single TXT record from zone. + * @param recordId + * @param userInfo user credentials + * @returns {Promise} + */ +const removeTxtRecord = async (recordId, userInfo) => { + console.log(`Delete TXT record "${recordId}"`); + const options = { + method: 'DELETE', + uri: `${akamaiHost}/SoftLayer_Dns_Domain_ResourceRecord/${recordId}`, + auth: {user: userInfo.user, pass: userInfo.apiKey} + }; + let response; + try { + response = await request(options); + } + catch (err) { + console.log(`Couldn't delete TXT record "${recordId}". Reason is: ${getErrorString(err)}`); + throw new Error(`Couldn't delete TXT record "${recordId}"`); + } + if (response.statusCode !== 200) { + console.log(`Couldn't delete TXT record "${recordId}". Reason is: status code ${response.statusCode} body ${JSON.stringify(response.body)}`); + throw new Error(`Couldn't delete TXT record "${recordId}"`); + } + console.log(`Delete TXT record "${recordId}" finished successfully.`); +}; + +/** + * Set the challenge . + * @param payload notification with challenge + * @param userInfo user credentials + * @returns {Promise} + */ +const setChallenge = async (payload, userInfo) => { + console.log(`Set challenge: '${payload.domain} : ${JSON.stringify(payload.challenge)}`); + let domain = payload.domain; + //remove wildcard in case its wildcard certificate. + domain = domain.replace('*.', ''); + const zoneId = await getZoneIdByDomain(domain, userInfo); + await addTxtRecord(zoneId, payload, userInfo); +}; + +/** + * Remove TXT record of challenge + * @param payload + * @param userInfo user credentials + * @returns {Promise} + */ +const removeChallenge = async (payload, userInfo) => { + console.log(`Remove challenge: '${payload.domain} : ${JSON.stringify(payload.challenge)}`); + let domain = payload.domain; + //remove wildcard in case its wildcard certificate. + domain = domain.replace('*.', ''); + const zoneId = await getZoneIdByDomain(domain, userInfo); + const records = await getTxtRecords(zoneId, payload, userInfo); + await Promise.all(records.map(r => removeTxtRecord(r.id, userInfo).catch())); + console.log(`Remove challenge for domain ${domain} finished.`); +}; + +/** + * + * main() will be run when you invoke this action + * + * @param params Cloud Functions actions accept a single parameter, which must be a JSON object. + * + * @return The output of this action, which must be a JSON object. + * + */ +const main = async (params)=> { + console.log("Cloud function invoked."); + try { + + const body = jwtDecode(params.data); + + // Validate that the notification was sent from a Certificate Manager instance that has allowed access + if (!params.allowedCertificateManagerCRNs || !params.allowedCertificateManagerCRNs[body.instance_crn]) { + console.error(`Certificate Manager instance ${body.instance_crn} is not allowed to invoke this action`); + return Promise.reject({ + statusCode: 403, + headers: {'Content-Type': 'application/json'}, + body: {message: 'Unauthorized'}, + }); + } + const certificateManagerApiUrl = `https://${params.cmRegion}.certificate-manager.cloud.ibm.com`; + const publicKey = await getPublicKey(body, certificateManagerApiUrl); + const decodedNotification = await jwtVerify(params.data, publicKey); + akamaiHost = params.host; + const userInfo = { + client_secret: params.client_secret, + access_token: params.access_token, + client_token: params.client_token + }; + + console.log(`Notification message body: ${JSON.stringify(decodedNotification)}`); + switch (decodedNotification.event_type) { + // Handle other certificate manager event types. + // ... + + // Handling domain validation event types. + case "cert_domain_validation_required": + await setChallenge(decodedNotification, userInfo); + break; + case "cert_domain_validation_completed": + await removeChallenge(decodedNotification, userInfo); + break; + } + } + catch (err) { + console.log(`Action failed. Reason: ${getErrorString(err)}`); + return Promise.reject({ + statusCode: err.statusCode ? err.statusCode : 500, + headers: {'Content-Type': 'application/json'}, + body: {message: err.message ? err.message : 'Error processing your request'}, + }); + } + return { + statusCode: 200, + headers: {'Content-Type': 'application/json'}, + body: {} + }; +}; + +const getErrorString = (error) => { + if (error) + return (typeof error.message === 'string') ? error.message : JSON.stringify(error); + else + return 'Error undefined'; +}; From 1180c9f5e88291c1bb45134537a047734b49ac7c Mon Sep 17 00:00:00 2001 From: LEI BAO Date: Wed, 30 Jun 2021 16:30:02 +0800 Subject: [PATCH 03/12] Add akamai create dns function --- AkamaiSample/README.md | 2 -- AkamaiSample/main.js | 65 ++++++++++++++++++++++++------------------ 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/AkamaiSample/README.md b/AkamaiSample/README.md index 77fca61..98c6af7 100644 --- a/AkamaiSample/README.md +++ b/AkamaiSample/README.md @@ -43,6 +43,4 @@ 6. `access_token` - The access token for Akamai API calling. (Get from Akamai client credential) - 7. `max-body` - The max body size of the Akamai API calling. (Get from Akamai client credential) - * Refer to [this guidance](https://developer.akamai.com/api/getting-started#authsetup) to create the Akamai client credential for API calling. diff --git a/AkamaiSample/main.js b/AkamaiSample/main.js index b443728..2d48a84 100644 --- a/AkamaiSample/main.js +++ b/AkamaiSample/main.js @@ -1,9 +1,8 @@ const {promisify} = require('bluebird'); -let request = promisify(require('request')); -request = request.defaults({json: true}); const jwtVerify = promisify(require('jsonwebtoken').verify); const jwtDecode = require('jsonwebtoken').decode; -const akamaiHost = "https://api.softlayer.com/rest/v3"; +const EdgeGrid = require('edgegrid'); +const akamaiHost = ""; /** * Get the public key used to verify that the notification payload is generated by your Certificate Manager instance. @@ -35,34 +34,40 @@ const getPublicKey = async (body, certificateManagerApiUrl) => { /** * Add TXT record to domain - * @param zoneId + * @param zoneName zone name * @param payload challenge data * @param userInfo user credentials * @returns {Promise} */ -const addTxtRecord = async (zoneId, payload, userInfo) => { +const addTxtRecord = async (zoneName, payload, userInfo) => { const recordName = payload.challenge.txt_record_name; const recordValue = payload.challenge.txt_record_val; console.log(`Add TXT record "${recordValue}" to zone ${zoneId}`); - const options = { + + var data = '{"name": recordName, "type": "txt", "ttl": 60, "rdata": [recordValue]}'; + + var eg = new EdgeGrid(userInfo.clientToken, userInfo.clientSecret, userInfo.accessToken, `https://"${akamaiHost}"/`); + + eg.auth({ + path: `/config-dns/v2/zones/${zoneName}/names/${recordName}/types/txt`, method: 'POST', - uri: `${akamaiHost}/SoftLayer_Dns_Domain_ResourceRecord`, - auth: {user: userInfo.user, pass: userInfo.apiKey}, - json: {"parameters": [{"host": recordName, "data": recordValue, "ttl": 60, "type": "txt", "domainId": zoneId}]} - }; - let response; - try { - response = await request(options); - } - catch (err) { - console.log(`Couldn't add TXT record "${recordValue}" to zone ${zoneId}. Reason is: ${getErrorString(err)}`); - throw new Error(`Couldn't add TXT record "${recordValue}" to zone ${zoneId}`); - } - if (response.statusCode !== 201) { - console.log(`Couldn't add TXT record "${recordValue}" to zone ${zoneId}. Reason is: status code ${response.statusCode} and body ${JSON.stringify(response.body)}`); - throw new Error(`Couldn't add TXT record "${recordValue}" to zone ${zoneId}`); - } - console.log(`TXT record "${recordValue}" added to zone ${zoneId} successfully.`); + headers: {'Content-Type': 'application/json'}, + body: data + }); + + eg.send(function(error, response, body) { + if (error) { + console.log(`Couldn't add TXT record "${recordValue}" to zone ${zoneName}. Reason is: ${getErrorString(err)}`); + throw new Error(`Couldn't add TXT record "${recordValue}" to zone ${zoneName}`); + } + + if (response.statusCode !== 201) { + console.log(`Couldn't add TXT record "${recordValue}" to zone ${zoneName}. Reason is: status code ${response.statusCode} and body ${JSON.stringify(response.body)}`); + throw new Error(`Couldn't add TXT record "${recordValue}" to zone ${zoneName}`); + } + + console.log(`TXT record "${recordValue}" added to zone ${zoneName} successfully.`); + }); }; /** @@ -165,12 +170,12 @@ const removeTxtRecord = async (recordId, userInfo) => { * @returns {Promise} */ const setChallenge = async (payload, userInfo) => { - console.log(`Set challenge: '${payload.domain} : ${JSON.stringify(payload.challenge)}`); + console.log(`Set Akamai challenge: '${payload.domain} : ${JSON.stringify(payload.challenge)}`); + let domain = payload.domain; //remove wildcard in case its wildcard certificate. domain = domain.replace('*.', ''); - const zoneId = await getZoneIdByDomain(domain, userInfo); - await addTxtRecord(zoneId, payload, userInfo); + await addTxtRecord(domain, payload, userInfo); }; /** @@ -180,7 +185,13 @@ const setChallenge = async (payload, userInfo) => { * @returns {Promise} */ const removeChallenge = async (payload, userInfo) => { - console.log(`Remove challenge: '${payload.domain} : ${JSON.stringify(payload.challenge)}`); + console.log(`Remove Akamai challenge: '${payload.domain} : ${JSON.stringify(payload.challenge)}`); + return { + statusCode: 200, + headers: {'Content-Type': 'application/json'}, + body: {} + }; + let domain = payload.domain; //remove wildcard in case its wildcard certificate. domain = domain.replace('*.', ''); From f32ecbf84b351ff12708984bcbab5b724d3126a1 Mon Sep 17 00:00:00 2001 From: LEI BAO Date: Thu, 1 Jul 2021 18:17:27 +0800 Subject: [PATCH 04/12] Add akamai DNS add and remove functions --- AkamaiSample/main.js | 189 +++++++++++++++----------------------- AkamaiSample/package.json | 7 ++ 2 files changed, 81 insertions(+), 115 deletions(-) create mode 100644 AkamaiSample/package.json diff --git a/AkamaiSample/main.js b/AkamaiSample/main.js index 2d48a84..41547e4 100644 --- a/AkamaiSample/main.js +++ b/AkamaiSample/main.js @@ -2,7 +2,7 @@ const {promisify} = require('bluebird'); const jwtVerify = promisify(require('jsonwebtoken').verify); const jwtDecode = require('jsonwebtoken').decode; const EdgeGrid = require('edgegrid'); -const akamaiHost = ""; +let akamaiHost = ""; /** * Get the public key used to verify that the notification payload is generated by your Certificate Manager instance. @@ -42,11 +42,20 @@ const getPublicKey = async (body, certificateManagerApiUrl) => { const addTxtRecord = async (zoneName, payload, userInfo) => { const recordName = payload.challenge.txt_record_name; const recordValue = payload.challenge.txt_record_val; - console.log(`Add TXT record "${recordValue}" to zone ${zoneId}`); + console.log(`Add TXT record "${recordName}" to zone ${zoneName}`); - var data = '{"name": recordName, "type": "txt", "ttl": 60, "rdata": [recordValue]}'; + var data = { + "name": recordName, + "type": "txt", + "ttl": 60, + "rdata": [recordValue] + }; - var eg = new EdgeGrid(userInfo.clientToken, userInfo.clientSecret, userInfo.accessToken, `https://"${akamaiHost}"/`); + var eg = new EdgeGrid( + userInfo.client_token, + userInfo.client_secret, + userInfo.access_token, + `https://${akamaiHost}`); eg.auth({ path: `/config-dns/v2/zones/${zoneName}/names/${recordName}/types/txt`, @@ -57,110 +66,56 @@ const addTxtRecord = async (zoneName, payload, userInfo) => { eg.send(function(error, response, body) { if (error) { - console.log(`Couldn't add TXT record "${recordValue}" to zone ${zoneName}. Reason is: ${getErrorString(err)}`); - throw new Error(`Couldn't add TXT record "${recordValue}" to zone ${zoneName}`); + console.log(`Couldn't add TXT record "${recordName}" to zone ${zoneName}. Reason is: ${getErrorString(error)}`); + throw new Error(`Couldn't add TXT record "${recordName}" to zone ${zoneName}`); } if (response.statusCode !== 201) { - console.log(`Couldn't add TXT record "${recordValue}" to zone ${zoneName}. Reason is: status code ${response.statusCode} and body ${JSON.stringify(response.body)}`); - throw new Error(`Couldn't add TXT record "${recordValue}" to zone ${zoneName}`); + console.log(`Couldn't add TXT record "${recordName}" to zone ${zoneName}. Reason is: status code ${response.statusCode} and body ${JSON.stringify(body)}`); + throw new Error(`Couldn't add TXT record "${recordName}" to zone ${zoneName}`); } - console.log(`TXT record "${recordValue}" added to zone ${zoneName} successfully.`); + console.log(`TXT record "${recordName}" added to zone ${zoneName} successfully.`); }); }; /** - * Get zone id by domain. - * @param domain - * @param userInfo user credentials - * @returns {Promise} - */ -const getZoneIdByDomain = async (domain, userInfo) => { - console.log(`Get zone id for domain ${domain}`); - const options = { - method: 'GET', - uri: `${akamaiHost}/config-dns/v2/zones?search=${encodeURIComponent(domain)}&showAll=true}`, - auth: {user: userInfo.user, pass: userInfo.apiKey} - }; - let response; - try { - response = await request(options); - } - catch (err) { - console.error(`Couldn't get zone for domain "${domain}". Reason is: ${getErrorString(err)}`); - throw new Error(`Couldn't get zone for domain "${domain}"`); - } - if (response.statusCode !== 200) { - console.error(`Couldn't get zone for domain ${domain}. Reason is: status code "${response.statusCode}" and body ${JSON.stringify(response.body)}`); - throw new Error(`Couldn't get zone for domain "${domain}"`); - } - if (response.body.length === 0) { - console.error(`Couldn't find domain ${domain}.`); - throw new Error(`Couldn't find domain ${domain}`); - } - console.log(`Get zone for domain "${domain}" finished successfully with body: ${JSON.stringify(response.body)}`); - return response.body[0].id; -}; - -/** - * Get challenge DNS TXT record ids - * @param zoneId + * Delete single TXT record from zone. + * @param zoneName zone name * @param payload challenge data * @param userInfo user credentials - * @returns {Promise<[]>} + * @returns {Promise} */ -const getTxtRecords = async (zoneId, payload, userInfo) => { +const removeTxtRecord = async (zoneName, payload, userInfo) => { const recordName = payload.challenge.txt_record_name; - const recordValue = payload.challenge.txt_record_val; - console.log(`Get records named ${recordName} from zone ${zoneId}`); - const options = { - method: 'GET', - uri: `${akamaiHost}/SoftLayer_Dns_Domain/${zoneId}/getResourceRecords?objectFilter={"resourceRecords":{"host":{"operation": "${recordName}"},"data":{"operation": "${recordValue}"}}}`, - auth: {user: userInfo.user, pass: userInfo.apiKey} - }; - let response; - try { - response = await request(options); - } - catch (err) { - console.error(`Couldn't get record named ${recordName}. Reason is: ${getErrorString(err)}`); - throw new Error(`Couldn't get record named ${recordName}`); - } - if (response.statusCode !== 200) { - console.error(`Couldn't get records named ${recordName}. Reason is: status code ${response.statusCode} and body ${JSON.stringify(response.body)}`); - throw new Error(`Couldn't get record named ${recordName}`); - } - console.log(`Get records named ${recordName} returned ${response.body.length} results.`); - return response.body; -}; + console.log(`Delete TXT record "${recordName}" to zone ${zoneName}`); -/** - * Delete single TXT record from zone. - * @param recordId - * @param userInfo user credentials - * @returns {Promise} - */ -const removeTxtRecord = async (recordId, userInfo) => { - console.log(`Delete TXT record "${recordId}"`); - const options = { + var eg = new EdgeGrid( + userInfo.client_token, + userInfo.client_secret, + userInfo.access_token, + `https://${akamaiHost}`); + + eg.auth({ + path: `/config-dns/v2/zones/${zoneName}/names/${recordName}/types/txt`, method: 'DELETE', - uri: `${akamaiHost}/SoftLayer_Dns_Domain_ResourceRecord/${recordId}`, - auth: {user: userInfo.user, pass: userInfo.apiKey} - }; - let response; - try { - response = await request(options); - } - catch (err) { - console.log(`Couldn't delete TXT record "${recordId}". Reason is: ${getErrorString(err)}`); - throw new Error(`Couldn't delete TXT record "${recordId}"`); - } - if (response.statusCode !== 200) { - console.log(`Couldn't delete TXT record "${recordId}". Reason is: status code ${response.statusCode} body ${JSON.stringify(response.body)}`); - throw new Error(`Couldn't delete TXT record "${recordId}"`); - } - console.log(`Delete TXT record "${recordId}" finished successfully.`); + headers: {'Content-Type': 'application/json'}, + body: {} + }); + + eg.send(function(error, response, body) { + if (error) { + console.log(`Couldn't delete TXT record "${recordName}" to zone ${zoneName}. Reason is: ${getErrorString(error)}`); + throw new Error(`Couldn't delete TXT record "${recordName}" to zone ${zoneName}`); + } + + if (response.statusCode !== 204) { + console.log(`Couldn't delete TXT record "${recordName}" to zone ${zoneName}. Reason is: status code ${response.statusCode} and body ${JSON.stringify(body)}`); + throw new Error(`Couldn't delete TXT record "${recordName}" to zone ${zoneName}`); + } + + console.log(`Delete TXT record "${recordName}" to zone ${zoneName} successfully.`); + }); }; /** @@ -186,18 +141,12 @@ const setChallenge = async (payload, userInfo) => { */ const removeChallenge = async (payload, userInfo) => { console.log(`Remove Akamai challenge: '${payload.domain} : ${JSON.stringify(payload.challenge)}`); - return { - statusCode: 200, - headers: {'Content-Type': 'application/json'}, - body: {} - }; let domain = payload.domain; //remove wildcard in case its wildcard certificate. domain = domain.replace('*.', ''); - const zoneId = await getZoneIdByDomain(domain, userInfo); - const records = await getTxtRecords(zoneId, payload, userInfo); - await Promise.all(records.map(r => removeTxtRecord(r.id, userInfo).catch())); + + await removeTxtRecord(domain, payload, userInfo); console.log(`Remove challenge for domain ${domain} finished.`); }; @@ -214,20 +163,28 @@ const main = async (params)=> { console.log("Cloud function invoked."); try { - const body = jwtDecode(params.data); - - // Validate that the notification was sent from a Certificate Manager instance that has allowed access - if (!params.allowedCertificateManagerCRNs || !params.allowedCertificateManagerCRNs[body.instance_crn]) { - console.error(`Certificate Manager instance ${body.instance_crn} is not allowed to invoke this action`); - return Promise.reject({ - statusCode: 403, - headers: {'Content-Type': 'application/json'}, - body: {message: 'Unauthorized'}, - }); - } - const certificateManagerApiUrl = `https://${params.cmRegion}.certificate-manager.cloud.ibm.com`; - const publicKey = await getPublicKey(body, certificateManagerApiUrl); - const decodedNotification = await jwtVerify(params.data, publicKey); + // const body = jwtDecode(params.data); + + // // Validate that the notification was sent from a Certificate Manager instance that has allowed access + // if (!params.allowedCertificateManagerCRNs || !params.allowedCertificateManagerCRNs[body.instance_crn]) { + // console.error(`Certificate Manager instance ${body.instance_crn} is not allowed to invoke this action`); + // return Promise.reject({ + // statusCode: 403, + // headers: {'Content-Type': 'application/json'}, + // body: {message: 'Unauthorized'}, + // }); + // } + // const certificateManagerApiUrl = `https://${params.cmRegion}.certificate-manager.cloud.ibm.com`; + // const publicKey = await getPublicKey(body, certificateManagerApiUrl); + // const decodedNotification = await jwtVerify(params.data, publicKey); + const decodedNotification = { + event_type: params.event_type, + domain: params.domain, + challenge: { + txt_record_name: params.record_name, + txt_record_val: params.record_value + } + }; akamaiHost = params.host; const userInfo = { client_secret: params.client_secret, @@ -270,3 +227,5 @@ const getErrorString = (error) => { else return 'Error undefined'; }; + +exports.main = main; \ No newline at end of file diff --git a/AkamaiSample/package.json b/AkamaiSample/package.json new file mode 100644 index 0000000..31b0999 --- /dev/null +++ b/AkamaiSample/package.json @@ -0,0 +1,7 @@ +{ + "name": "akamai-cert-manager-dns-domain-validation", + "main": "main.js", + "dependencies": { + "edgegrid": "^3.0.8" + } +} From f75019dae7a2465283897442eb5c3384c61b6500 Mon Sep 17 00:00:00 2001 From: LEI BAO Date: Thu, 1 Jul 2021 22:17:48 +0800 Subject: [PATCH 05/12] Add akamai edgegrid packet installation steps --- AkamaiSample/README.md | 54 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/AkamaiSample/README.md b/AkamaiSample/README.md index 98c6af7..8e8a120 100644 --- a/AkamaiSample/README.md +++ b/AkamaiSample/README.md @@ -11,17 +11,57 @@ ### IBM Cloud Function action -1. Create a new [IBM Cloud Function action](https://cloud.ibm.com/docs/openwhisk/index.html#openwhisk_start_hello_world) +1. Clone the sample code - * In IBM Cloud Functions, select **Actions** from the sidebar - * Click on **Create** - * Follow the on-screen instructions +```bash +git clone https://github.com/ibm-cloud-security/certificate-manager-domain-validation-cloud-function-sample +``` -2. Deploy the sample +2. Enter the `AkamaiSmaple` directory - Select **Code** from the sidebar, and paste the contents of the **main.js** file of the sample +```bash +cd AkamaiSample/ +``` -3. [Bind parameters to the action](https://cloud.ibm.com/docs/openwhisk/parameters.html#default-params-action) +3. Install the package + +```bash +npm install npm install package-lock.json +``` + +4. Compress the content + +```bash +zip -r action.zip * +``` + +5. Follow this doc to install the CLI and plug-in, https://cloud.ibm.com/docs/openwhisk?topic=openwhisk-cli_install + +6. Login IBM cloud + +```bash +ibmcloud login --sso +``` + +7. Create a new namespace + +```bash +ibmcloud fn namespace create DNSCertManagerNS +``` + +8. Target to the new namespace + +```bash +ibmcloud fn namespace target DNSCertManagerNS +``` + +9. Create a cloud function action and upload the sample code + +```bash +ibmcloud fn action create AkamaiCertManagerAction action.zip --kind nodejs:12 +``` + +10. [Bind parameters to the action](https://cloud.ibm.com/docs/openwhisk/parameters.html#default-params-action) Select **Parameters** from the sidebar, and add the following: From 3d8da2c1f655313fe2550dd2e3850142b6e67a75 Mon Sep 17 00:00:00 2001 From: LEI BAO Date: Thu, 1 Jul 2021 22:18:31 +0800 Subject: [PATCH 06/12] Add files into gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6fb99ff..ec3b516 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,4 @@ typings/ .next *.iml /.idea +AkamaiSample/action.zip From 97bed7a83168a49562bf329dfa9dc500fc4a170d Mon Sep 17 00:00:00 2001 From: LEI BAO Date: Tue, 6 Jul 2021 11:21:28 +0800 Subject: [PATCH 07/12] Add update function cmd --- AkamaiSample/README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/AkamaiSample/README.md b/AkamaiSample/README.md index 8e8a120..8ae9a34 100644 --- a/AkamaiSample/README.md +++ b/AkamaiSample/README.md @@ -26,7 +26,7 @@ cd AkamaiSample/ 3. Install the package ```bash -npm install npm install package-lock.json +npm install package-lock.json ``` 4. Compress the content @@ -61,6 +61,12 @@ ibmcloud fn namespace target DNSCertManagerNS ibmcloud fn action create AkamaiCertManagerAction action.zip --kind nodejs:12 ``` +And you can also update the code with this cmd: + +```bash +ibmcloud fn action update AkamaiCertManagerAction action.zip --kind nodejs:12 +``` + 10. [Bind parameters to the action](https://cloud.ibm.com/docs/openwhisk/parameters.html#default-params-action) Select **Parameters** from the sidebar, and add the following: From fe9ea05382549622e8abcfa4f7fbc512700039df Mon Sep 17 00:00:00 2001 From: LEI BAO Date: Tue, 6 Jul 2021 11:22:39 +0800 Subject: [PATCH 08/12] Use the await request method to send request to Akamai --- AkamaiSample/main.js | 87 +++++++++++++++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 22 deletions(-) diff --git a/AkamaiSample/main.js b/AkamaiSample/main.js index 41547e4..45dae7e 100644 --- a/AkamaiSample/main.js +++ b/AkamaiSample/main.js @@ -1,4 +1,6 @@ const {promisify} = require('bluebird'); +let request = promisify(require('request')); +// request = request.defaults({json: true}); const jwtVerify = promisify(require('jsonwebtoken').verify); const jwtDecode = require('jsonwebtoken').decode; const EdgeGrid = require('edgegrid'); @@ -64,19 +66,33 @@ const addTxtRecord = async (zoneName, payload, userInfo) => { body: data }); - eg.send(function(error, response, body) { - if (error) { - console.log(`Couldn't add TXT record "${recordName}" to zone ${zoneName}. Reason is: ${getErrorString(error)}`); - throw new Error(`Couldn't add TXT record "${recordName}" to zone ${zoneName}`); - } + // eg.send(function(error, response, body) { + // if (error) { + // console.log(`Couldn't add TXT record "${recordName}" to zone ${zoneName}. Reason is: ${getErrorString(error)}`); + // throw new Error(`Couldn't add TXT record "${recordName}" to zone ${zoneName}`); + // } - if (response.statusCode !== 201) { - console.log(`Couldn't add TXT record "${recordName}" to zone ${zoneName}. Reason is: status code ${response.statusCode} and body ${JSON.stringify(body)}`); - throw new Error(`Couldn't add TXT record "${recordName}" to zone ${zoneName}`); - } + // if (response.statusCode !== 201) { + // console.log(`Couldn't add TXT record "${recordName}" to zone ${zoneName}. Reason is: status code ${response.statusCode} and body ${JSON.stringify(body)}`); + // throw new Error(`Couldn't add TXT record "${recordName}" to zone ${zoneName}`); + // } - console.log(`TXT record "${recordName}" added to zone ${zoneName} successfully.`); - }); + // console.log(`TXT record "${recordName}" added to zone ${zoneName} successfully.`); + // }); + + let response; + try { + response = await request(eg.request); + } + catch (err) { + console.log(`Couldn't add TXT record "${recordName}" to zone ${zoneName}. Reason is: ${getErrorString(error)}`); + throw new Error(`Couldn't add TXT record "${recordName}" to zone ${zoneName}`); + } + if (response.statusCode !== 201) { + console.log(`Couldn't add TXT record "${recordName}" to zone ${zoneName}. Reason is: status code ${response.statusCode} and body ${JSON.stringify(response.body)}`); + throw new Error(`Couldn't add TXT record "${recordName}" to zone ${zoneName}`); + } + console.log(`Delete TXT record "${recordName}" finished successfully.`); }; /** @@ -103,19 +119,34 @@ const removeTxtRecord = async (zoneName, payload, userInfo) => { body: {} }); - eg.send(function(error, response, body) { - if (error) { - console.log(`Couldn't delete TXT record "${recordName}" to zone ${zoneName}. Reason is: ${getErrorString(error)}`); - throw new Error(`Couldn't delete TXT record "${recordName}" to zone ${zoneName}`); - } + // eg.send(function(error, response, body) { + // throw new Error(`Couldn't delete TXT record "${recordName}" to zone ${zoneName}`); + // if (error) { + // console.log(`Couldn't delete TXT record "${recordName}" to zone ${zoneName}. Reason is: ${getErrorString(error)}`); + // throw new Error(`Couldn't delete TXT record "${recordName}" to zone ${zoneName}`); + // } - if (response.statusCode !== 204) { - console.log(`Couldn't delete TXT record "${recordName}" to zone ${zoneName}. Reason is: status code ${response.statusCode} and body ${JSON.stringify(body)}`); - throw new Error(`Couldn't delete TXT record "${recordName}" to zone ${zoneName}`); - } + // if (response.statusCode !== 204 && response.statusCode !== 404) { + // console.log(`Couldn't delete TXT record "${recordName}" to zone ${zoneName}. Reason is: status code ${response.statusCode} and body ${JSON.stringify(body)}`); + // throw new Error(`Couldn't delete TXT record "${recordName}" to zone ${zoneName}`); + // } - console.log(`Delete TXT record "${recordName}" to zone ${zoneName} successfully.`); - }); + // console.log(`Delete TXT record "${recordName}" to zone ${zoneName} successfully.`); + // }); + + let response; + try { + response = await request(eg.request); + } + catch (err) { + console.log(`Couldn't delete TXT record "${recordName}". Reason is: ${getErrorString(err)}`); + throw new Error(`Couldn't delete TXT record "${recordName}"`); + } + if (response.statusCode !== 204 && response.statusCode !== 404) { + console.log(`Couldn't delete TXT record "${recordName}". Reason is: status code ${response.statusCode} body ${JSON.stringify(response.body)}`); + throw new Error(`Couldn't delete TXT record "${recordName}"`); + } + console.log(`Delete TXT record "${recordName}" finished successfully.`); }; /** @@ -131,6 +162,7 @@ const setChallenge = async (payload, userInfo) => { //remove wildcard in case its wildcard certificate. domain = domain.replace('*.', ''); await addTxtRecord(domain, payload, userInfo); + console.log(`Add challenge for domain ${domain} finished.`); }; /** @@ -161,6 +193,17 @@ const removeChallenge = async (payload, userInfo) => { */ const main = async (params)=> { console.log("Cloud function invoked."); + // const params = { + // host: "akab-b44g4bn7hhe422bf-tk2nbacgi42fu6fs.luna.akamaiapis.net", + // event_type: "cert_domain_validation_required", + // domain: "cdn-demo.com", + // record_name: "text1.cdn-demo.com", + // record_value: "text1", + // client_secret: "096SIjUzu41XRD/JRKNzfw8uzrWlVn3P3zRzyQjemzw=", + // access_token: "akab-uhgiaytcrvjao6mx-47lm5aqb3wygrje5", + // client_token: "akab-lyqudahrqrw57kko-lfx4rm3yf3xw2au6" + // } + try { // const body = jwtDecode(params.data); From 3aa0bc35fb32941542ead0280eb4dc893ba3c829 Mon Sep 17 00:00:00 2001 From: LEI BAO Date: Tue, 6 Jul 2021 11:23:03 +0800 Subject: [PATCH 09/12] Add multiple packets --- AkamaiSample/package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/AkamaiSample/package.json b/AkamaiSample/package.json index 31b0999..4c266cc 100644 --- a/AkamaiSample/package.json +++ b/AkamaiSample/package.json @@ -2,6 +2,9 @@ "name": "akamai-cert-manager-dns-domain-validation", "main": "main.js", "dependencies": { - "edgegrid": "^3.0.8" + "bluebird": "^3.7.2", + "edgegrid": "^3.0.8", + "jsonwebtoken": "^8.5.1", + "package-lock.json": "^1.0.0" } } From a4048ebc4485bd756e4cc696ed493cbe21370cf3 Mon Sep 17 00:00:00 2001 From: LEI BAO Date: Tue, 6 Jul 2021 12:47:47 +0800 Subject: [PATCH 10/12] Add duplicated logic --- AkamaiSample/main.js | 65 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/AkamaiSample/main.js b/AkamaiSample/main.js index 45dae7e..0676868 100644 --- a/AkamaiSample/main.js +++ b/AkamaiSample/main.js @@ -34,6 +34,63 @@ const getPublicKey = async (body, certificateManagerApiUrl) => { return response.body.publicKey; }; +/** + * Get TXT record to domain + * @param zoneName zone name + * @param payload challenge data + * @param userInfo user credentials + * @returns {Promise} + */ + const getTxtRecord = async (zoneName, payload, userInfo) => { + const recordName = payload.challenge.txt_record_name; + console.log(`Get TXT record "${recordName}" to zone ${zoneName}`); + + var eg = new EdgeGrid( + userInfo.client_token, + userInfo.client_secret, + userInfo.access_token, + `https://${akamaiHost}`); + + eg.auth({ + path: `/config-dns/v2/zones/${zoneName}/names/${recordName}/types/txt`, + method: 'GET', + headers: {'Content-Type': 'application/json'}, + }); + + // eg.send(function(error, response, body) { + // if (error) { + // console.log(`Couldn't get TXT record "${recordName}" to zone ${zoneName}. Reason is: ${getErrorString(error)}`); + // throw new Error(`Couldn't get TXT record "${recordName}" to zone ${zoneName}`); + // } + + // if (response.statusCode !== 201) { + // console.log(`Couldn't get TXT record "${recordName}" to zone ${zoneName}. Reason is: status code ${response.statusCode} and body ${JSON.stringify(body)}`); + // throw new Error(`Couldn't get TXT record "${recordName}" to zone ${zoneName}`); + // } + + // console.log(`Get TXT record "${recordName}" to zone ${zoneName} successfully.`); + // }); + + let response; + let ret; + try { + response = await request(eg.request); + } + catch (err) { + console.error(`Couldn't get record named ${recordName}. Reason is: ${getErrorString(err)}`); + throw new Error(`Couldn't get record named ${recordName}`); + } if (response.statusCode == 200) { + ret = response.body; + } else if (response.statusCode == 404) { + ret = []; + } else { + console.error(`Couldn't get records named ${recordName}. Reason is: status code ${response.statusCode} and body ${JSON.stringify(response.body)}`); + throw new Error(`Couldn't get record named ${recordName}`); + } + console.log(`Get records named ${recordName} returned ${ret}.`); + return ret; +}; + /** * Add TXT record to domain * @param zoneName zone name @@ -44,7 +101,7 @@ const getPublicKey = async (body, certificateManagerApiUrl) => { const addTxtRecord = async (zoneName, payload, userInfo) => { const recordName = payload.challenge.txt_record_name; const recordValue = payload.challenge.txt_record_val; - console.log(`Add TXT record "${recordName}" to zone ${zoneName}`); + console.log(`Add TXT record "${recordName}:${recordValue}" to zone ${zoneName}`); var data = { "name": recordName, @@ -92,7 +149,7 @@ const addTxtRecord = async (zoneName, payload, userInfo) => { console.log(`Couldn't add TXT record "${recordName}" to zone ${zoneName}. Reason is: status code ${response.statusCode} and body ${JSON.stringify(response.body)}`); throw new Error(`Couldn't add TXT record "${recordName}" to zone ${zoneName}`); } - console.log(`Delete TXT record "${recordName}" finished successfully.`); + console.log(`Add TXT record "${recordName}" finished successfully.`); }; /** @@ -161,6 +218,10 @@ const setChallenge = async (payload, userInfo) => { let domain = payload.domain; //remove wildcard in case its wildcard certificate. domain = domain.replace('*.', ''); + record = await getTxtRecord(domain, payload, userInfo); + if (record.length !== 0) { + await removeTxtRecord(domain, payload, userInfo); + } await addTxtRecord(domain, payload, userInfo); console.log(`Add challenge for domain ${domain} finished.`); }; From f34c4d402be802b7e8bf336aa4c0ac73219ffa89 Mon Sep 17 00:00:00 2001 From: LEI BAO Date: Tue, 6 Jul 2021 12:49:27 +0800 Subject: [PATCH 11/12] Remove unused code --- AkamaiSample/main.js | 93 +++++++------------------------------------- 1 file changed, 13 insertions(+), 80 deletions(-) diff --git a/AkamaiSample/main.js b/AkamaiSample/main.js index 0676868..e8204a3 100644 --- a/AkamaiSample/main.js +++ b/AkamaiSample/main.js @@ -57,20 +57,6 @@ const getPublicKey = async (body, certificateManagerApiUrl) => { headers: {'Content-Type': 'application/json'}, }); - // eg.send(function(error, response, body) { - // if (error) { - // console.log(`Couldn't get TXT record "${recordName}" to zone ${zoneName}. Reason is: ${getErrorString(error)}`); - // throw new Error(`Couldn't get TXT record "${recordName}" to zone ${zoneName}`); - // } - - // if (response.statusCode !== 201) { - // console.log(`Couldn't get TXT record "${recordName}" to zone ${zoneName}. Reason is: status code ${response.statusCode} and body ${JSON.stringify(body)}`); - // throw new Error(`Couldn't get TXT record "${recordName}" to zone ${zoneName}`); - // } - - // console.log(`Get TXT record "${recordName}" to zone ${zoneName} successfully.`); - // }); - let response; let ret; try { @@ -123,20 +109,6 @@ const addTxtRecord = async (zoneName, payload, userInfo) => { body: data }); - // eg.send(function(error, response, body) { - // if (error) { - // console.log(`Couldn't add TXT record "${recordName}" to zone ${zoneName}. Reason is: ${getErrorString(error)}`); - // throw new Error(`Couldn't add TXT record "${recordName}" to zone ${zoneName}`); - // } - - // if (response.statusCode !== 201) { - // console.log(`Couldn't add TXT record "${recordName}" to zone ${zoneName}. Reason is: status code ${response.statusCode} and body ${JSON.stringify(body)}`); - // throw new Error(`Couldn't add TXT record "${recordName}" to zone ${zoneName}`); - // } - - // console.log(`TXT record "${recordName}" added to zone ${zoneName} successfully.`); - // }); - let response; try { response = await request(eg.request); @@ -176,21 +148,6 @@ const removeTxtRecord = async (zoneName, payload, userInfo) => { body: {} }); - // eg.send(function(error, response, body) { - // throw new Error(`Couldn't delete TXT record "${recordName}" to zone ${zoneName}`); - // if (error) { - // console.log(`Couldn't delete TXT record "${recordName}" to zone ${zoneName}. Reason is: ${getErrorString(error)}`); - // throw new Error(`Couldn't delete TXT record "${recordName}" to zone ${zoneName}`); - // } - - // if (response.statusCode !== 204 && response.statusCode !== 404) { - // console.log(`Couldn't delete TXT record "${recordName}" to zone ${zoneName}. Reason is: status code ${response.statusCode} and body ${JSON.stringify(body)}`); - // throw new Error(`Couldn't delete TXT record "${recordName}" to zone ${zoneName}`); - // } - - // console.log(`Delete TXT record "${recordName}" to zone ${zoneName} successfully.`); - // }); - let response; try { response = await request(eg.request); @@ -254,47 +211,23 @@ const removeChallenge = async (payload, userInfo) => { */ const main = async (params)=> { console.log("Cloud function invoked."); - // const params = { - // host: "akab-b44g4bn7hhe422bf-tk2nbacgi42fu6fs.luna.akamaiapis.net", - // event_type: "cert_domain_validation_required", - // domain: "cdn-demo.com", - // record_name: "text1.cdn-demo.com", - // record_value: "text1", - // client_secret: "096SIjUzu41XRD/JRKNzfw8uzrWlVn3P3zRzyQjemzw=", - // access_token: "akab-uhgiaytcrvjao6mx-47lm5aqb3wygrje5", - // client_token: "akab-lyqudahrqrw57kko-lfx4rm3yf3xw2au6" - // } try { - // const body = jwtDecode(params.data); + const body = jwtDecode(params.data); - // // Validate that the notification was sent from a Certificate Manager instance that has allowed access - // if (!params.allowedCertificateManagerCRNs || !params.allowedCertificateManagerCRNs[body.instance_crn]) { - // console.error(`Certificate Manager instance ${body.instance_crn} is not allowed to invoke this action`); - // return Promise.reject({ - // statusCode: 403, - // headers: {'Content-Type': 'application/json'}, - // body: {message: 'Unauthorized'}, - // }); - // } - // const certificateManagerApiUrl = `https://${params.cmRegion}.certificate-manager.cloud.ibm.com`; - // const publicKey = await getPublicKey(body, certificateManagerApiUrl); - // const decodedNotification = await jwtVerify(params.data, publicKey); - const decodedNotification = { - event_type: params.event_type, - domain: params.domain, - challenge: { - txt_record_name: params.record_name, - txt_record_val: params.record_value - } - }; - akamaiHost = params.host; - const userInfo = { - client_secret: params.client_secret, - access_token: params.access_token, - client_token: params.client_token - }; + // Validate that the notification was sent from a Certificate Manager instance that has allowed access + if (!params.allowedCertificateManagerCRNs || !params.allowedCertificateManagerCRNs[body.instance_crn]) { + console.error(`Certificate Manager instance ${body.instance_crn} is not allowed to invoke this action`); + return Promise.reject({ + statusCode: 403, + headers: {'Content-Type': 'application/json'}, + body: {message: 'Unauthorized'}, + }); + } + const certificateManagerApiUrl = `https://${params.cmRegion}.certificate-manager.cloud.ibm.com`; + const publicKey = await getPublicKey(body, certificateManagerApiUrl); + const decodedNotification = await jwtVerify(params.data, publicKey); console.log(`Notification message body: ${JSON.stringify(decodedNotification)}`); switch (decodedNotification.event_type) { From 83ddf39ae65e69d45f610022de1529aefb9bd3d2 Mon Sep 17 00:00:00 2001 From: LEI BAO Date: Fri, 9 Jul 2021 11:21:55 +0800 Subject: [PATCH 12/12] Update the record if it's already existed --- AkamaiSample/main.js | 54 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/AkamaiSample/main.js b/AkamaiSample/main.js index e8204a3..8a1e7ab 100644 --- a/AkamaiSample/main.js +++ b/AkamaiSample/main.js @@ -124,6 +124,54 @@ const addTxtRecord = async (zoneName, payload, userInfo) => { console.log(`Add TXT record "${recordName}" finished successfully.`); }; +/** + * Update TXT record to domain + * @param zoneName zone name + * @param payload challenge data + * @param userInfo user credentials + * @returns {Promise} + */ + const updateTxtRecord = async (zoneName, payload, userInfo) => { + const recordName = payload.challenge.txt_record_name; + const recordValue = payload.challenge.txt_record_val; + console.log(`Update TXT record "${recordName}" to zone ${zoneName}`); + + var data = { + "name": recordName, + "type": "txt", + "ttl": 60, + "rdata": [recordValue] + }; + + var eg = new EdgeGrid( + userInfo.client_token, + userInfo.client_secret, + userInfo.access_token, + `https://${akamaiHost}`); + + eg.auth({ + path: `/config-dns/v2/zones/${zoneName}/names/${recordName}/types/txt`, + method: 'PUT', + headers: {'Content-Type': 'application/json'}, + body: data + }); + + let response; + try { + // console.log(eg); + response = await request(eg.request); + } + catch (err) { + console.log(`Couldn't update TXT record "${recordName}" to zone ${zoneName}. Reason is: ${getErrorString(error)}`); + throw new Error(`Couldn't update TXT record "${recordName}" to zone ${zoneName}`); + } + if (response.statusCode !== 200) { + console.log(`Couldn't update TXT record "${recordName}" to zone ${zoneName}. Reason is: status code ${response.statusCode} and body ${JSON.stringify(response.body)}`); + throw new Error(`Couldn't update TXT record "${recordName}" to zone ${zoneName}`); + } + console.log(`Update TXT record "${recordName}" finished successfully.`); +}; + /** * Delete single TXT record from zone. * @param zoneName zone name @@ -177,9 +225,11 @@ const setChallenge = async (payload, userInfo) => { domain = domain.replace('*.', ''); record = await getTxtRecord(domain, payload, userInfo); if (record.length !== 0) { - await removeTxtRecord(domain, payload, userInfo); + //await removeTxtRecord(domain, payload, userInfo); + await updateTxtRecord(domain, payload, userInfo); + } else { + await addTxtRecord(domain, payload, userInfo); } - await addTxtRecord(domain, payload, userInfo); console.log(`Add challenge for domain ${domain} finished.`); };