A security-focused DevSecOps project demonstrating how to build, test, containerize, scan, and provision cloud infrastructure for a Python API.
The project combines FastAPI, Docker, Terraform, GitHub Actions, and AWS-compatible services running locally through LocalStack. It applies automated security checks throughout the development workflow without requiring a billing-enabled AWS account.
The API accepts text artifacts and stores them in an S3-compatible bucket protected by a customer-managed KMS encryption key.
Protected API endpoints require a valid API key supplied through the X-API-Key request header. Authorized clients can upload, list, retrieve, and delete artifacts. Terraform defines the infrastructure and its security controls as code, while GitHub Actions automatically tests and scans every change.
The project demonstrates:
- Containerized API development with Docker
- API-key authentication
- Secure secret management using environment variables and GitHub Actions secrets
- AWS infrastructure provisioning with Terraform
- Encrypted object storage using S3 and KMS
- Least-privilege IAM permissions
- Authenticated artifact upload, listing, retrieval, and deletion
- Automated unit testing with Pytest
- Automated LocalStack integration testing
- Dependency vulnerability scanning with pip-audit
- Python static security analysis with Bandit
- Container and Terraform scanning with Trivy
- Continuous integration with GitHub Actions
- Pull-request-based development
flowchart LR
Developer[Developer] -->|Push code| GitHub[GitHub Repository]
GitHub --> CI[GitHub Actions CI]
CI --> Tests[Pytest Unit Tests]
CI --> CodeScan[Bandit and pip-audit]
CI --> ImageScan[Docker Build and Trivy Scan]
CI --> IaCScan[Terraform Validation and Trivy Scan]
CI --> IntegrationTest[LocalStack Integration Test]
Client[API Client] -->|X-API-Key Header| API[FastAPI Container]
API --> Auth[API-Key Validation]
Auth -->|Authorized Request| Boto3[Boto3 Client]
Boto3 --> S3[LocalStack S3]
S3 -->|Encrypt Objects| KMS[Customer-Managed KMS Key]
Terraform[Terraform] --> S3
Terraform --> KMS
Terraform --> IAM[Least-Privilege IAM Role]
- A client sends a request to the FastAPI application.
- The API checks the
X-API-Keyrequest header. - Requests with missing or incorrect credentials are rejected.
- For uploads, the API validates the artifact name and content.
- Boto3 sends artifact operations to the local S3-compatible service.
- Stored objects are encrypted using the customer-managed KMS key.
- Authorized clients can list, retrieve, and delete stored artifacts.
- Missing artifacts return a controlled
404 Not Foundresponse instead of exposing storage-layer errors.
| Area | Control | Implementation |
|---|---|---|
| API security | API-key authentication | Artifact endpoints require a valid X-API-Key header |
| Secret management | Environment isolation | Local secrets are stored in an ignored .env file |
| CI secret management | Encrypted repository secrets | API and LocalStack credentials are stored as GitHub Actions secrets |
| Credential comparison | Timing-safe comparison | API keys are checked using secrets.compare_digest |
| Data protection | Encryption at rest | S3 objects use a customer-managed KMS key |
| Key management | Automatic key rotation | KMS key rotation is enabled in Terraform |
| Storage security | Public access prevention | All four S3 public-access-block settings are enabled |
| Transport security | HTTPS-only access | The bucket policy denies requests using insecure transport |
| Data recovery | Object versioning | S3 bucket versioning preserves previous object versions |
| Access control | Least privilege | The API role is limited to the required S3 list, upload, read, delete, and KMS operations |
| Encrypted retrieval | KMS decryption | The API role receives kms:Decrypt only for the project KMS key |
| Input security | Request validation | Artifact names and content sizes are restricted using Pydantic |
| Missing-object handling | Controlled 404 responses |
Retrieval and deletion of missing artifacts return 404 Not Found |
| Application security | Safe error handling | Storage failures return a controlled HTTP 503 response |
| Container security | Non-root execution | The API container runs as an unprivileged application user |
| Network exposure | Localhost binding | API and LocalStack ports are bound to 127.0.0.1 |
| CI permissions | Read-only repository access | GitHub Actions receives only contents: read permission |
| Dependency security | Vulnerability auditing | pip-audit checks production dependencies for known vulnerabilities |
| Source security | Static analysis | Bandit scans Python source code for insecure patterns |
| Image security | Container scanning | Trivy blocks builds containing high or critical vulnerabilities |
| Infrastructure security | IaC scanning | Terraform is formatted, validated, and scanned by Trivy |
The GitHub Actions workflow runs on pushes and pull requests targeting the main branch.
The workflow is separated into three jobs.
- Check out the repository without preserving Git credentials.
- Set up the required Python version.
- Install the pinned Python dependencies.
- Audit production dependencies with pip-audit.
- Scan Python source code with Bandit.
- Run the automated Pytest suite.
- Build the Docker image.
- Scan the completed image with Trivy.
- Check out the repository.
- Install the required Terraform version.
- Verify Terraform formatting.
- Initialize Terraform without a remote backend.
- Validate the Terraform configuration.
- Scan the infrastructure code for high and critical misconfigurations.
The integration job starts only after the application and infrastructure jobs pass.
It then:
- Starts a temporary LocalStack environment.
- Initializes and applies the Terraform configuration.
- Creates the local S3, KMS, and IAM resources.
- Sends an authenticated request through FastAPI.
- Stores an artifact through Boto3 in LocalStack S3.
- Retrieves the stored object.
- Verifies its content.
- Confirms that KMS encryption was applied.
- Deletes the temporary test object.
A failed test, vulnerability check, container scan, infrastructure scan, or integration test stops the workflow.
The artifact endpoints require an API key.
Clients must send the key in this HTTP header:
X-API-Key: your-api-key
Authentication behavior:
- Missing API key:
401 Unauthorized - Incorrect API key:
401 Unauthorized - Authentication not configured on the server:
503 Service Unavailable - Correct API key: the request continues normally
The /health endpoint remains public so monitoring systems can check whether the service is running.
The API key must never be committed to Git or included in screenshots, logs, or documentation.
| Method | Endpoint | Authentication | Purpose | Successful response |
|---|---|---|---|---|
GET |
/health |
Public | Check whether the API is running | 200 OK |
POST |
/artifacts |
API key required | Validate and store a text artifact | 201 Created |
GET |
/artifacts |
API key required | List stored artifact names | 200 OK |
GET |
/artifacts/{name} |
API key required | Retrieve a stored artifact and its content | 200 OK |
DELETE |
/artifacts/{name} |
API key required | Delete a stored artifact | 200 OK |
GET |
/docs |
Public | Open the interactive API documentation | 200 OK |
{
"name": "security-report.txt",
"content": "No critical vulnerabilities found."
}curl --request POST \
--url http://localhost:8000/artifacts \
--header "Content-Type: application/json" \
--header "X-API-Key: $API_KEY" \
--data '{
"name": "security-report.txt",
"content": "No critical vulnerabilities found."
}'Expected response:
{
"name": "security-report.txt",
"status": "stored"
}curl \
--header "X-API-Key: $API_KEY" \
http://localhost:8000/artifactscurl \
--header "X-API-Key: $API_KEY" \
http://localhost:8000/artifacts/security-report.txtExpected response:
{
"name": "security-report.txt",
"content": "No critical vulnerabilities found."
}If the artifact does not exist, the API returns:
{
"detail": "Artifact not found"
}with HTTP status 404 Not Found.
curl --request DELETE \
--header "X-API-Key: $API_KEY" \
http://localhost:8000/artifacts/security-report.txtExpected response:
{
"name": "security-report.txt",
"status": "deleted"
}If the artifact does not exist, the API returns 404 Not Found.
The automated unit test suite contains twelve tests covering:
- API health checking
- Successful artifact uploads
- Artifact listing
- Successful artifact downloads
404 Not Foundresponses for missing downloads- Successful artifact deletion
404 Not Foundresponses for attempts to delete missing artifacts- Rejection of unsafe artifact names
- Controlled behavior when storage is unavailable
- Rejection of missing API keys
- Rejection of incorrect API keys
- Controlled behavior when authentication is not configured
Unit tests replace the real storage connection with temporary fake functions. This keeps the basic test suite fast and allows application behavior to be tested without starting LocalStack.
A separate integration test verifies the complete flow:
FastAPI → API-key authentication → Boto3 → LocalStack S3 → KMS encryption
Unlike the unit tests, the integration test uses real local infrastructure created by Terraform. It verifies that:
- The authenticated upload request succeeds
- The artifact appears in the S3 bucket
- The stored content matches the uploaded content
- The object uses KMS encryption
- The temporary test object is deleted afterward
The download and deletion endpoints were also verified manually against the running LocalStack environment to confirm real S3 retrieval, successful deletion, and 404 Not Found behavior for missing artifacts.
Run the unit tests locally with:
python -m pytest -vRun the LocalStack integration test with:
RUN_INTEGRATION_TESTS=1 \
AWS_ACCESS_KEY_ID=test \
AWS_SECRET_ACCESS_KEY=test \
AWS_DEFAULT_REGION=us-east-1 \
API_KEY="$API_KEY" \
python -m pytest tests/integration/test_localstack.py -vLocalStack must be running and the Terraform infrastructure must be applied before running the integration test.
Install the following tools before starting:
- Git
- Python
- Docker Desktop with Docker Compose
- Terraform
1.15.8 - A LocalStack authentication token
No billing-enabled AWS account is required.
- Clone the repository:
git clone https://github.com/abdulrahmancoding/secure-api-devsecops.git
cd secure-api-devsecops- Create the local environment file:
cp .env.example .env- Open
.envand configure:
LOCALSTACK_AUTH_TOKEN=your-localstack-token
API_KEY=your-secure-random-api-key
Generate a secure API key with:
python -c "import secrets; print(secrets.token_urlsafe(32))"Never commit .env, display its contents publicly, or include its secrets in screenshots.
- Start LocalStack:
docker compose up --detach localstack
docker compose psWait until LocalStack reports a healthy status.
- Initialize and apply the Terraform infrastructure:
terraform -chdir=infrastructure init
terraform -chdir=infrastructure applyReview the plan and enter yes when prompted.
- Build and start the API:
docker compose up --detach --build api
docker compose ps- Verify the public health endpoint:
curl http://localhost:8000/healthExpected response:
{"status":"healthy"}- Load the environment variables into the current terminal:
set -a
source .env
set +a- Verify an authenticated endpoint:
curl \
--header "X-API-Key: $API_KEY" \
http://localhost:8000/artifacts- Open the interactive API documentation:
http://localhost:8000/docs
Click Authorize, enter the API key, and then test the protected endpoints.
Do not share screenshots containing the generated API key or Swagger curl commands containing the X-API-Key header.
docker compose downThe AWS resources exist only inside the local environment and do not create charges in a real AWS account.
Run the same major checks locally before committing changes:
python -m pytest -v
bandit --recursive app
python -m pip_audit --requirement requirements.txt
terraform -chdir=infrastructure fmt -check
terraform -chdir=infrastructure validate
git diff --checkThese commands verify application behavior, scan the source code and dependencies, validate the infrastructure, and detect formatting problems.
| Category | Technology |
|---|---|
| API | Python, FastAPI, Pydantic |
| Authentication | API key through the X-API-Key header |
| AWS integration | Boto3 |
| Containers | Docker, Docker Compose |
| Local cloud environment | LocalStack |
| Infrastructure as Code | Terraform |
| Cloud services | Amazon S3, AWS KMS, AWS IAM |
| Automated testing | Pytest |
| Security scanning | Bandit, pip-audit, Trivy |
| Continuous integration | GitHub Actions |
| Version control | Git and GitHub |
secure-api-devsecops/
├── .github/
│ └── workflows/
│ └── ci.yml
├── app/
│ ├── main.py
│ ├── security.py
│ └── storage.py
├── infrastructure/
│ ├── .terraform.lock.hcl
│ └── main.tf
├── tests/
│ ├── integration/
│ │ └── test_localstack.py
│ ├── test_artifacts.py
│ └── test_health.py
├── .dockerignore
├── .env.example
├── .gitignore
├── compose.yaml
├── Dockerfile
├── requirements-dev.txt
├── requirements.txt
└── README.md
app/main.pydefines the API endpoints and request validation.app/security.pyvalidates API keys.app/storage.pyconnects the application to S3 through Boto3 and implements upload, list, retrieval, and deletion operations.infrastructure/contains the AWS-compatible Terraform configuration and least-privilege S3/KMS permissions.tests/contains the unit and integration tests..github/workflows/ci.ymldefines the CI and security pipeline.compose.yamlconnects the API container to LocalStack..env.exampledocuments the required environment variables without exposing real secrets.
The project currently provides:
- A containerized FastAPI service
- Protected artifact-management endpoints
- API-key authentication
- Authenticated artifact upload and listing
- Authenticated artifact retrieval
- Authenticated artifact deletion with missing-object handling
- Secure S3-compatible artifact storage
- Customer-managed KMS encryption
- Least-privilege S3 list, upload, read, and delete permissions
- KMS decryption permission for encrypted artifact retrieval
- S3 object versioning for recovery
- Terraform-based infrastructure provisioning
- Automated unit and integration testing
- Automated source, dependency, image, and infrastructure scanning
- A multi-job GitHub Actions pipeline
- Pull-request-based development with CI checks before merge
- Local development without a billing-enabled AWS account
The infrastructure follows AWS-compatible patterns but has not been deployed to a production AWS environment.
Potential future improvements include:
- Adding a PostgreSQL database for artifact metadata
- Replacing the single API key with user-specific authentication and authorization
- Adding API-key rotation and expiration
- Adding rate limiting to protect the API from abuse
- Adding structured security logs and request identifiers
- Adding code coverage and automated secret scanning
- Adding S3 lifecycle rules to expire old noncurrent object versions and delete markers for storage cost optimization
- Adding monitoring, metrics, dashboards, and alerts
- Deploying the container to Amazon ECS or another managed platform
- Using GitHub OpenID Connect for secure AWS deployment authentication
- Storing Terraform state in a secured remote backend
- Adding separate development, staging, and production environments
- Adding automated API documentation and release versioning
- Adding policy-as-code checks with tools such as Checkov or Open Policy Agent
Building this project provided practical experience with:
- Designing a complete DevSecOps workflow
- Building and containerizing a Python API
- Protecting endpoints with API key authentication
- Managing secrets safely in local and CI environments
- Provisioning AWS-compatible resources using Terraform
- Applying S3, KMS, IAM, and network security controls
- Designing least-privilege permissions as application capabilities evolve
- Implementing authenticated object upload, listing, retrieval, and deletion
- Handling missing cloud objects with controlled HTTP responses
- Writing unit and end-to-end integration tests
- Testing real interactions between FastAPI, Boto3, S3, and KMS
- Scanning source code, dependencies, containers, and infrastructure
- Building multi-job GitHub Actions workflows
- Debugging Docker, WSL, YAML, Terraform, LocalStack, and CI failures
- Using feature branches and pull requests to deliver changes safely
- Considering the tradeoff between S3 versioning for recovery and lifecycle management for future cost optimization
Abdulrahman Abuzeid
GitHub: abdulrahmancoding