Tuesday, May 3, 2022
HomeHealth CareChatOps: Methods to Safe Your Webex Bot

ChatOps: Methods to Safe Your Webex Bot

[ad_1]

That is the second weblog in our collection about writing software program for ChatOps. Within the first submit of this ChatOps collection, we constructed a Webex bot that obtained and logged messages to its working console. In this submit, we’ll stroll by way of methods to safe your Webex bot with authentication and authorization. Securing a Webex bot on this method will permit us to really feel extra assured in our deployment as we transfer on to including extra complicated options.

[Access the complete code for this post on GitHub here.]

Essential: This submit picks up proper the place the primary weblog on this ChatOps collection left off. You should definitely learn the primary submit of our ChatOps collection to discover ways to make your native growth setting publicly accessible in order that Webex webhook occasions can attain your API. Be sure that your tunnel is up and working and webhook occasions can circulate by way of to your API efficiently earlier than continuing on to the following part. From right here on out, this submit assumes that you simply’ve taken these steps and have a profitable end-to-end information circulate. [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 collection: “ChatOps: Methods to Construct Your First Webex Bot” 

Methods to safe your Webex bot with an authentication verify

Webex employs HMAC-SHA1 encryption based mostly on a secret key that you would be able to present, so as to add safety to your service endpoint. For the needs of this weblog submit, I’ll embody that within the internet service code as an Specific middleware perform, which can be utilized to all routes. This manner, it will likely be checked earlier than every other route handler is named. In your setting, you would possibly add this to your API gateway (or no matter is powering your setting’s ingress, e.g. Nginx, or an OPA coverage).

Methods to add a secret to the Webhook

Use your most well-liked software to generate a random, distinctive, and complicated string. Be sure that it’s lengthy and complicated sufficient to be tough to guess. There are many instruments out there 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 should definitely maintain onto it. You’ll use it in a couple of locations within the subsequent few steps.

Now you should utilize that string to replace your Webhook with a PUT request. You can even add it to a brand new Webhook when you’d prefer to DELETE your outdated one:

Adding a Secret to the Webhook

Webex will now ship an extra header with every notification request below the header key x-spark-spark-signature. The header worth can be a one-way encryption of the POST physique, carried out with the key worth that you simply offered. On the server aspect, we will try the identical one-way encryption. If the API consumer 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.

Methods to add an software configuration

Now that issues are beginning to get extra complicated, let’s construct out an software alongside the traces of what we will count on to see in the true world. First, we create a easy (however extensible) AppConfig class in config/appConfig.js. We’ll use this to drag in setting variables after which reference these values in different elements 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 look at 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 complicated:

// 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 essential: You should definitely 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 may expose a way to run our encrypted string comparability:

// in providers/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');
        // evaluate 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 technique in a router middleware that may 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 known as routes/auth.js:

// in routes/auth.js
import specific from 'specific'
import {AppConfig} from '../config/AppConfig.js';
import {Auth} from "../providers/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 circulate by way of the service logic earlier than it ever will get to every 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 when you run your server once more, you possibly can take a look at out your authentication verify. You may 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 towards my native service:

$ curl --location --request POST 'localhost:3000' 
--header 'x-spark-signature: incorrect-value' 
--header 'Content material-Sort: software/json' 
--data-raw '{
    "key": "worth"
}'

Operating that very same request in Postman produces the next output:

Postman request adding application configuration

Now, when you ship a message to your bot by way of Webex, it is best to see the Webhook occasion circulate by way of your authentication verify and into the route handler that we created within the first submit.

Methods to add optionally available authorization

At this level, we will relaxation assured that any request that comes by way of got here from Webex. However that doesn’t imply we’re carried out with safety! We’d need 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 nicely.

Methods to verify towards a listing of approved customers

Webex sends consumer info with every occasion notification, indicating the Webex consumer ID and the corresponding electronic mail deal with of the one who triggered the occasion (an instance is displayed within the first submit on this collection). Within the case of a message creation occasion, that is the one who wrote the message about which our internet service is notified. There are dozens of how to verify for authorization – AD teams, AWS Cognito integrations, and many others.

For simplicity’s sake, on this demo service, I’m simply utilizing a hard-coded listing of accepted electronic mail addresses that I’ve added to the Auth service constructor, and a easy public technique to verify the e-mail deal with that Webex offered within the POST physique towards that hard-coded listing. Different, extra difficult modes of authz checks are past the scope of this submit.

// in providers/Auth.js
export class Auth {
    constructor(appConfig) {
        this.encryptionSecret = appConfig.encryptionSecret;
        this.encryptionAlgorithm = appConfig.encryptionAlgorithm;
        // ADDING AUTHORIZED USERS
        this.authorizedUsers = [
           "[email protected]" // hey, that’s me!
        ];
    }

    // ADDING AUTHZ CHECK METHOD
    isUserAuthorized(messageBody) {
        return this.authorizedUsers.indexOf(messageBody.information.personEmail) 
!== -1
    }
// the remainder of the category is unchanged

Identical 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 deal with isn’t in that listing, the bot will ship a 403 again to the API consumer with a message that the consumer was unauthorized. However that doesn’t actually let the consumer know what went fallacious, does it?

Consumer Suggestions

If the consumer is unauthorized, we should always allow them to know in order that they aren’t below the wrong assumption that their request was profitable — or worse, questioning why nothing occurred. On this scenario, the one method to supply 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 completed 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 submit. We’ll must 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 known as providers/WebexNotifications.js, which is able to notify our customers of what’s occurring within the backend.

// in providers/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 method. We simply want to offer our customers suggestions based mostly on whether or not or not their request was profitable. That may be carried out with a single technique, carried out in our two routers; one to point authorization failures and the opposite to point profitable end-to-end messaging.

A observe 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 utilize a third-party HTTP request library, like axios, and use that instead of any traces the place you see fetch used.

Weʼll begin by implementing the sendNotification technique, which is able to take the identical messageBody object that weʼre utilizing for our auth checks:

// in providers/WebexNotifications.js…
// contained in the WebexNotifications.js class

   async sendNotification(messageBody, success=false) {
       // we'll begin a response by tagging the one who created the message
       let responseToUser = 
`<@personEmail:${messageBody.information.personEmail}>`;
       // decide if the notification is being despatched resulting from a profitable or failed authz verify
       if (success === false) {
           responseToUser += ` Uh oh! You are not approved to make requests.`;
       } else {
           responseToUser += ` Thanks to your message!`;
       }
       // ship a message creation request on behalf of the bot
       const res = await fetch("https://webexapis.com/v1/messages", {
           headers: {
               "Content material-Sort": "software/json",
               "Authorization": `Bearer ${this.botToken}`
           },
           technique: "POST",
           physique: JSON.stringify({
               roomId: messageBody.information.roomId,
               markdown: responseToUser
           })
       });
       return res.json();
   }

Now it’s only a matter of calling this technique 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 "../providers/Auth.js";
// ADD THE WEBEXNOTIFICATIONS IMPORT
import {WebexNotifications} from '../providers/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 technique name to routes/index.js. Right here’s the ultimate model of routes/index.js as soon as we’ve added a couple 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 '../providers/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.submit('/', async perform(req, res) {
  console.log(`Acquired 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 deal with from the accepted listing after which ship a message to my bot. As you possibly can 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 deal with, the request goes by way of efficiently.

If I un-comment my email address the request goes through successfully.

Conclusion

Wow, that coated rather a lot in a brief quantity of code. Now we’ve an end-to-end working setup for ChatOps, with built-in safety. We will begin enthusiastic about all of the totally different workflows that we’d prefer to allow for issues like:

  • DevOps automation
  • Function-based workflows
  • Agile practices for distant groups
  • And anything you possibly can consider!

That is solely the second in a collection of weblog posts about ChatOps. Now that we’ve secured our Webex bot, we’ve a terrific jump-off level for constructing complicated workflows that may automate lots of downstream processes. As we cowl extra subjects, we’ll submit these right here, so verify again usually!

Comply with Cisco Studying & Certifications

Twitter, Fb, LinkedIn and Instagram.

Share:



[ad_2]

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments