SlackAP Bolt-Python App with AWS Lambda and AWS CDK

essence

  • Story: Since the retirement of rtm.start And Slack recommends not to use custom integrations anymore, so we need to move to new Slack apps.
  • With the new Slack Apps feature and the Bolt family of SDKs, we can implement Slack bots with AWS Serverless using Slack commands or at-mention.
  • This post will follow and extend the example from slackpy/bolt-python and the AWS CDK to manage infrastructure as code using TypeScript.

Table of Contents


I pre-requisite

  • You Need Slack Workspace (Free) to Build Slack Apps
  • Getting started with AWS CDK

I Create Slack App

  • First, create Slack app, grant permission for bot user OAuth & Permissions and then install in workspace. You can follow the guide from Slack page

  • Received Bot User OAuth Token From OAuth Tokens for Your Workspace below OAuth & Permissions
  • Get Signing Secrets from App Credentials below Basic Information,
  • Store bot token and sign secret .env file and make sure you have already ignored .env In .gitignore file. For better security we can let lambda get credentials from ssm parameter store or secret manager but it will slow down lambda operation a bit.

I Create lambda function with function URL

  • Why do we need lambda function URL here? Let’s have a look at the following overview diagram

  • The lambda function URL is used as the request URL for the slash command.
  • Not sure about the flow, but the lambda function needs permission to implement itself, otherwise we’ll get the following error:
  2022-09-30 10:47:35,222 Failed to run a middleware middleware (error: An error occurred (AccessDeniedException) when calling the Invoke operation: User: arn:aws:sts::123456789012:assumed-role/sin-d1-slack-app-lambda-role/sin-d1-slack-app-lambda-handler is not authorized to perform: lambda:InvokeFunction on resource: arn:aws:lambda:ap-southeast-1:123456789012:function:sin-d1-slack-app-lambda-handler because no identity-based policy allows the lambda:InvokeFunction action)
enter fullscreen mode

exit fullscreen mode

  • Here building the CDK is simple as we don’t run any actual processes or use other AWS services. The stack contains the IAM role associated with the lambda function along with the enabled function URL. Note that the lambda function’s default timeout of 3s is not enough to run process_request so we need to set timeout: Duration.seconds(10)
  import { PythonFunction } from '@aws-cdk/aws-lambda-python-alpha';
  import { App, Duration, Stack, StackProps } from 'aws-cdk-lib';
  import { Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam';
  import { FunctionUrlAuthType, Runtime } from 'aws-cdk-lib/aws-lambda';
  import { RetentionDays } from 'aws-cdk-lib/aws-logs';
  import { Construct } from 'constructs';
  import { join } from 'path';
  import { SLACK_BOT_TOKEN, SLACK_SIGNING_SECRET } from './shared/configs';
  import { devEnv, EnvironmentConfig } from './shared/environment';

  export class SlackAppTest extends Stack {
    constructor(scope: Construct, id: string, reg: EnvironmentConfig, props: StackProps = {}) {
      super(scope, id, props);

      const prefix = `${reg.pattern}-${reg.stage}-slack-app`;

      const role = new Role(this, `${prefix}-lambda-role`, {
        roleName: `${prefix}-lambda-role`,
        assumedBy: new ServicePrincipal('lambda.amazonaws.com'),
        managedPolicies: [
          {managedPolicyArn: 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'},
          {managedPolicyArn: 'arn:aws:iam::aws:policy/service-role/AWSLambdaRole'}
        ]
      });

      const lambda = new PythonFunction(this, `${prefix}-lambda-handler`, {
        functionName: `${prefix}-lambda-handler`,
        runtime: Runtime.PYTHON_3_9,
        environment: {
          SLACK_BOT_TOKEN: SLACK_BOT_TOKEN,
          SLACK_SIGNING_SECRET: SLACK_SIGNING_SECRET
        },
        entry: join(__dirname, 'lambda-handler'),
        logRetention: RetentionDays.ONE_DAY,
        role: role,
        timeout: Duration.seconds(10)
      });
      lambda.addFunctionUrl({authType: FunctionUrlAuthType.NONE});
    }
  }

  const app = new App();

  new SlackAppTest(app, 'SlackAppLambda', devEnv, {
    description: 'Create Slack App with lambda function',
    env: devEnv
  });

  app.synth();
enter fullscreen mode

exit fullscreen mode

  • Now, deploy the stack with the following command
  cdk deploy --method=direct
  SlackAppLambda: updating stack...

  ✅  SlackAppLambda

  ✨  Deployment time: 14s

  Outputs:
  SlackAppLambda.sind1slackappfunctionUrl = https://ehvkewqbgl7ukplwavdbpycyiy0nicsk.lambda-url.ap-southeast-1.on.aws/
  Stack ARN:
  arn:aws:cloudformation:ap-southeast-1:123456789012:stack/SlackAppLambda/7c775980-3ff4-11ed-bec3-024e925623dc

  ✨  Total time: 18.09s
enter fullscreen mode

exit fullscreen mode

I lambda functions

  • lambda handler here are two parts of index.py
    • Processes are related to Slack Bolt-Python: the important thing is to verify the signing request to clear the request is sent from the Slack app, it is implemented as a middleware, when you instantiate the app So is enabled by default (see the attribute request_verification_enabled).
    • Process event: handle the main operation

I Lazy Listener (FaaS)

  • In the lambda handler, you see lazy function, what is it?
  • The lazy listener is a feature (at the time of this post, only Python support) that makes it easy to deploy Slack apps to a FaaS (function-as-a-service) environment.

  • calling ack() The Slack API is responsible for returning an immediate HTTP response to the server within 3 seconds. Conversely, lazy functions should not provide any feedback. They can also do anything by taking advantage of the arguments of all the listeners. ack() utility. Also, they are completely free of 3-second timeouts.
  • As lazy is a list, you can set up multiple lazy tasks for the same listener. Lazy tasks will be executed in parallel.

I test slash command

  • make slash command with Command name is correct command We set in the lambda handler, get the output of the lambda function URL after running cdk deploy and add to Request URL

  • Once installed in Slack workspace, try typing /hello-bolt-python-lambda hello in any channel

I test the app on mention

  • we need to subscribe app_mention Provide lambda function url to bot event (your apps > event subscription > subscribe to bot events) and request url

  • With subscribing to this event, you can check if things are working by sending a message like “hello @yourbotname” to a channel your bot is a member of (sending this message will give you the option to add your bot to the channel ) very). If everything is working, you should get a response from your bot in the channel! hope this helps!

I conclusion

  • With lambda function url, we don’t need to host Slackbot in instance/server, all is serverless. And with AWS CDK, all is managed through code and deployed by cdk-pipeline
  • There are several features of the new Slack app that you can find out more about in the Slack API page.

References:


from dao image

Leave a Comment