Skip to content

Prerequisites

  • All steps in "Prepare Lambda Environment".
  • All steps in "Public threat intelligence feed parser (Lambda)".

Network topology

Base Layout
(Click to zoom)

Overview

This guide creates the private-db-threat-intelligence-feed-pull AWS Lambda function:

This Lambda function runs when Amazon S3 creates new .json objects in your feed bucket (the same bucket public-threat-intelligence-feed-parser writes to). For each S3 event record, calls GetObject, parses the JSON document, and inserts a row into dashboard.panel_feed via psycopg2 (db.py).

This function runs inside the VPC on the Private Web Subnet so it can reach PostgreSQL on a private address.

From a subnet without a NAT path to the internet, S3 must be reachable via an S3 gateway VPC endpoint . Without that, GetObject can hang until the Lambda times out even though IAM is correct.

Exercise code

Download the Python sources (and any packaged .zip file there) from the exercise repository:

private-db-threat-intelligence-feed-pull on GitHub

Step 1: Create the Lambda function

  1. Open the Lambda console ➔ Create function.
  2. Choose Author from scratch.
  3. Configure:
  4. Function name: private-db-threat-intelligence-feed-pull
  5. Runtime: Python 3.12
  6. Architecture: x86_64
  7. Execution role: Use an existing roleprojectx-lambda-feed-exec-role
  8. Choose Create function.

Step 2: General configuration

Under ConfigurationGeneral configurationEdit:

  • Description (suggested): S3-triggered ingest of threat intel JSON into dashboard.panel_feed (VPC).
  • Timeout: 1 minute. (this is good for what we are doing).
  • Memory: 256 MB (may need to adjust to higher if you see timeout errors).

Save.

Step 3: Environment variables

Under ConfigurationEnvironment variablesEdit, set at least:

Variable Required Description
EC2_ENDPOINT Yes PostgreSQL host reachable from the private web subnet (typically the private IP of projectx-prod-websvr-public, e.g. 10.0.x.x).
EC2_DB_NAME No Database name (default in code: projectxdb).
EC2_USERNAME Yes Database user (e.g. webapp_rw or projectx_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.

Example:

EC2_ENDPOINT=10.0.0.240
EC2_DB_NAME=projectxdb
EC2_USERNAME=webapp_rw
EC2_PASSWORD=your-secret
EC2_PORT=5432
EC2_SSLMODE=require

Save.

WA101 Neon / Fly.io

For the portfolio deploy path (Neon + Fly.io), use the web-attacks-101 copy of this function with NEON_DATABASE_URL — see Deploy the threat intelligence dashboard (Fly.io + Neon).

Step 4: Deployment package (.zip)

  1. Download the Python files from private-db-threat-intelligence-feed-pull on GitHub (for example lambda_function.py and db.py).

  2. The repository may include private-db-threat-intelligence-feed-pull.zip in the parent threat_intelligence_lambda_functions directory with those modules at the zip root; you can upload that file directly if present.

  3. In Lambda CodeUpload from.zip file, upload the zip.

  4. Confirm Runtime settingsHandler is lambda_function.lambda_handler.

Step 5: Attach the Lambda layer

Under CodeLayersAdd a layer:

  • Choose Customthreat-intel-lambda-layer
  • Compatible runtime must include Python 3.12.

Step 6: VPC (Private Web Subnet)

Attach this function to the Private Web Subnet so it can reach PostgreSQL on a private IP.

Under ConfigurationVPCEdit:

  • VPC: projectx-prod-vpc
  • Subnets: Private Web Subnet
  • Security groups: projectx-lambda-feed-SG.

Save and wait for ENI configuration to finish.

Step 7: S3 event notifications

Configure the same bucket public-threat-intelligence-feed-parser uses so that new .json objects invoke private-db-threat-intelligence-feed-pull.

Option A — S3 bucket console

  1. Open S3 ➔ your feed bucket ➔ PropertiesEvent notificationsCreate event notification.
  2. Name: e.g. threat-intel-feed-ingest
  3. Prefix (optional): match Lambda 1’s key prefix if you use one (for example threat-intel/); leave empty for the whole bucket.
  4. Suffix: .json
  5. Event types: s3:ObjectCreated:* (or Put / Post / CompleteMultipartUpload as needed).
  6. Destination: Lambda functionprivate-db-threat-intelligence-feed-pull
  7. Confirm the prompt so S3 may invoke the function.

Option B — Lambda console

  1. Open private-db-threat-intelligence-feed-pullAdd triggerS3.
  2. Select the bucket, object created event type, optional prefix/suffix, enable the trigger, and save.

Do not configure this function to write objects that match the same prefix/suffix pattern.

Step 9: Test event

Under TestCreate new event, name it testpull, use a JSON body shaped like an S3 notification (adjust bucket, key, and Region):

{
  "Records": [
    {
      "eventSource": "aws:s3",
      "awsRegion": "us-east-2",
      "s3": {
        "bucket": { "name": "threat-intelligence-feed-log-bucket" },
        "object": { "key": "2026-03-31/security_news_rss.json" }
      }
    }
  ]
}

👉 The key must match a real object in the bucket (upload one with public-threat-intelligence-feed-parser or manually).

Run a Test. A successful invocation returns HTTP 200 (or 207 if some records in a batch fail) in the function result, dashboard.panel_feed receives a new row, and CloudWatch shows GetObject followed by a successful database connection.

Check CloudWatch Logs for the log group /aws/lambda/private-db-threat-intelligence-feed-pull if anything fails.

Finished!

We are now finished with our entire threat intelligence feed set up, complete with a public and private segmentation, using an S3 bucket as an interface.

In Web & Attacks 101, we will build the feeds out, while learning about relevant security attacks and defenses. You are now ready to move onto the next session.