case study
Serverless (Lambda)
I built a serverless AWS threat-intelligence pipeline using Lambda, S3, IAM, VPC networking, and PostgreSQL. I configured the supporting Lambda environment and permissions, deployed a public Lambda function to collect threat-intelligence data and store it as JSON in S3, and used S3 events to trigger a private Lambda function that ingested the data into PostgreSQL. I then deployed the Threat Intelligence Dashboard and verified the complete workflow from public feed collection through database ingestion to displaying the data in the application.
#Prepare Lambda Environment
1.Configure Lambda S3 Permissions
I configured the Lambda execution role with scoped S3 permissions allowing the threat-intelligence functions to read and write objects in the dedicated threat-intelligence feed bucket.
2.Create Shared Lambda Layer
I created a shared Lambda layer containing the Python dependencies required by the threat-intelligence functions. Packaging the dependencies as a reusable layer keeps the individual Lambda deployment packages smaller and allows multiple functions to use the same libraries.
#Create threat-intelligence-feed-parser-bucket
1.Create Threat Intelligence S3 Bucket
I created a dedicated S3 bucket to act as the storage interface between the threat-intelligence Lambda functions.
2.Configure S3 VPC Endpoint
I configured an S3 Gateway VPC endpoint to provide private S3 connectivity from the ProjectZero VPC.
#Configure IAM Role for Lambda Functions
I configured a dedicated IAM execution role for the Lambda functions, providing basic execution permissions and scoped read/write access to the threat-intelligence S3 bucket. This allows the functions to interact with the required AWS resources without using hardcoded credentials.
1.Create Lambda Execution Role
I configured AWS Lambda as the trusted service, allowing Lambda functions to assume the execution role.
2.Attach Basic Lambda Execution Permissions
I created the projectzero-lambda-feed-exec-role and attached the AWSLambdaBasicExecutionRole managed policy to provide basic Lambda execution and CloudWatch logging permissions.
3.Configure Scoped S3 Access
I created a custom IAM policy granting the Lambda functions s3:GetObject and s3:PutObject permissions for the threat-intelligence S3 bucket, then attached the policy to the Lambda execution role.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ThreatIntelFeedBucketReadWriteObjects",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::threat-intelligence-feed-log-bucket/*"
}
]
}#Prepare web server PostgreSQL for Lambda Ingest
I prepared the web server and PostgreSQL database to support threat-intelligence ingestion from AWS Lambda. This included restricting database traffic through security groups, enabling PostgreSQL network connectivity, configuring authenticated database access, and preparing the threat-intel-app to securely connect to projectzerodb.
1.Configure Lambda-to-PostgreSQL Network Access
I configured a dedicated security group for the Lambda functions and allowed PostgreSQL traffic from the Lambda security group to the web server on TCP port 5432.
2.Configure PostgreSQL Network Listener
I updated PostgreSQL to listen for network connections so the database could accept traffic from Lambda within the VPC.
sudo nano /etc/postgresql/17/main/postgresql.conf
3.Authorize Lambda Database Access
I configured PostgreSQL host-based authentication to allow the webapp_rw account to connect to projectzerodb from the VPC network using TLS and SCRAM-SHA-256 authentication.
sudo nano /etc/postgresql/17/main/pg_hba.conf
4.Prepare Threat Intelligence Application
I deployed the updated threat-intel-app to the web server and configured the application with the PostgreSQL connection parameters required to access projectzerodb.
Add .env File to threat-intel-app
I created a .env file to define the application's PostgreSQL connection settings.
DB_HOST='Private IP Address'
DB_PORT=5432
DB_NAME=projectzerodb
DB_USER='webapp_rw'
DB_PASSWORD='PASSWORD'
DB_SSLMODE=requireprojectzerodb using the restricted webapp_rw account with SSL required.#Public Threat Intelligence Feed Parser
I deployed a Lambda function to retrieve data from public threat-intelligence sources, process the feeds, and write normalized JSON snapshots to the S3 threat-intelligence bucket. I configured the Lambda runtime, execution role, environment variables, dependencies, and deployment package, then validated the workflow by invoking a test feed and confirming that the resulting JSON objects were successfully generated and stored in S3.
1.Create Public Threat Intelligence Lambda
I created the public-threat-intelligence-feed-parser Lambda function using Python 3.12 and assigned the existing projectzero-lambda-feed-exec-role execution role.
2.Configure Lambda Runtime
I configured the function with 256 MB of memory and a one minute timeout to support external threat-intelligence retrieval and processing.
3.Configure Environment Variables
I configured the environment variables used by the Lambda function to define the S3 output destination and threat-intelligence feed settings.
| Variable | Required | Description |
EC2_ENDPOINT | Yes | PostgreSQL host reachable from the private web subnet (typically the private IP of projectzero-prod-websvr-public, e.g. 10.0.x.x). |
EC2_DB_NAME | No | Database name (default in code: projectzerodb). |
EC2_USERNAME | Yes | Database user (e.g. webapp_rw or pz_dbadmin per your lab). |
EC2_PASSWORD | Yes | User password (prefer Secrets Manager or Parameter Store in production). |
EC2_PORT | No | Default 5432. |
EC2_SSLMODE | No | Default require; use disable only if your lab explicitly uses non-TLS local access. |
Database Ingest Configuration Example
The database-ingest Lambda uses the following environment variable structure to establish an authenticated PostgreSQL connection from the Lambda function.
EC2_ENDPOINT=10.0.0.240
EC2_DB_NAME=projectzerodb
EC2_USERNAME=webapp_rw
EC2_PASSWORD=your-secret
EC2_PORT=5432
EC2_SSLMODE=require4.Deploy Lambda Function Code
I uploaded the deployment package containing the Python modules used to retrieve, process, and store public threat-intelligence feeds.
5.Verify Lambda Function Code
I verified that the deployment package was successfully extracted and that the Lambda function contained the handler and individual threat-intelligence feed modules required by the parser.
6.Attach Shared Lambda Layer
I attached the threat-intel-lambda-layer to provide the third party Python dependencies required by the feed processing modules.
7.Create Test Event
I created a test event using the security_news_rss panel key with a maximum of five results to validate the feed-processing workflow.
8.Validate Lambda Execution
I sccessfully invoked the Lambda function and verified that the processed threat-intelligence data was written to the configured S3 bucket. The function created JSON objects for the requested feeds, confirming that the parser could retrieve the source data, process it, and store the resulting output in S3.
#Private DB Threat Intelligence Feed Pull
I deployed the private-db-threat-intelligence-feed-pull Lambda function to ingest the JSON threat-intelligence objects generated by the public parser into the PostgreSQL dashboard.panel_feed table. I configured the function with database connection parameters, required dependencies, VPC networking and execution permissions, and an S3 event trigger so new threat-intelligence objects could be automatically retrieved from S3 and written to the database. I validated the complete ingestion workflow with a successful statusCode: 200, confirming S3 object retrieval, private PostgreSQL connectivity, and successful database insertion.
1.Create Private Database Ingest Lambda
I created the private-db-threat-intelligence-feed-pull Lambda function using Python 3.12 and assigned the existing projectzero-lambda-feed-exec-role. This function serves as the private ingestion component of the pipeline, processing JSON objects generated by the public threat-intelligence parser and writing the data to PostgreSQL.
2.Configure Lambda Runtime
I configured the function with 256 MB of memory and a one-minute timeout to provide sufficient resources for retrieving JSON objects from S3 and inserting the processed data into PostgreSQL.
3.Configure Database Environment Variables
I configured environment variables containing the PostgreSQL connection parameters required by the Lambda function. Separating these values from the application code allows the database endpoint, database name, credentials, port, and TLS settings to be managed independently from the deployment package.
| Variable | Required | Description |
EC2_ENDPOINT | Yes | PostgreSQL host reachable from the private web subnet (typically the private IP of projectzero-prod-websvr-public, e.g. 10.0.x.x). |
EC2_DB_NAME | No | Database name (default in code: projectzerodb). |
EC2_USERNAME | Yes | Database user (e.g. webapp_rw or pz_dbadmin per your lab). |
EC2_PASSWORD | Yes | User password (prefer Secrets Manager or Parameter Store in production). |
EC2_PORT | No | Default 5432. |
EC2_SSLMODE | No | Default require; use disable only if your lab explicitly uses non-TLS local access. |
Database Connection Configuration
EC2_ENDPOINT=10.0.0.240
EC2_DB_NAME=projectzerodb
EC2_USERNAME=webapp_rw
EC2_PASSWORD=your-secret
EC2_PORT=5432
EC2_SSLMODE=require4.Deploy Private Lambda Function Code
I uploaded the deployment package containing the Python modules responsible for retrieving threat-intelligence JSON objects from S3 and ingesting the data into PostgreSQL.
5.Attach Lambda Dependency Layer
I attached the custom threat-intel-lambda-layer, providing the Python dependencies required by the function, including the PostgreSQL client library used to connect to the database.
6.Enable Lambda VPC Access
I added AWSLambdaVPCAccessExecutionRole to the Lambda execution role, allowing the function to create and manage the network interfaces required to operate inside the VPC.
7.Configure S3 Event Trigger
I configured the threat-intelligence S3 bucket as an event source, allowing newly created threat-intelligence objects to automatically invoke the private ingestion Lambda. This creates an event-driven pipeline between S3 and the private Lambda function, automatically processing new threat-intelligence data as it becomes available.
8.Validate S3-to-PostgreSQL Ingestion
I tested the complete ingestion workflow and received a statusCode: 200, confirming the Lambda function executed successfully. The execution logs verified that the function retrieved the threat-intelligence JSON object from S3, connected to PostgreSQL over the private VPC network, and inserted the processed data into dashboard.panel_feed.
#Deploy Threat Intelligence Application
I deployed the Threat Intelligence Dashboard on the EC2 web server and validated the complete data flow from the public threat-intelligence feed to the application. The public Lambda processed the security_news_rss feed and stored the JSON output in S3, which triggered the private Lambda to ingest the data into PostgreSQL. I then accessed the deployed application and confirmed the Security News RSS data was successfully displayed on the dashboard.
1.Build and Launch Threat Intelligence Application
I built the updated threat-intel-app and launched the Astro application on the web server, exposing the application through the server's network interface.
npm run build
2.Populate Threeat Intelligence Feeds
I invoked the public threat-intelligence parser using the security_news_rss feed to generate data for the threat-intelligence application. The invocation returned a statusCode: 200, confirming that the RSS feed was successfully retrieved, processed, and written as a JSON object to S3 for downstream ingestion into the application.
3.Trigger Private Database Ingestion
After the public parser wrote the security_news_rss JSON object to S3, the configured S3 event notification automatically triggered the private-db-threat-intelligence-feed-pull Lambda function. The private Lambda then retrieved the JSON object and ingested the threat-intelligence data into PostgreSQL for use by the dashboard.
4.Access the Deployed Application
I retrieved the public IPv4 address of the EC2 web server to access the deployed threat-intelligence application from my browser on port 4321.
5.Verify Threat Intelligence Dashboard
I accessed the deployed Threat Intelligence Dashboard and confirmed that the Security News RSS panel was populated with the feed data processed by the Lambda pipeline. This validated the complete flow from the public feed parser, through S3 and the private ingestion Lambda, into PostgreSQL and finally the web application.
6.Remove Temporary Public Application Access
After validating the deployment, I removed the temporary inbound rule for TCP port 4321 from the web server security group to prevent unnecessary public access to the application.
The port was only required to validate the application directly from the browser. Removing the rule after testing reduces the exposed attack surface of the EC2 instance.
#Why This Matters
This section brought the individual AWS components together into a complete serverless threat-intelligence pipeline. I configured the Lambda environment, IAM permissions, S3 storage, VPC connectivity, and PostgreSQL access needed for the functions to communicate securely. The public Lambda retrieved threat-intelligence data and stored it as structured JSON objects in S3, while S3 events triggered the private Lambda to retrieve those objects and ingest them into PostgreSQL. Finally, I deployed the threat-intelligence application and verified that the stored data was successfully displayed in the dashboard.
This demonstrated how Lambda, IAM, S3, VPC networking, security groups, event-driven triggers, PostgreSQL, and a web application can work together to automate the collection, processing, storage, and presentation of security data.


































