LittleLinks Automation w/Security

LittleLinks Automation w/Security
Photo by Jason Goodman / Unsplash

tl;dr:

Code is here: https://github.com/elreydetoda/littlelink

  1. have an AWS account with CLI access configured and an s3 bucket created
  2. Create the IAM Role permissions
  3. Use devenv and AWS-CDK to deploy the GitHub OIDC connection
  4. Ensure the GitHub (GH) action environment settings are configured
  5. Run your GH Action to deploy

Background

With tech and information always being desperate it is hard to stay organized. Also, when you want to start sharing more information some social media websites only let you have a single link or character limited description. So, if you want to link out to all the things you're working on it is hard to do that.

That's where software like Linktree comes into play, but as an avid self-hoster I prefer to stray away from third party services. Whether for cost, political stances, or whatever they want they can always remove your account or content. So, that's where LittleLink (LL) saves the day. I've heard about it for a while but wanted to keep it simple on how I deploy it so it was low maintenance but easily update-able.

Previously at work I'd configured a GH action to automatically upload stuff to an S3 bucket to automate a process. So, since I've already configured my S3 bucket to be my short link redirector (i.e. elrey.casa/me) I'd thought it would be easy to deploy LL from a GH Action runner. For work I also recently used some AWS-CDK code to automatically create the GH OIDC connection, so I figured I could pair those two things together to make the configuration for this deployment that much easier 😁.

Setup

Local and AWS

After forking the LL project, I've started using devenv to handle all the dependencies that I'd need, so I configured my devenv.nix file so I could have uv to quickly and repeatably pin my AWS-CDK python stack. I also was able to locally view my changes by running the following command to spin up a quick web server: uv run -m http.server

After creating the devenv setup and then configuring a sub-folder of IaC (Infrastructure as Code) for my AWS-CDK (a.k.a. CDK from now on) code, I then configured the dependency requirements in the pyproject.toml file. I attempted to run CDK via the nixpkgs first, but kept running into this issue, so I reverted to running it via a general npm install (which is why I have a package.json file in the repo as well). After all this configuration my command to execute CDK sub-commands was uv run cdk <sub_command> which was WAY too much for me to type all the time, so I wrote a wrapper script for the CDK of cdk_wrap.

Next I created the IAM role for the GH runner to assume, but I didn't include the name that it assumes in the code because that could be considered sensitive. That is mainly because if someone knows your full ARN to the role and is somehow able to satisfy the requirements for your OIDC connection, then they can utilize that connection to interact with your account. Our IAM role is VERY limited in what it can do, so this isn't that much of a concern but still security is like an onion (it is good to have as many layers as you can). I actually had the role name leaked during one of the CI runs that failed because I didn't have an explicit secret for the role name and the AWS action revealed it when it failed, so because of this I created an explicit secret in my prod GH action environment called: MISC_BLOCK_ACCOUNT_NAME to have it redacted if the S3 upload fails again. Also, a quick side note, the environment variable will need to be the full ARN for your IAM role.

GitHub

Create an environment in GitHub and have it look like this.

Adding the Required reviewers section at the top will require your account to approve a GH action Continuous Deployment (CD) run whenever it goes to run. Since you should be the only one committing to the main branch and you're granting GH action running access into your AWS account from a public repo, I believe this is a fair trade-off. The AWS_BUCKET and AWS_REGION are again to obfuscate things wherever possible to add more layers so my AWS account information isn't disclosed, and if you were doing this in a private repo you probably wouldn't need as many GH Secrets.

Deployment

So, now that you have devenv, uv, your CDK binary, and your GH repo configured. Now you'll need to run the following commands:

devenv shell
cd IaC
cdk_prep

# define these environment variables used in the CDK stack
 export AWS_ROLE_NAME=<IAM_ROLE_NAME> AWS_ACCOUNT_ID=<AWS_ACCOUNT_NUM> AWS_ACCOUNT_REGION=<REGION_CDK_DEPLOYS_IN>

# validate your AWS creds are working and everything will deploy correctly
cdk_wrap synth
# prep your AWS account for CDK
cdk_wrap bootstrap
# actually deploy your account modifications
cdk_wrap deploy

Afterwards, you'll have the OIDC connection deployed to your AWS account. Make sure you've swapped out my repo for your own GH repo in the CDK code, and you can just re-run cdk_wrap deploy if you need to re-deploy that.

Now, you can manually run your GH action to upload the repo's content to your S3 bucket.

Conclusion

This was a quick blog post about how I made the deployment of my https://elrey.casa/links LL website. Let me know if you'd like me to create another post about how I setup my S3 bucket as my short-link handler.

Reference

IAM Role permissions

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VisualEditor0",
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:PutObjectAcl"
      ],
      "Resource": [
        "arn:aws:s3:::<BUCKET_NAME>/links",
        "arn:aws:s3:::<BUCKET_NAME>/ll/*"
      ]
    },
    {
      "Sid": "VisualEditor1",
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::<BUCKET_NAME>",
      "Condition": {
        "StringLike": {
          "s3:prefix": [
            "",
            "ll/"
          ]
        }
      }
    }
  ]
}