• DevsWorld
  • Posts
  • Automatically Allow IP Addresses for Github Actions Runners in AWS EC2 Security Group

Automatically Allow IP Addresses for Github Actions Runners in AWS EC2 Security Group

A simple how-to guide on how to deploy your app on EC2 with Github Actions without leaving your SSH port open to the world

Wondering how you can support this newsletter? I would love it if you bought me a coffee. It helps me write more code and more newsletters.

Buy Me A Coffee

How’s it going? Has AI taken your job yet? Hopefully not if you’re in the DevOps space. We need more of us and there’s never a better time to upskill yourself and learn more about automation.

I want to dig into a solution I created for myself a while back.

The problem:

Don’t expose SSH for my app to the whole world while not needing to add all of Github Actions runner IPs (there’s like 1k CIDRs available…)

The solution:

Dynamically update the security group with the current Github Action Runner’s IP address.

Background

Let’s say you have an application running on your EC2 instance. In this particular case, I have a NodeJS application that is running on the machine. WeLl WhY nOt UsE dOcKeR??

Well, I use an ARM machine locally and I use ARM on the EC2 instance. If I build locally and push, that’s an OK solution, but then I need to remember to build and push before I push and I like to automate things, so that’s a no-go.

I could use ARM GitHub actions runners to build the Docker image! No. ARM machines from GitHub actions is an enterprise feature and this is a personal project that I am not going to subscribe for just that reason…

The other solution would be to build the Dockerfile on the machine itself, however, if I do that, it takes a ton of resources from the machine to build there. If you have users, this is a no-go since I already have quite a bit of resource usage on the machine without the build happening.

If I could build on Github actions runners and deploy the image to an image repository, that would be so ideal. Regardless, that’s not entirely the point of this. You’d still need to SSH to the machine during the deployment to pull the image and run it.

Which brings us to the meat of this post, how do you securely allow GitHub Actions Runners to access your machine without leaving SSH ports wide open?

About Security Groups

By default, AWS EC2 security groups deny access on all ports. It might be seem obvious, but it’s important. If you want some external build system (such as Github Actions) to access it, you need to allow it. How do you allow this securely though?

  • You can open SSH to the world, which is the easiest solution, but it’s 100% insecure.

  • You could add the GitHub Action Runner IPs to access the machine, but there’s, um, a lot. Also, they can change at any time! Even on the GitHub documentation it states that they can change at any time. Which means one day, you will have machines not able to access it… That’s not cool.

  • The coolest solution could be to dynamically update the security group with the current action runner’s IP address and then always remove it after the workflow has finished

Nitty Gritty Details

To do this, there’s a few things to setup:

  1. You need an AWS IAM role that has sufficient permissions to update the security group.

  2. You need an SSH key that will be used by GitHub Actions workflows to access the machine.

  3. You also need to add the SSH key to the authorized keys on your machine to avoid the script hanging.

IAM Role

First of all, you should create an OIDC role for GitHub to access. This is already defined in the actions documentation for this action:

This will generate temporary credentials to access AWS within the workflow.

Policy

Next, you’ll need to create an IAM policy that will be attached to the role. You can copy this one, but notice that you need to update the ARN for your security group.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DescribeSecurityGroups",
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeSecurityGroups"
      ],
      "Resource": "*"
    },
    {
      "Sid": "ModifySpecificSecurityGroupSSHOnly",
      "Effect": "Allow",
      "Action": [
        "ec2:RevokeSecurityGroupIngress",
        "ec2:AuthorizeSecurityGroupIngress"
      ],
      "Resource": "arn:aws:ec2:*:<your-aws-account-id>:security-group/<ec2-security-group-id>",
      "Condition": {
        "NumericEquals": {
          "ec2:FromPort": 22,
          "ec2:ToPort": 22
        }
      }
    }
  ]
}

GitHub Action Setup

Cool, now that you’ve set this up, you will need to generate the SSH key. You can easily handle that with the ssh-keygen command. You will then need to add it as a secret in your GitHub repo. Here’s the docs for doing that: https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets

Also, you can add your server’s IP address as an environment variable or, what I do is just use the server’s domain name as it should resolve properly. Either works fine.

Once that’s complete, you can add a script like this to your deploy workflow:

  - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v4
      with:
        role-to-assume: arn:aws:iam::<account-id>:role/GithubOIDCRole
        aws-region: your-region


    - name: 🔎 Check IP
      run: echo "GITHUB_RUNNER_IP=$(curl -s https://api.ipify.org)" >> $GITHUB_ENV

    - name: Add IP to security group.
      run: |
        aws ec2 authorize-security-group-ingress --group-id '<security-group-id>' --protocol tcp --port 22 --cidr ${{ env.GITHUB_RUNNER_IP }}/32

    - name: SSH & Deploy
      uses: appleboy/[email protected]
      with:
        host: {{ env.SERVER_IP }} # Or server domain name
        username: ec2-user
        key: ${{ secrets.SSH_KEY }}
        port: 22
        script: |
          cd $HOME/<my-app-dir>
          bash ./deploy.sh # Or whatever you need to do on your machine
    - name: Remove IP from security group.
      if: always() # Very important!!! This makes sure that it runs no matter if the deploy fails or succeeds
      run: |
        aws ec2 revoke-security-group-ingress --group-id '<security-group-id>' --protocol tcp --port 22 --cidr ${{ env.GITHUB_RUNNER_IP }}/32

Boom! You’re now doing some really cool automation that allows GitHub actions to access your server to do your deployments without compromising on security.

This took me around 30 minutes to set up the first time and now, I add it to every new backend app that I make. It saves me a ton of time setting up the deployment as I can adjust the deploy script freely.

If you liked this short snippet, let me know and share it with someone who has SSH open to the world for their deployments and don’t forget to buy me a coffee. 🙂