I’ve been using Serverless at work for a little bit of time. While doing that, I’ve been exploring it even more on my own time. There are a few things that I want on all my projects and don’t want to do manually every time. Amongst other things, I want my S3 buckets to never be public and I want some kind of git status on my lambda. I know there are plenty of Serverless plugins to do just that, but I want to learn how to do a plugin. Here is how I ended up writing a Serverless Plugin for S3 security and Git status.
As always, the code I worked on for this sample can be found on my GitHub.
Basically, I wanted my plugin to be used as a master plugin for my other projects. I want it to do the following:
- Store the SHA, branch, and user that deployed
- Ensure that all my S3 buckets can’t be made public (using PublicAccessBlockConfiguration)
- Ensure that all my log groups have a retention policy
- Validate the stage that I’m using for deployment to select environment configuration
Since I wanted to see how to setup custom options, I allowed the user to whitelist some S3 buckets to not receive the block.
Writing the plugin
Creating the base of the plugin is simple enough. Still, I decided to use serverless to create the skeleton for me using serverless create --template plugin. Running this command will create a index.js file containing the boilerplate for the plugin. You will then need to build a package around that file.
Registering to the hooks
Integrating your plugin into Serverless’s workflow is done through hooks. Everything is a step and you can hook before or after any step. You can even create new ones when you add commands. Finding documentation on these steps is quite hard. The best documentation of the flows I could find is on this gist.
After investing quite a bit of time I found the two places where I needed to hook my plugin. The first one was after the package:initialize step. In my code, hooking to this step can be found here. Using this hook, I validate that the configuration used makes sense for me.
The second hook I needed is used to inject into the resources. This needed to be far enough in the flow so that resources are already created, but not so far so the CloudFormation templates are not yet generated. I found that hooking before the package:finalize step gets that done. This part can be found here in my code.
Finding all generated resources
Serverless can store your resources in two different places depending on how they got created.
If a resource is added through the resources section of serverless.yml it will end up in serverless.service.resources.Resources. An example of this is my public assets S3 bucket.
If a resource is generated by Serverless itself, for example, a deployment bucket, it will end up in serverless.service.provider.compiledCloudFormationTemplate.Resources.
To make handling these two types of resources easier, I wrote a quick wrapper to iterate on both of these.
Using custom options from serverless.yml
To make my plugin a bit more usable, I wanted to see how to interact with options. These are added in the custom section of your serverless.yml file.
In your plugin code, you’ll be able to access the configured values through the serverless object. For example, in my code, I access serverless.service.custom.core.skippedBuckets here. I use this value to skip some buckets and allow these to be public.
Getting the git repository status
I wanted to keep my code as self-contained as possible. In order to do so, I wanted to refrain from using a shell command to get the git information. I looked at NodeGit and that was exactly what I needed. Sadly, installing the package on my RaspberryPi ended up taking way too long and I decided to simply use shell commands.
I ended up using 4 commands to get the information I needed. These can be found here in my code, nothing too fancy around there.
Using the plugin
In order to use the plugin, you’ll need to do two things. The first one is to add the dependency to your package.json. In my case, I decided to simply use a file path to refer to the project. Once this is done, you’ll need to register the plugin in your serverless.yml file.
If your plugin requires any special configuration, you’ll be able to add it into the custom section of your serverless.yml file. In my case, I added a whitelisting for one of my S3 buckets. This was done by adding the following code section:
- Writing serverless plugin (part 1 and part 2)
- Sample plugin with a lot of detail and information