[ad_1]
That is the second weblog in our sequence about writing software program for ChatOps. Within the first publish of this ChatOps sequence, we constructed a Webex bot that obtained and logged messages to its working console. In this publish, we’ll stroll via find out how to safe your Webex bot with authentication and authorization. Securing a Webex bot on this manner will enable us to really feel extra assured in our deployment as we transfer on to including extra advanced options.
[Access the complete code for this post on GitHub here.]
Crucial: This publish picks up proper the place the primary weblog on this ChatOps sequence left off. You’ll want to learn the primary publish of our ChatOps sequence to discover ways to make your native growth setting publicly accessible in order that Webex webhook occasions can attain your API. Be sure your tunnel is up and working and webhook occasions can move via to your API efficiently earlier than continuing on to the following part. From right here on out, this publish assumes that you just’ve taken these steps and have a profitable end-to-end knowledge move. [You can find the code from the first post on how to build a Webex bot here.]
Have to catch up? Learn the primary a part of Colin Lacy’s ChatOps sequence: “ChatOps: The way to Construct Your First Webex Bot”
The way to safe your Webex bot with an authentication verify
Webex employs HMAC-SHA1 encryption based mostly on a secret key which you could present, so as to add safety to your service endpoint. For the needs of this weblog publish, I’ll embody that within the net service code as an Specific middleware operate, which might be utilized to all routes. This manner, it is going to be checked earlier than some other route handler known as. In your setting, you may add this to your API gateway (or no matter is powering your setting’s ingress, e.g. Nginx, or an OPA coverage).
The way to add a secret to the Webhook
Use your most well-liked device to generate a random, distinctive, and sophisticated string. Ensure that it’s lengthy and advanced sufficient to be tough to guess. There are many instruments obtainable to create a key. Since I’m on a Mac, I used the next command:
$ cat /dev/urandom | base64 | tr -dc '0-9a-zA-Z' | head -c30
The ensuing string was printed into my Shell window. You’ll want to maintain onto it. You’ll use it in a number of locations within the subsequent few steps.
Now you should use that string to replace your Webhook with a PUT request. You may as well add it to a brand new Webhook in the event you’d wish to DELETE your previous one:

Webex will now ship an extra header with every notification request beneath the header key x-spark-spark-signature. The header worth might be a one-way encryption of the POST physique, carried out with the key worth that you just offered. On the server facet, we will try the identical one-way encryption. If the API shopper sending the POST request (ideally Webex) used the identical encryption secret that we used, then our ensuing string ought to match the x-spark-spark-signature header worth.
The way to add an software configuration
Now that issues are beginning to get extra advanced, let’s construct out an software alongside the traces of what we will anticipate to see in the actual world. First, we create a easy (however extensible) AppConfig class in config/appConfig.js. We’ll use this to tug in setting variables after which reference these values in different components of our code. For now, it’ll simply embody the three variables wanted to energy authentication:
- the key that we added to our Webhook
- the header key the place we’ll study the encrypted worth of the POST physique
- the encryption algorithm used, which on this case is ”sha1”
Right here’s the code for the AppConfig class, which we’ll add as our code will get extra advanced:
// in config/appConfig.js import course of from 'course of'; export class AppConfig { constructor() { this.encryptionSecret = course of.env['WEBEX_ENCRYPTION_SECRET']; this.encryptionAlgorithm = course of.env['WEBEX_ENCRYPTION_ALGO']; this.encryptionHeader = course of.env['WEBEX_ENCRYPTION_HEADER']; } }
Tremendous vital: You’ll want to populate these setting variables in your growth setting. Skipping this step can lead to a couple minutes of frustration earlier than remembering to populate these values.
Now we will create an Auth service class that can expose a technique to run our encrypted string comparability:
// in companies/Auth.js import crypto from "crypto"; export class Auth { constructor(appConfig) { this.encryptionSecret = appConfig.encryptionSecret; this.encryptionAlgorithm = appConfig.encryptionAlgorithm; } isProperlyEncrypted(signedValue, messsageBody) { // create an encryption stream const hmac = crypto.createHmac(this.encryptionAlgorithm, this.encryptionSecret); // write the POST physique into the encryption stream hmac.write(JSON.stringify(messsageBody)); // shut the stream to make its ensuing string readable hmac.finish(); // learn the encrypted worth const hash = hmac.learn().toString('hex'); // examine the freshly encrypted worth to the POST header worth, // and return the consequence return hash === signedValue; } }
Fairly simple, proper? Now we have to leverage this methodology in a router middleware that can verify all incoming requests for authentication. If the authentication verify doesn’t cross, the service will return a 401 and reply instantly. I do that in a brand new file referred to as routes/auth.js:
// in routes/auth.js import specific from 'specific' import {AppConfig} from '../config/AppConfig.js'; import {Auth} from "../companies/Auth.js"; const router = specific.Router(); const config = new AppConfig(); const auth = new Auth(config); router.all('/*', async (req, res, subsequent) => { // a comfort reference to the POST physique const messageBody = req.physique; // a comfort reference to the encrypted string, with a fallback if the worth isn’t set const signedValue = req.headers[config.encryptionHeader] || ""; // name the authentication verify const isProperlyEncrypted = auth.isProperlyEncrypted(signedValue, messageBody); if(!isProperlyEncrypted) { res.statusCode = 401; res.ship("Entry denied"); } subsequent(); }); export default router;
All that’s left to do is so as to add this router into the Specific software, simply earlier than the handler that we outlined earlier. Failing the authentication verify will finish the request’s move via the service logic earlier than it ever will get to some other route handlers. If the verify does cross, then the request can proceed on to the following route handler:
// in app.js import specific from 'specific'; import logger from 'morgan'; // ***ADD THE AUTH ROUTER IMPORT*** import authRouter from './routes/auth.js'; import indexRouter from './routes/index.js'; // skipping among the boilerplate… // ***ADD THE AUTH ROUTER TO THE APP*** app.use(authRouter); app.use('/', indexRouter); // the remainder of the file stays the identical…
Now in the event you run your server once more, you may take a look at out your authentication verify. You possibly can strive with only a easy POST from a neighborhood cURL or Postman request. Right here’s a cURL command that I used to check it in opposition to my native service:
$ curl --location --request POST 'localhost:3000' --header 'x-spark-signature: incorrect-value' --header 'Content material-Kind: software/json' --data-raw '{ "key": "worth" }'
Working that very same request in Postman produces the next output:

Now, in the event you ship a message to your bot via Webex, you need to see the Webhook occasion move via your authentication verify and into the route handler that we created within the first publish.
The way to add elective authorization
At this level, we will relaxation assured that any request that comes via got here from Webex. However that doesn’t imply we’re carried out with safety! We’d wish to limit which customers in Webex can name our bot by mentioning it in a Webex Room. If that’s the case, we have to add an authorization verify as properly.
The way to verify in opposition to an inventory of approved customers
Webex sends consumer data with every occasion notification, indicating the Webex consumer ID and the corresponding electronic mail handle of the one that triggered the occasion (an instance is displayed within the first publish on this sequence). Within the case of a message creation occasion, that is the one that wrote the message about which our net service is notified. There are dozens of the way to verify for authorization – AD teams, AWS Cognito integrations, and so forth.
For simplicity’s sake, on this demo service, I’m simply utilizing a hard-coded record of accepted electronic mail addresses that I’ve added to the Auth service constructor, and a easy public methodology to verify the e-mail handle that Webex offered within the POST physique in opposition to that hard-coded record. Different, extra sophisticated modes of authz checks are past the scope of this publish.
// in companies/Auth.js export class Auth { constructor(appConfig) { this.encryptionSecret = appConfig.encryptionSecret; this.encryptionAlgorithm = appConfig.encryptionAlgorithm; // ADDING AUTHORIZED USERS this.authorizedUsers = [ "colacy@cisco.com" // hey, that’s me! ]; } // ADDING AUTHZ CHECK METHOD isUserAuthorized(messageBody) { return this.authorizedUsers.indexOf(messageBody.knowledge.personEmail) !== -1 } // the remainder of the category is unchanged
Similar to with the authentication verify, we have to add this to our routes/auth.js handler. We’ll add this between the authentication verify and the subsequent() name that completes the route handler.
// in routes/auth.js // … const isProperlyEncrypted = auth.isProperlyEncrypted(signedValue, messageBody); if(!isProperlyEncrypted) { res.statusCode = 401; res.ship("Entry denied"); return; } // ADD THE AUTHORIZATION CHECK const isAuthorized = auth.isUserAuthorized(messageBody); if(!isAuthorized) { res.statusCode = 403; res.ship("Unauthorized"); return; } subsequent(); // …
If the sender’s electronic mail handle isn’t in that record, the bot will ship a 403 again to the API shopper with a message that the consumer was unauthorized. However that doesn’t actually let the consumer know what went flawed, does it?
Person Suggestions
If the consumer is unauthorized, we must always allow them to know in order that they aren’t beneath the wrong assumption that their request was profitable — or worse, questioning why nothing occurred. On this scenario, the one manner to offer the consumer with that suggestions is to reply within the Webex Room the place they posted their message to the bot.
Creating messages on Webex is finished with POST requests to the Webex API. [The documentation and the data schema can be found here.] Bear in mind, the bot authenticates with the entry token that was offered again after we created it within the first publish. We’ll have to cross that in as a brand new setting variable into our AppConfig class:
// in config/AppConfig.js export class AppConfig { constructor() { // ADD THE BOT'S TOKEN this.botToken = course of.env['WEBEX_BOT_TOKEN']; this.encryptionSecret = course of.env['WEBEX_ENCRYPTION_SECRET']; this.encryptionAlgorithm = course of.env['WEBEX_ENCRYPTION_ALGO']; this.encryptionHeader = course of.env['WEBEX_ENCRYPTION_HEADER']; } }
Now we will begin a brand new service class, WebexNotifications, in a brand new file referred to as companies/WebexNotifications.js, which is able to notify our customers of what’s occurring within the backend.
// in companies/WebexNotifications.js export class WebexNotifications { constructor(appConfig) { this.botToken = appConfig.botToken; } // new strategies to go right here }
This class is fairly sparse. For the needs of this demo, we’ll hold it that manner. We simply want to present our customers suggestions based mostly on whether or not or not their request was profitable. That may be carried out with a single methodology, applied in our two routers; one to point authorization failures and the opposite to point profitable end-to-end messaging.
A notice on the code beneath: To remain future-proof, Iʼm utilizing the NodeJS model 17.7, which has fetch enabled utilizing the execution flag –experimental-fetch. When you’ve got an older model of NodeJS, you should use a third-party HTTP request library, like axios, and use that rather than any traces the place you see fetch used.
Weʼll begin by implementing the sendNotification methodology, which is able to take the identical messageBody object that weʼre utilizing for our auth checks:
// in companies/WebexNotifications.js… // contained in the WebexNotifications.js class async sendNotification(messageBody, success=false) { // we'll begin a response by tagging the one that created the message let responseToUser = `<@personEmail:${messageBody.knowledge.personEmail}>`; // decide if the notification is being despatched because of a profitable or failed authz verify if (success === false) { responseToUser += ` Uh oh! You are not approved to make requests.`; } else { responseToUser += ` Thanks on your message!`; } // ship a message creation request on behalf of the bot const res = await fetch("https://webexapis.com/v1/messages", { headers: { "Content material-Kind": "software/json", "Authorization": `Bearer ${this.botToken}` }, methodology: "POST", physique: JSON.stringify({ roomId: messageBody.knowledge.roomId, markdown: responseToUser }) }); return res.json(); }
Now it’s only a matter of calling this methodology from inside our route handlers. In routes/auth.js we’ll name it within the occasion of an authorization failure:
// in routes/auth.js import specific from 'specific' import {AppConfig} from '../config/AppConfig.js'; import {Auth} from "../companies/Auth.js"; // ADD THE WEBEXNOTIFICATIONS IMPORT import {WebexNotifications} from '../companies/WebexNotifications.js'; // … const auth = new Auth(config); // ADD CLASS INSTANTIATION const webex = new WebexNotifications(config); // ... if(!isAuthorized) { res.statusCode = 403; res.ship("Unauthorized"); // ADD THE FAILURE NOTIFICATION await webex.sendNotification(messageBody, false); return; } // ...
Equally, we’ll add the success model of this methodology name to routes/index.js. Right here’s the ultimate model of routes/index.js as soon as we’ve added a number of extra traces like we did within the auth route:
// in routes/index.js import specific from 'specific' // Add the AppConfig import import {AppConfig} from '../config/AppConfig.js'; // Add the WebexNotifications import import {WebexNotifications} from '../companies/WebexNotifications.js'; const router = specific.Router(); // instantiate the AppConfig const config = new AppConfig(); // instantiate the WebexNotification class, passing in our app config const webex = new WebexNotifications(config); router.publish('/', async operate(req, res) { console.log(`Obtained a POST`, req.physique); res.statusCode = 201; res.finish(); await webex.sendNotification(req.physique, true); }); export default router;
To check this out, I’ll merely remark out my very own electronic mail handle from the accepted record after which ship a message to my bot. As you may see from the screenshot beneath, Webex will show the notification from the code above, indicating that I’m not allowed to make the request.

If I un-comment my electronic mail handle, the request goes via efficiently.

Conclusion
Wow, that coated so much in a brief quantity of code. Now we have now an end-to-end working setup for ChatOps, with built-in safety. We are able to begin interested by all of the completely different workflows that we’d wish to allow for issues like:
- DevOps automation
- Function-based workflows
- Agile practices for distant groups
- And anything you may consider!
That is solely the second in a sequence of weblog posts about ChatOps. Now that we have now secured our Webex bot, we have now an excellent jump-off level for constructing advanced workflows that may automate numerous downstream processes. As we cowl extra subjects, we’ll publish these right here, so verify again usually!
Observe Cisco Studying & Certifications
Twitter, Fb, LinkedIn and Instagram.
Share:
[ad_2]
