Introduction
Socless Playbooks are simply JSON objects that describe the actions to take and resources to use to automate a response plan. On deployment, the JSON objects are converted to State Machines that can be executed by the AWS Step Functions service. As such, the JSON objects are written according to the AWS States Language specification but with some slight differences
The basic, bare-bones structure for any Socless Playbook JSON object is shown below:
{
"Playbook": "",
"Comment": "",
"StartAt": "",
"States": {
}
}
It consists of the following top-level fields: - Playbook: (required) UpperCamelCase name of the playbook. eg. InvestigateLogin - Comment: (optional) Human readable description of the playbook - StartAt: (required) The starting state of the playbook - States: (required) A description of all the states in the playbook and their relationships to each other i.e. transitions. Currently, states should be named using the Upper_Case notation e.g Send_Slack_Message
In this tutorial, we'll be implementing the InvestigateLogin
playbook we discussed in the overview for this tutorial. Our Playbook will be triggered by the alert below
{
"alert_name": "Anomalous Login IP Detected",
"details": [{"username": "bruce.wayne", "ip": "113.63.125.3"}],
"response_plan": "InvestigateLogin"
}
It will respond by taking the following steps:
- Geolocate the ip
using our GeoIP integration
- Use our SendMessage integration to post a notifiction of the login activity + geo ip information to our #bat-signals channel
Let's start the tutorial by setting up our playbooks development environment
Setting Up
Clone Playbooks Template
To begin, clone and set up the socless-playbooks-template
into your high-level project directory by running the commands below
git clone git@github.com:twilio-labs/socless-playbooks-template.git socless-playbooks
cd socless-playbooks
./setup
The resulting socless-playbooks
directory is a Serverless framework application that will serve as the deployment stack for ALL your Socless Playbooks going forward.
Within the socless-playbooks
directory, create a folder named playbooks
. This folder is where all your Playbooks will be written.
Reference Integration ARNs in our Playbook stack
To complete the setup for our Playbooks stack, let's create references to the ARNs of the Lambda Integrations we deployed in the last tutorial so we can use those Integrations in the playbook we'll create.
Open the serverless.yml file. The custom
section currently looks like this
custom:
statesRole: ${{cf:socless-${{self:provider.stage}}.StatesExecutionRoleArn}}
socless:
CreateEvents: ${{cf:socless-${{self:provider.stage}}.CreateEvents}}
MergeParallelOutput: ${{cf:socless-${{self:provider.stage}}.MergeParallelOutput}}
Counter: ${{cf:socless-${{self:provider.stage}}.Counter}}
AwaitMessageResponseActivity: ${{cf:socless-${{self:provider.stage}}.AwaitMessageResponseActivity}}
SetInvestigationStatus: ${{cf:socless-${{self:provider.stage}}.SetInvestigationStatus}}
QueryCustomMapping: ${{cf:socless-${{self:provider.stage}}.QueryCustomMapping}}
AddCustomMapping: ${{cf:socless-${{self:provider.stage}}.AddCustomMapping}}
CacheResults: ${{cf:socless-${{self:provider.stage}}.CacheResults}}
Underneath the socless
key are references to the ARNs of the Lambda Integrations that Socless' core infrastructure ships with by default. These references will allow us use the ARNs of these Lambda integrations in our Socless playbooks.
Whenever you need to use an integration in a Socless playbook, you'll need to ensure that the ARN of the Lambda Integration is referenced here. The current convention for referencing Lambda ARNs in your playbook stack is to nest the Integration names under their relevant "product" stack in the custom
section of the serverless.yml file. That is
custom:
product:
Integration: ${{cf:socless-product-${{self:provider.stage}}.Integration}}
For our tutorial, we have two integrations (GeoIP & SendMessage) that are currently deployed in our tutorial "product" stack. Reference their ARNs in our Playbook stack by adding the below code one-indentation level under our custom
field (i.e. on the same level as the socless
field):
tutorial:
GeoIP: ${{cf:socless-tutorial-${{self:provider.stage}}.GeoIP}}
SendMessage: ${{cf:socless-tutorial-${{self:provider.stage}}.SendMessage}}
We will now be able to reference the GeoIP
and SendMessage
Integrations in our Playbook using the variables ${{self:custom.tutorial.GeoIP}}
and ${{self:custom.tutorial.SendMessage}}
respectively.
The references rely on The Serverless Frameworks' Variable System. Be sure to review that documentation to understand how it works.
Coding The Playbook
Laying out Basic Structure
Our tutorial playbook will be called InvestigateLogin
. In our playbooks
directory, create a folder called investigate_login
. In the investigate_login
folder, create a file named playbook.json
. This file is where our playbook's implementation will live. It must be called playbook.json
.
Open the playbook.json file in your favorite text editor and layout the basic structure of our playbook as shown below:
{
"Playbook": "InvestigateLogin",
"Comment": "Playbook to investigate a login",
"StartAt": "Geolocate_IP",
"States": {
}
}
This defines a playbook called InvestigateLogin
that starts at a state called Geolocate_IP
. Let's configure that Geolocate_IP
state in the States
object.
Configuring the Geolocate_IP state
Our Geolocate_IP
state will be a Task state that makes use of our GeoIP integration. Courtesy of AWS Step Functions, Socless playbooks support multiple types of states that all serve different purposes in automating a workflow. Task states are primarily used to execute actions in a playbook. Start the definition for our Geolocate_IP state by updating your playbook.json to match the code shown below
{
"Playbook": "InvestigateLogin",
"Comment": "Playbook to investigate a login",
"StartAt": "Geolocate_IP",
"States": {
"Geolocate_IP": {
}
}
}
The update starts the definition for our Geolocate_IP
state. Note that the current convention for naming states in Socless is to use Upper_Case_Underscore_Separated
notation.
A bare-bones configuration for a Task state requires the below field:
Type
: The state type. For Task states, this must be set toTask
Resource
: The URI for the resource the state uses to execute its action. For Lambda Integrations, this is the ARN of the Lambda function for the Integration. Since we'll be using the GeoIP Integration, we'll specify its ARN using the${{self:custom.tutorial.GeoIP}}
variable we configured in our serverless.yml file.Parameters
: An object with a key-value mapping of all the parameters an Integration needs to complete its action. As explained in the Parameter References & Template Variables documentation, there are many ways to pass parameters to Integrations depending on where the information lives. The IP address we want to geolocate in our playbook is contained in the event payload sent to our Playbook and can be referenced using the parameter$.artifacts.event.details.ip
. Since our GeoIP integration requires anip
, we'll pass it the IP using the configuration{"ip": "$.artifacts.event.details.ip"}
- A transition configuration, either
Next
if we're transitioning to another state, orEnd
if the state being configured is the final state of the playbook. Our GeoIP state will transition to "Notify_Bat_Signals_Channel" once it completes its execution, so we'll configure aNext
transition
The full configuration of our Geolocate_IP state is shown below.
{
"Playbook": "InvestigateLogin",
"Comment": "Playbook to investigate a login",
"StartAt": "Geolocate_IP",
"States": {
"Geolocate_IP": {
"Type": "Task",
"Resource": "${{self:custom.tutorial.GeoIP}}",
"Parameters": {
"ip": "$.artifacts.event.details.ip",
},
"Next": "Notify_Bat_Signals_Channel"
}
}
}
The GeoIP Integration used by this state returns country
, latitude
and longitude
. These values will be accessible by subsequent integrations using either Parameter references in the format $.results.Geolocate_IP.*
or Template variables in the format {context.results.Geolocate_IP.*}
.
Configuring the Notify_Bat_Signals_Channel
state
Next, let's configure our Notify_Bat_Signals_Channel
state. This state will also be a Task state. It'll use our SendMessage integration to send a message to our #bat-signals Slack channel. The message will read "{username}
just logged in from {latitude}, {longitude}
". Finally, our state will serve as the End
state for our playbook.
Extend your playbook.json with the configuration for our state as shown below
{
"Playbook": "InvestigateLogin",
"Comment": "Playbook to investigate a login",
"StartAt": "Geolocate_IP",
"States": {
"Geolocate_IP": {
"Type": "Task",
"Resource": "${{self:custom.tutorial.GeoIP}}",
"Parameters": {
"ip": "$.artifacts.event.details.ip"
},
"Next": "Notify_Bat_Signals_Channel"
},
"Notify_Bat_Signals_Channel": {
"Type": "Task",
"Resource": "${{self:custom.tutorial.SendMessage}}",
"Parameters": {
"target": "bat-signals",
"target_type": "channel",
"message_template": "`{context.artifacts.event.details.username}` logged in from `{context.results.Geolocate_IP.country_name}` at coordinates `{context.results.Geolocate_IP.latitude}`, `{context.results.Geolocate_IP.longitude}`"
},
"End": true
}
}
}
Voila! our InvestigateLogin playbook is completely written and ready to deploy.
Configuring our Playbook for Deployment
Open your serverless.yml file. Add a playbooks
sub-section underneath the custom
section and list the folder name of the playbook as shown below
playbooks:
- investigate_login
Our playbook is no configured for deployment.
Deploying the Playbook
To deploy the playbook to our dev environment, execute the below command:
npm run dev
Conclusion
Head to the AWS Step Functions Console in the AWS Region of your Socless dev environment. You should see the InvestigateLogin Playbook listed there. The easiest way to test our playbook is to send an alert to our Playbook through an Event Endpoint. So let's create your first event endpoint