AWS Lambda Access Strategy

Sym's AWS Lambda Access Strategy gives you ultimate flexibility and safety when executing Lambdas in your own environment.

๐Ÿ‘

You can generate this!

You can automatically generate an AWS SSO Flow with symflow generate aws-lambda!

Otherwise, follow the AWS Runtime Setup tutorial to set up your AWS dependencies.

Sym's AWS Lambda integration enables you to protect workflows and resources to which Sym doesn't directly integrate.

We'll call your function twice: when we escalate a user after approval, and again on deescalate. All of your SDK Reducers and Hooks still apply in the middle, giving you full flexibility to craft workflows around your own last-mile implementations.

๐Ÿ“˜

Full Example

You can find the complete code for this example in our AWS Lambda Access Strategy Example.

Create a Lambda for Escalation and De-escalation

Before provisioning your flow, you will need a Lambda to invoke on escalate and de-escalate.

Input Payload

The payload supplied to your Lambda is very similar to that of the Reporting module. You can see a full schema here.

Lambda Response Format

๐Ÿšง

Responses are required

If you don't include a formatted response from your Lambda, your Flow will succeed, but you will receive a warning message directing you to this guide.

The return from your Lambda must be formatted as a dict with two keys:

  • body: Dict[str, Any], a dict with one or more string keys
  • errors: List[str], a list of strings

For example:

{
  "body": {
    "message": "this is a custom message I'm going to DM someone!"
  },
  "errors": [] // send an empty array for no errors
}

Any errors in the response body will be displayed as bullets. For example:

def lambda_handler(event, context):
    print("many magical things are happening here")
    return {
        "body": {
            "message": "A message"
        },
        "errors": [
            "Is this the real life?",
            "Is this just fantasy?",
            "Caught in a landslide",
            "No escape from reality",
        ],
    }

The above will result in the below error being sent to your Sym Errors channel:

Terraform your Lambda

We recommend managing your Lambda via Terraform with the terraform-aws-lambda module. Then, you will only need to provide a folder containing your Lambda handler function.

module "lambda_function" {
  source  = "terraform-aws-modules/lambda/aws"
  version = "2.36.0"

  function_name = "your_lambda"
  description   = "A Lambda to be invoked on escalate and de-escalate"
  handler       = "handler.lambda_handler"
  runtime       = "python3.8"

  # The relative path to your lambda source code
  source_path = "${path.module}/lambda_src"
}

Provision an AWS Lambda Strategy

Connect the Sym Runtime with your AWS Account

Follow the AWS Runtime Setup tutorial on the main AWS page to set up your runtime_connector module.

Add a AWS Lambda Integration

Define the Lambda Connector

The AWS Lambda Access Strategy relies on a special module lambda-connector. This module defines the AWS Lambda Resources that enable Sym to invoke a Lambda in your AWS Account.

  • lambda_arns: A list of Lambdas you wish to allow the Sym Runtime to invoke
module "lambda_connector" {
  source  = "symopsio/lambda-connector/aws"
  version = ">= 1.0.0"

  environment = "main"

  # Add ARN of the lambda you wish to invoke to this list.
  lambda_arns       = [module.lambda_function.lambda_function_arn]
  runtime_role_arns = [module.runtime_connector.sym_runtime_connector_role.arn]
}

Define the Lambda Permission Context Integration

Define a sym_integration resource with type = permission_context. This tells Sym to assume the AWS IAM Role defined by the Lambda Connector module when invoking Lambdas, and will be referenced in the sym_strategy resource later.

  • external_id: Your AWS Account ID. Use the account_id output from module.lambda-connector.settings
  • settings: The settings output from module.lambda-connector
resource "sym_integration" "lambda_context" {
  type = "permission_context"
  name = "lambda-context-main"

  external_id = module.lambda_connector.settings.account_id
  settings    = module.lambda_connector.settings
}

Add an AWS Lambda Target

For an AWS Lambda Strategy, the sym_target will be the Lambda you wish you invoke to handle your custom escalation and de-escalation.

  • arn: A required setting, which must be set to the ARN of the Lambda to invoke on escalate and de-escalate.
resource "sym_target" "super_secret_button" {
  type  = "aws_lambda_function"
  name  = "super-secret-button"
  label = "Super Secret Button"

  settings = {
    # For this example, we are using an ARN outputted by the module we used to terraform the lambda.
    arn = module.lambda_function.lambda_function_arn
  }
}

Add an AWS Lambda Access Strategy

Define a sym_strategy resource with type = aws_lambda and include the AWS Lambda Permission Context and AWS Lambda Access Targets you defined above.

resource "sym_strategy" "lambda" {
  type = "aws_lambda"
  name = "main-lambda-strategy"

  integration_id = sym_integration.lambda_context.id
  targets        = [sym_target.super_secret_button.id]
}

Add the AWS Lambda Strategy to your Flow

In your sym_flow resource, reference your AWS Lambda sym_strategy as the strategy_id in your Flow Parameters.

resource "sym_flow" "this" {
  name  = "secret-button"
  label = "Secret Button Access"

  # ... other Flow attributes not shown

  params {
    strategy_id = sym_strategy.lambda.id

    # ... other Flow params not shown
  }
}

Using Lambda responses in your impl.py

Lambda responses can be retrieved in your Python implementation inside of a Hook that triggers after the Lambda is called:

In both cases, your response output payload can be retrieved via get_step_output().

Best practices

The easiest way to work with your Lambda responses is to treat them like REST responses. Simply grab the payload, throw it in a local variable, and grab the keys off it as you would expect.

from sym.sdk.integrations import slack
from sym.sdk.templates import get_step_output

@hook
def after_escalate(event):
  # Get your Lambda response body
  escalate_output = get_step_output()
  
  # Send a Slack message with the body from your output
  slack.send_message(event.user, escalate_output["body"]["message"])

Lambda Templates

Our lambda-templates repo provides a starting point for deploying a Lambda Function which can parse and process incoming payloads from Sym. In that repo, you'll find implementations in Python, Go, and Typescript, as well as a set of test payloads that you can use to test your implementation.