As I started to dive into learning AWS CDK development for repeatable Infrastructure as Code (IaC), I quickly saw how many components of the CDK library were missing Layer 2 constructs.
I've been looking for a way to learn CDK more, with the goal of being able to provide a better resource for developers that are working on IaC projects at my work. If I'm asked about best practices for code styling, secrets management, and ensuring the projects can be easily deployed through dev, staging, and production environments, I want to be sure I understand the structure of CDK.
I was also looking for ways to contribute back to the open source community in a meaningful way. Contributing a Layer 2 (L2) construct to the CDK repo seemed like a good way to both learn the code structure and contribute back.
Ideally I wanted my L2 construct to be something that would be helpful not only to the community, but also to my current job. While I was looking for ways to replace a physical firewall device with a cloud native solution, I found that AWS Network Firewall was missing L2 constructs. This seemed like a good place to start my contribution, and possibly provide a useful alternative to my firewall search.
I started in the aws-cdk repo looking for an existing efforts to work on this project. I found an issue ticket that noted the absence of the Network Firewall L2 constructs, but no existing PR with the new change being implemented.
The Issue ticket was created a month before I found it, and this indicated to me that there was not much internal push to get the L2 constructs written.
Over the next few weekends I worked on building the new L2 constructs into a PR that I could propose before another contributor beat me to the punch.
The First PR.
This was the first time I had worked out of a large repository with proper CI/CD functionality built in, and it was amazing.
The startup process of getting a development environment was a little rocky, mostly due to learning typescript with a primarily javascript background.
Once I learned the basic build and test commands for the project and I felt like I knew the general structure of the repo, I was off to the races.
The error checking was nice to have, but the automated integration tests and there is even a scanner to ensure you properly updated the ReadeMe file to show that there are now L2 constructs.
By the time I had the PR ready, I felt like my code was bullet proof.
Most developers I've worked with fear writing unit tests as they can appear to be just "more work" for development. I found that the requirement of code coverage for this project only increased my confidence in my code, and my hope that the code wasn't going to embarrass me.
The GitHub repo for AWS CDK was just getting through a redesign of the contributing pipeline, and I found that a pull request directly to the repo was no-longer the preferred way to contribute.
A new aws-cdk-rfcs repo was created to take in Request for Comments (rfcs), which would allow contributors to talk with the CDK development team prior to actually working on the contribution. This was done largely to ensure that the new constructs would be maintainable and have a similar style of function layout/parameters as the rest of the project.
The RFC
After learning about the new RFC process I had some new-found motivation to work on this project. My PR was attracting attention of the CDK team, and even though I wasn't making a contribution yet, I felt like I was a step closer to getting my name on the contributors list.
I didn't have much documentation for my code, other than the extensive comments in the code. So the RFC also provided me a goal to structure exactly what it was that I was creating.
Since I had already written a lot of the code for my initial PR, I heavily structured the RFC document around my existing code. Ideally my RFC would solve all issues and I wouldn't need to change my original code at all.
But I started writing the document, I realized I had questions about the structure I had built.
I wanted the library to implement best-practice defaults whenever possible. One of the main benefits of using a IaC tool from the cloud provider is it implements their best practices and saves your developers from having to answer security questions as they go.
I wanted my L2 construct to be helpful in the same way. But I didn't know what that best practice looked like.
When adding a new firewall in a place where there wasn't a firewall before, it can be hard to immediately lock down the allowed traffic without accidentally cutting off some traffic that should normally be allowed.
One way to help with this issue is to make the firewall mostly open at first, and slowly lock that down as a baseline of "known good" traffic is established.
But for new developments, where there is no "known good" baseline to be had, it can be easier to start with a completely locked down firewall, and poke holes through where they are needed.
This ensures the best security as all requests are formally requested with a reason for their use.
In my L2 construct, should I expect that the firewall would be being added as an additional layer to an existing stack, or would it be primarily used in new developments efforts?
Should I follow the format of the Security Groups, which allow all outbound traffic, but restrict all inbound traffic?
As I wrote the RFC I noted these questions and explained my choice to stick with an unopinionated approach that required the user to setup default network rules.
This approach increased the amount of boilerplate, but by not having a default, it allowed the addition of a default in the future if one was decided.
Self Publishing
The RFC I created say for a while after I made it. The CDK team requested that I reach out to their Slack channel to find a bar-raiser to assist in reviewing the request. But it appeared that the CDK team was spread pretty thin on other projects and I never met with anyone from the CDK team.
However, The RFC did attract some attention from the community. I attempted to get some peer review of the RFC in hopes of getting ahead of any issues before I talked to the CDK team about the code.
It seemed the RFC was clean enough, and the community just wanted a package to work with.
It was requested that I self-publish the code I did have until the CDK team could review my request to contribute to the main CDK repo.
I setup my repo using the projen tool which appears to be designed to help community members self-publish their packages. The tool handled most of the build pipeline including publishing the typescript and python variants to their respective package stores.
The package deployment went pretty smooth thanks to the advanced CI pipeline from the AWS repo, and my cdk-networkfirewall-l2 package was published to pnpm an pypi for the community to use.
The package was also available in the constructs.dev package search tool to help CDK developers to find the package.
Since publishing I have only had to perform minor maintenance on the package for dependency updates.
There was one feature request to be able to read in a file of Suricata rules instead of 1 string at a time.
https://github.com/durkinza/cdk-networkfirewall-l2
Next Steps
I would like to start on a set of L3 constructs, using the L2 constructs as a base for the L3.
Configuring an Inspection VPC could be designed as a L3 construct to handle reviewing traffic before passing it on to another set of VPCs for processing.
I would also like to implement a Suricata rule validation step, where the strings and files of Suricata rules are parsed to ensure they have proper syntax before uploading them to a cloudformation template.
Comments
Write a comment ...