Back in March, SimpleMDM (of which I am a big fan) announced they were adding webhooks to their Mobile Device Manager. These allow you to send an HTTP POST to the URL(s) of your choosing when certain events occur in your SimpleMDM account. Following this announcement, I built an AWS Lambda function for responding to these events, partly as a learning. This function is designed to allow for more sophisticated response whenever a device is enrolled or unenrolled in SimpleMDM.
I recently went back and gave it an overhaul, improving the logging system and making it more easily extendable and customizable. I’ve also been on an infrastructure-as-code kick this year, and so defined the necessary AWS infrastructure in a Terraform file.
In the hope that someone might find it useful, I will explain a bit about how to set up the Lambda function, how it all works, and how you can modify it to meet your particular needs. I won’t cover the basics of AWS, Lambda, or Terraform, but hopefully this will give you a basic understanding of how this function works and how you can use it effectively.
My “Tool of the Year Award” for 2018 goes to Terraform (last year’s goes is Ansible and the year before that to Munki; I intend to cover each of these in future posts). Most of the projects I’ve worked on this year that rely on a public cloud or VPS provider has had its infrastructure defined in Terraform files, and this one is no exception. The ability to create, change, and delete AWS or DigitalOcean resources quickly and repeatably has been a gamechanger.
If you’re familiar with Terraform, you should be able to get up and running pretty quickly. (If not, I encourage you to check it out; the docs are well-written and an excellent place to start). The
example.tfvars file contains all the variables that can be be set, though not all are required. Create a copy of this file, set the variables you want and delete the rest, and you should be ready to get started with
terraform apply. This will stand up all the requisite AWS infrastructure—not just the Lambda function and its environmental variables, but the API Gateway for receiving the webhook, an S3 bucket for logs, and the necessary IAM role and permission policies.
You can, of course, configure these services manually or using CloudFormation. You will need to create each of the aforementioned components and make certain they have the proper policies to communicate with one another. Be sure to create a separate IAM role with permissions limited to only those necessary to carry out the Lambda function’s tasks.
How it Works
Whenever a device is enrolled or unenrolled in SimpleMDM, an HTTP POST is sent to the API Gateway URL. This request includes information about the event type, the time of occurrence, and additional device metadata in JSON format. AWS forwards this data to the Lambda function, which calls the function corresponding with the webhook event and logs the results.
Depending on what environmental variables are set, it then steps through the logic of that webhook function:
- If a SimpleMDM API key is set, the function queries the API and newly enrolled devices are moved to a specific SimpleMDM group based on that information.
- If certain Munki-related variables are included, then a Munki manifest file will be created and added to an S3 bucket for newly enrolled devices.
- if the Slack URL environmental variable is set, the Lambda function will send a message to that Slack channel notifying it what device has enrolled or unenrolled.
When the Lambda function has concluded its run, it uploads a JSON log of its actions to an S3 bucket.
Customize and Extending the Lambda Function
One of my goals in writing this Lambda function was to allow other users to easily extend and customize it to suit their needs without modifying the core code. One advantage of this is that it allows users to include their changes and additions while still allowing them to easily merge changes to the original code.
For each webhook event there is a corresponding sub-function to be run in response. As seen below, the main Lambda module first attempts to import these functions from a file called
webhook_functions.py, which by default does not exist. If that fails, it will import the functions from the included
To alter the response to a particular webhook event, create a function with the same name and arguments in a new
webhook_functions.py module. The only requirements are that the functions takes the same arguments and that the new module include any necessary imports.
Adding new environmental variables is likewise straightforward. The default environmental variables are set in
env_vars.py. This can be used as a template to create an
additional_env_vars.py module and set any new variables there. No other changes are necessary.
Finally, any new modules required to extend functionality can be added simply by importing them. You are also encouraged to use the logging objects from
utils.py to log event outcomes (see other modules for examples). This makes it easier to include additional features such as uploading the new device to an asset management system or sending notifications to other services. If you feel your modules might be useful to others, consider making a pull request to add it to the main repo.
This tool and guide are limited to a rather specific audience: MacAdmins who use SimpleMDM as their Mobile Device Manager and who wish to automate the response to newly enrolled and unenrolled devices. Even so, it was useful as an exercise for me in learning about AWS Lambda and API Gateway and writing good, DRY, extensible code. As with any technical concept, you don’t really understand it until you can explain it effectively. Hopefully, between this post and the README in the GitHub repo, I’ve done that.