This repository has been archived by the owner on Jan 25, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9
/
main.js
318 lines (286 loc) · 11.1 KB
/
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
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');
let akamaiHost = "";
/**
* 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<publicKey>}
*/
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;
};
/**
* Get TXT record to domain
* @param zoneName zone name
* @param payload challenge data
* @param userInfo user credentials
* @returns {Promise<void>}
*/
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'},
});
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
* @param payload challenge data
* @param userInfo user credentials
* @returns {Promise<void>}
*/
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}:${recordValue}" 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: 'POST',
headers: {'Content-Type': 'application/json'},
body: data
});
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(`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<void>}
*/
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
* @param payload challenge data
* @param userInfo user credentials
* @returns {Promise<void>}
*/
const removeTxtRecord = async (zoneName, payload, userInfo) => {
const recordName = payload.challenge.txt_record_name;
console.log(`Delete 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: 'DELETE',
headers: {'Content-Type': 'application/json'},
body: {}
});
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.`);
};
/**
* Set the challenge .
* @param payload notification with challenge
* @param userInfo user credentials
* @returns {Promise<void>}
*/
const setChallenge = async (payload, userInfo) => {
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('*.', '');
record = await getTxtRecord(domain, payload, userInfo);
if (record.length !== 0) {
//await removeTxtRecord(domain, payload, userInfo);
await updateTxtRecord(domain, payload, userInfo);
} else {
await addTxtRecord(domain, payload, userInfo);
}
console.log(`Add challenge for domain ${domain} finished.`);
};
/**
* Remove TXT record of challenge
* @param payload
* @param userInfo user credentials
* @returns {Promise<void>}
*/
const removeChallenge = async (payload, userInfo) => {
console.log(`Remove Akamai challenge: '${payload.domain} : ${JSON.stringify(payload.challenge)}`);
let domain = payload.domain;
//remove wildcard in case its wildcard certificate.
domain = domain.replace('*.', '');
await removeTxtRecord(domain, payload, userInfo);
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);
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';
};
exports.main = main;