Accessing your AWS EC2 instances on the go
I’ve released an updated version with additional features:
- Auto IP Update — when activated, the React app will detect IP address changes and will update current SG Rules accordingly.
- Current SG Rules List — each user can now see their current rules and to delete rules they don’t need any more.
A nice serverless solution for easily opening access to EC2 instances. Skip to the flowchart to see how this all works. A GitHub link is also somewhere down there. Enjoy.
You’re at Re:Invent conference, enjoying the talks, the drinks and the company. You’re sitting in a talk about a very interesting topic. Suddenly you’re getting a notification that your web server isn’t responding! You put on your smug smile, pull out your super sophisticated laptop, connect to your mobile’s hotspot (free WiFi is so 2015) and start your client to connect to your rogue server.
So far so good. You’re cool.
But it won’t connect, right? You followed best practices and opened port 22 (or.. shhhh.. 3389) only to a limited number of IP addresses.
So what should you do next? A bit less cool now, because the server is down, your customer is getting impatient and the talk is about to finish (they promised boutique ice cream will be served soon!).
Well, you know the drill.. Access your AWS console, login (MFA is a must, but it’s a hassle!), reach the security groups page, can’t find it, realise you’re in the wrong region, finally find that SG and add the rule. At last.. you can access your server.
Anyway, I hope I’m not the only one who got himself in this situation.. After too many times I decided to do something about it. Now, maybe there are solutions out there, I didn’t bother checking. I took this real world problem that I faced and decided to tackle it using serverless — no better way to pick up new knowledge than to give it a purpose!
Enter… Let Me In!
LMI gives me quick access to my EC2 instances. I access a React web app (hosted on S3), authenticate (against Amazon Cognito) and then can submit a request (to API Gateway) to open for a limited time certain ports for a specific IP. By default it will populate the IP field with my current public IP.
Here’s what happens when I submit the request to open ports for me:
- API Gateway ensures it’s an authenticated request.
- The Lambda associated with APIG will ensure the parameters are valid and will store the request in a DynamoDB table.
- A success response is returned to the client.
A second Lambda is triggered by DynamoDB Streams and processes the changes in the DDB table and adds/removes the SG rules. It will then publish a message via SNS to me. This confirms that the action was done.
DDB Stream events:
- INSERT event — when a new rule is inserted the Lambda will add the rule to the SG and publish a confirmation about addition of rule.
- REMOVE event — when it’s time to remove the rule then the Lambda will remove the SG rule and publish a confirmation about the removal.
- Since we are dealing with opening ports then this solution is intentionally limited — you can only update a single SG. This SG is not passed from the client, but instead is set as a Lambda environment variable. Expiry is capped at 30 minutes in case the user enters a higher value. Don’t forget to attach the SG with the servers you wish LMI to open access to.
- By leveraging DynamoDB TTL I don’t need to worry about checking when a rule should be removed. I do the expiry time calculation as part of inserting the record and DDB will remove the record when TTL arrives (kinda — read next bullet).
- More about DDB TTL — when TTL arrives then the record expires, but this won’t trigger the DDB Stream. The DDB job that handles TTLs will ensure records are removed not before their TTL, but there’s no guarantee when they will be removed. In my tests I saw it varies, in some cases records were removed within 2–3 minutes and in others after 20 minutes.
- Notice the permissions the Lambda needs in order to read the DDB Stream.
One Serverless Framework Gotcha
- It’s impossible to assign an Amazon Cognito User Pool ARN to a Lambda’s authorizer using a dynamic expression (Fn::GetAtt). Therefore I was forced to create the user pool in advance, grab its ARN and plug it in serverless.yml file.
It was a fun experience to work with Serverless Framework. The developer experience is very smooth and fits well in the overall workflow. There are some small issues such as the one I personally encountered, but they are not deal breakers. There’s a vibrant community to talk to and it feels everyone’s there just for the fun of it!
I think that deploying the client app (in my case a React web app) should be done as part of the Serverless Framework deployment process. When the vision is to replace traditional web applications with serverless-based ones then the client is a major part of it.
Oh! Don’t worry, you made it for the ice cream on time! ;)
If you enjoyed this article and maybe even benefitted from the code then how about recommending it by clicking on the green heart below? This will increase the article exposure and hopefully will help more readers like yourself! Thanks!