Introducing Today's Project!
In this project, I will demonstrate how to identify and remove hard‑coded credentials from a configuration file, create and store those credentials in AWS Secrets Manager, update the application to fetch secrets at runtime, configure least‑privilege IAM access so the app can retrieve the secret, and rotate the secret while verifying the application continues to authenticate; I’m doing this project to show practical Secrets Manager workflows, secure secret handling that prevents committing credentials to source control, how to design minimal IAM policies, and how to test secret rotation and deployment changes so I can confidently deploy applications without exposing sensitive data.
Tools and concepts
Services I used were AWS Secrets Manager, AWS IAM, Amazon S3, the boto3 AWS SDK for Python, GitHub (with secret scanning), Git (interactive rebase and history rewrite), and Python tooling (virtualenv, FastAPI, uvicorn, requirements.txt); Key concepts demonstrated are: never hardcode secrets, use a managed secrets store to centralize and encrypt credentials, apply least‑privilege IAM policies, automating secret rotation, practice Git hygiene and removing secrets from history when leaks occur, fetch secrets at runtime instead of storing them in config files, and follow local‑dev best practices to isolate dependencies and keep sensitive data out of source control.
Project reflection
This project took me approximately an hour. The most challenging part was safely removing hardcoded credentials from the repository history and configuring Secrets Manager and IAM so the app could retrieve secrets at runtime. It was most rewarding to replace insecure config files with a managed secrets workflow and verify the app could access S3 using least‑privilege credentials.
I chose to do this project today because I wanted to demonstrate practical skills in securing application credentials and show I can move a demo app from insecure config files to a managed‑secrets workflow; something that would make learning with NextWork even better is more hands‑on, bite‑sized labs with step‑by‑step remediation (including sample IAM policies, Secrets Manager rotation examples, and CI/CD checks) plus instant feedback or badges to validate each completed task.
Hardcoding credentials
In this project, a sample web app is exposing AWS credentials and other secrets directly in source files config.py. Hardcoding credentials is unsafe because anyone with repo access or attackers who find the repo can steal keys to impersonate the app, access or delete data, run costly resources, or escalate privileges; secrets also persist in Git history. Use a managed secrets store, least‑privilege IAM, and rotate/revoke compromised keys.
I put placeholder credentials and config values into config.py to simulate an insecure app for the exercise: example AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY, an AWS_REGION, a database connection string stub (e.g., DB_HOST, DB_USER, DB_PASS), and a DEBUG=True flag. These are fake/example values so you can reproduce the risk of hard‑coding without exposing real secrets; the goal is to demonstrate detection, removal, and replacement with a secure store (Secrets Manager), then rotate and revoke any real keys if they were used.
Using my own AWS credentials
As an extension for this project, I also decided to install fastapi==0.115.8 to build a lightweight and efficient web API, uvicorn==0.34.0 as the ASGI server to run FastAPI applications, boto3==1.36.20 to interact with AWS services like Secrets Manager, and python-multipart==0.0.5 to handle file uploads in API requests—all essential for securely managing secrets while building and deploying the application.
When I first ran the app, I ran into an error because the AWS Access Key ID I provided was invalid, meaning it didn’t match any credentials in AWS’s records. This happens when the key is mistyped, deleted, or not properly configured, and it prevents the app from authenticating to AWS to list the S3 buckets.
To resolve the InvalidAccessKeyId error, I updated the config.py file to include the correct AWS credentials. It now contains the proper AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_REGION values, ensuring the app can authenticate with AWS and successfully list the S3 buckets.
Pushing Insecure Code to GitHub
Once I updated the web app code with credentials, I forked the repository because I wanted my own independent copy under my GitHub account where I could make changes without affecting the original project. A fork is different from a clone because a fork stays linked to the original repository on GitHub, allowing me to propose changes back through pull requests, while a clone is just a local copy on my machine with no direct connection to the original.
To connect my local repository to the forked one, I first realized git add was pointing to the original repo I didn’t have permission to write to because I initially cloned directly to my IDE. So I used git remote set-url origin https://github.com/hyelngtil/nextwork-security-secretsmanager.git to update the remote to my fork. Then I ran git remote -v to confirm the change, followed by git add and git commit to stage and save my updates locally. Finally, git push to upload those commits to my forked repository on GitHub.
GitHub blocked my push because there were hardcoded secrets in my config.py file, which triggered its secret scanning protection. This is a good security feature because it prevents sensitive credentials from being exposed publicly, protecting both my AWS account and anyone who might accidentally use those leaked keys.
Secrets Manager
Secrets Manager is an AWS service designed to securely store, manage, and automatically rotate sensitive information like credentials, API keys, and database passwords. I'm using it to store my AWS access keys so they aren’t hardcoded in my application files, which keeps them protected and retrievable only when needed. Other common use cases include managing database connection strings, third‑party API tokens, and encryption keys, all while ensuring secrets remain encrypted and access is tightly controlled.
Another feature in Secrets Manager is secret rotation, which means the service can automatically update and replace stored credentials (like AWS keys or database passwords) on a schedule without manual intervention. It's useful in situations where long‑lived credentials could become a security risk, such as database connections or API tokens, because rotation reduces exposure time and ensures that applications always use fresh, valid secrets.
Secrets Manager provides sample code in various languages, like Python, Java, and JavaScript, to show developers how to retrieve secrets directly from their applications. This is helpful because it makes integration straightforward, reduces the risk of exposing credentials in code, and ensures that applications can securely access secrets without manual handling.
Updating the web app code
I updated the config.py file to retrieve credentials directly from AWS Secrets Manager instead of hardcoding them. The get_secret() function will connect to Secrets Manager, fetch the stored secret containing my AWS access keys, and return those values securely at runtime. This way, the application uses credentials managed by AWS rather than exposing them in the source code.
I also added code to config.py to extract the individual values—like AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_REGION from the JSON secret retrieved by get_secret(). This is important because the application needs those specific fields to authenticate with AWS services, and by parsing them out of the secret object, the app can use them securely at runtime without ever exposing the raw credentials in the source code.
Resetting the repository
Git reset is a Git command that lets you move your current branch pointer to a different commit and optionally change the state of your working directory and staging area. I used it to roll back the repository to a clean state before the commit that contained my hardcoded secrets. This was necessary because those secrets had already been staged and committed, and I needed to remove them from the commit history so they wouldn’t be pushed to GitHub or exposed publicly.
A merge conflict occurred during rebasing because rewriting the branch history left the working tree and the new base out of sync and both versions modified the same lines in config.py. I resolved it by checking git status and git diff, manually editing config.py to keep the Secrets Manager changes and remove the hardcoded keys, staging and committing the fix, and then updating the remote so the exposed credentials were removed.
Once the merge conflict was resolved, I verified that my hardcoded credentials were out of sight in the repository by checking the files with git status and git diff to confirm no sensitive lines remained, reviewing the commit history with git log to ensure the secrets were removed from past commits, and inspecting the remote repository on GitHub to confirm that only the updated config.py with Secrets Manager integration was present.
Key Takeaways
Zero-Trust Secret Handling: Refactored a FastAPI web application to remediate architectural vulnerabilities caused by hard-coded credentials in config.py. Implemented dynamic, runtime retrieval of AWS access keys and database strings via AWS Secrets Manager, mitigating the risk of source control credential leaks.
Git History Sanitization: Utilized git reset and interactive rebasing to purge compromised commits from the Git metadata. Successfully resolved merge conflicts to establish strict Git hygiene and bypass GitHub Secret Scanning blockers during remote pushes.
Least-Privilege & Automation: Designed minimal AWS IAM policies to grant the application restrictive access to secrets, while successfully configuring and validating automated secret rotation loops.
Top comments (0)