How would you provide secure third-party access to a database in a public subnet?

How would you provide secure third-party access to a database in a public subnet?

Five approaches ranked from simplest to most secure — with architecture diagrams and real-world guidance.


Introduction

A common scenario: your database holds data that a third-party partner needs to access — a payment processor, an analytics vendor, a B2B integration partner. The question isn’t just “how do I connect them?” but “how do I connect them securely?”

Before anything else, let me state the most important point:

Best practice: your database should NOT be in a public subnet. Move it to a private subnet and use one of the methods below to give the third party secure access. If it’s currently public-facing, that’s the first thing to fix.

In this post, I’ll walk through five approaches — from the simplest (IP whitelisting) to the most secure (AWS PrivateLink) — with the trade-offs of each.


The Five Options at a Glance

# Method Internet Exposure Complexity Security Level Best For
1 Security Group IP Whitelist Yes (over internet) Low Basic Quick setup, static IP partner
2 Site-to-Site VPN No (encrypted tunnel) Medium High Partners with own data centre
3 AWS PrivateLink + NLB + RDS Proxy No (AWS backbone) High Highest Cross-account, zero internet
4 Direct Connect No (private fibre) High Highest Large enterprise, high bandwidth
5 Cross-Account IAM Role + RDS IAM Auth Depends on setup Medium High Partner already on AWS

Option 1: Security Group IP Whitelist (Simplest)

“Allow the third party’s specific IP address to reach the database port. Block everything else.”

How It Works

Third Party (Static IP: 203.0.113.50)
        │
        │ Internet (TLS encrypted)
        ▼
Security Group Rule:
  Inbound: TCP 5432 from 203.0.113.50/32 → ALLOW
  Everything else → DENY (implicit)
        │
        ▼
RDS Database (port 5432)

Implementation

resource "aws_security_group_rule" "third_party_access" {
  type              = "ingress"
  from_port         = 5432
  to_port           = 5432
  protocol          = "tcp"
  cidr_blocks       = ["203.0.113.50/32"]   # Partner's STATIC IP only
  security_group_id = aws_security_group.database.id
  description       = "Partner XYZ - DB read access (approved: 2026-07-01)"
}

What You Must Also Do

  • Enforce TLS on the database connection (rds.force_ssl = 1)
  • Create a dedicated database user with read-only access to specific tables only
  • Never use 0.0.0.0/0 — always a specific IP or CIDR
  • Set an expiry/review date — revisit the rule quarterly

Pros and Cons

Pros Cons
Simple to implement (5 minutes) Traffic crosses the public internet
No infrastructure to manage Partner’s IP might change (breaks access)
Works immediately Database must have public endpoint (or NAT route)
No deep packet inspection
Can’t control what the partner does once connected

When to Use

  • Quick proof-of-concept or temporary integration
  • Partner has a static, known IP
  • Low-sensitivity data (not PII or payment data)

Option 2: Site-to-Site VPN (Encrypted Private Tunnel)

“Create an IPsec VPN tunnel between the partner’s network and your AWS VPC. Traffic never touches the public internet.”

How It Works

Third Party Data Centre                    Your AWS VPC
┌──────────────────────┐                  ┌──────────────────────┐
│                      │                  │                      │
│  Partner App ────────┼── IPsec VPN ────►│─── RDS Database     │
│                      │   (encrypted)    │    (private subnet)  │
│  Customer Gateway    │                  │    VPN Gateway       │
│  (their firewall)    │                  │                      │
└──────────────────────┘                  └──────────────────────┘

Implementation Steps

  1. Create a Virtual Private Gateway (VGW) and attach to your VPC
  2. Create a Customer Gateway pointing to the partner’s public IP
  3. Create a VPN Connection between VGW and Customer Gateway (AWS provides config)
  4. Share the VPN configuration with the partner (IKE settings, pre-shared keys)
  5. Update route tables to route partner’s CIDR through the VPN
  6. Add security group rule allowing the partner’s private IP range on port 5432
resource "aws_vpn_connection" "partner" {
  vpn_gateway_id      = aws_vpn_gateway.main.id
  customer_gateway_id = aws_customer_gateway.partner.id
  type                = "ipsec.1"
  static_routes_only  = false   # Use BGP for dynamic routing

  tags = { Name = "partner-xyz-vpn" }
}

resource "aws_customer_gateway" "partner" {
  bgp_asn    = 65000
  ip_address = "198.51.100.1"   # Partner's public IP (their VPN device)
  type       = "ipsec.1"

  tags = { Name = "partner-xyz-cgw" }
}

Pros and Cons

Pros Cons
Traffic never touches public internet Partner needs VPN-capable hardware/software
Encrypted by default (IPsec) Setup takes coordination (both sides configure)
Private IP communication Throughput limited to 1.25 Gbps per tunnel
Database stays in private subnet Ongoing maintenance (tunnel flapping, DPD settings)
Familiar to enterprise network teams Cost: ~$36/month per VPN connection

When to Use

  • Partner has their own data centre or office network
  • You need private, encrypted connectivity
  • Medium to long-term integration
  • Regulatory requirement for no public internet exposure

“Traffic stays entirely on AWS’s private backbone. Never touches the internet. Works even if IP ranges overlap between accounts.”

How It Works

Partner's AWS Account                     Your AWS Account
┌──────────────────────────┐             ┌──────────────────────────┐
│                          │             │                          │
│  Partner App             │             │  RDS Database            │
│       │                  │             │       ▲                  │
│       ▼                  │             │       │                  │
│  VPC Interface Endpoint  │             │  RDS Proxy               │
│  (PrivateLink consumer)  │─── AWS ────►│       ▲                  │
│                          │  backbone   │       │                  │
│  Private IP in their VPC │  (no internet)│  Network Load Balancer │
│                          │             │       ▲                  │
│                          │             │       │                  │
│                          │             │  VPC Endpoint Service    │
│                          │             │  (PrivateLink provider)  │
└──────────────────────────┘             └──────────────────────────┘

Implementation Steps

In YOUR account (provider side):

  1. Set up RDS Proxy in front of your database (stable endpoints, connection pooling)
  2. Create a Network Load Balancer (NLB) pointing to the RDS Proxy
  3. Create a VPC Endpoint Service pointing to the NLB
  4. Accept the partner’s connection request (or auto-accept from allowed accounts)
# NLB targeting RDS Proxy
resource "aws_lb" "rds_proxy_nlb" {
  name               = "partner-db-access"
  internal           = true
  load_balancer_type = "network"
  subnets            = var.private_subnet_ids
}

resource "aws_lb_target_group" "rds_proxy" {
  name        = "rds-proxy-tg"
  port        = 5432
  protocol    = "TCP"
  target_type = "ip"
  vpc_id      = var.vpc_id
}

# VPC Endpoint Service (PrivateLink provider)
resource "aws_vpc_endpoint_service" "database" {
  acceptance_required        = true   # Manually approve connections
  network_load_balancer_arns = [aws_lb.rds_proxy_nlb.arn]

  allowed_principals = [
    "arn:aws:iam::PARTNER_ACCOUNT_ID:root"
  ]
}

In the PARTNER’s account (consumer side):

# They create an Interface Endpoint in their VPC
resource "aws_vpc_endpoint" "partner_db_access" {
  vpc_id              = var.partner_vpc_id
  service_name        = "com.amazonaws.vpce.eu-central-1.vpce-svc-YOUR_SERVICE_ID"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = var.partner_private_subnets
  security_group_ids  = [var.partner_sg_id]
  private_dns_enabled = false
}

Why This Is the Best Option

Benefit Explanation
Zero internet exposure Traffic flows on AWS backbone, never public
Works with overlapping CIDRs Your VPC and partner’s VPC can use the same IP range (10.0.0.0/16) — PrivateLink handles it
Provider controls access You approve/reject endpoint connections. You can revoke at any time
Private IP in partner’s VPC The endpoint gets a private IP in the partner’s network — feels local to them
No firewall/VPN configuration No hardware, no IPsec settings, no tunnel maintenance
Scales infinitely No bandwidth limits like VPN tunnels

Pros and Cons

Pros Cons
Highest security (no internet) More complex setup (NLB + RDS Proxy + Endpoint Service)
Works cross-account and cross-region Cost: NLB (~$16/month) + data processing
No IP overlap issues Partner must also be on AWS
You control who connects Initial setup requires coordination
RDS Proxy adds connection pooling benefit

When to Use

  • This is the recommended approach for any production cross-account database access
  • Partner is on AWS (or can deploy a component on AWS)
  • Sensitive data (PII, payments, healthcare)
  • Compliance requirements (no internet exposure)
  • Long-term integration

Option 4: AWS Direct Connect (Dedicated Private Fibre)

“A physical private fibre connection from the partner’s data centre to AWS. Maximum bandwidth, minimum latency, zero internet.”

How It Works

Partner Data Centre ──── Physical Fibre ────► AWS Direct Connect Location ────► Your VPC
                     (dedicated, private)         (colocation facility)

When to Use

  • Very large data volumes (50+ GB/day)
  • Need consistent low latency (not best-effort like internet)
  • Long-term partnership (setup takes weeks/months)
  • Enterprise partners with colocation presence

Cost

  • Dedicated connection: $0.30/hr (1 Gbps) = ~$220/month + data transfer
  • Hosted connection: available from partners at lower commitment

Pros and Cons

Pros Cons
Highest bandwidth (up to 100 Gbps) Expensive setup and monthly cost
Consistent latency (not internet-variable) Takes weeks to provision (physical)
Private (no internet) Requires colocation facility access
Can layer VPN on top for encryption Overkill for most integrations

Option 5: Cross-Account IAM Role + RDS IAM Authentication

“The partner assumes an IAM role in your account and uses temporary credentials to authenticate to the database. No long-lived passwords exchanged.”

How It Works

Partner's AWS Account                     Your AWS Account
┌──────────────────────┐                 ┌──────────────────────┐
│                      │                 │                      │
│  Partner App         │                 │  IAM Role            │
│       │              │  AssumeRole     │  (cross-account)     │
│       ▼              │────────────────►│       │              │
│  STS:AssumeRole      │                 │       ▼              │
│  (with external ID)  │                 │  RDS IAM Auth Token  │
│                      │                 │       │              │
│                      │                 │       ▼              │
│                      │                 │  RDS Database        │
└──────────────────────┘                 └──────────────────────┘

Implementation

In YOUR account — create a cross-account role:

resource "aws_iam_role" "partner_db_access" {
  name = "partner-xyz-database-readonly"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Principal = {
        AWS = "arn:aws:iam::PARTNER_ACCOUNT_ID:role/their-app-role"
      }
      Action = "sts:AssumeRole"
      Condition = {
        StringEquals = {
          "sts:ExternalId" = "unique-secret-external-id-xyz123"
        }
      }
    }]
  })
}

resource "aws_iam_role_policy" "partner_rds_connect" {
  role = aws_iam_role.partner_db_access.id
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect   = "Allow"
      Action   = "rds-db:connect"
      Resource = "arn:aws:rds-db:eu-central-1:YOUR_ACCOUNT:dbuser:DB_RESOURCE_ID/partner_readonly"
    }]
  })
}

Key security features:

  • External ID — prevents the “confused deputy” attack (another customer of the same vendor can’t trick them into accessing your account)
  • Temporary credentials — STS tokens expire (1 hour default). No long-lived passwords
  • Scoped to specific DB user — partner_readonly has SELECT-only permissions on specific tables
  • CloudTrail audit — every AssumeRole call is logged

When to Use

  • Partner is already on AWS
  • You want zero long-lived credentials exchanged
  • Fine-grained, auditable access
  • Can be combined with PrivateLink for network-level isolation

What to Add on TOP of Any Method (Always)

Regardless of which method you choose, always layer these controls:

Control Why
Enforce TLS/SSL on database connections Encrypt data in transit even on private networks
Least-privilege database user Partner gets SELECT on specific tables, not GRANT ALL
Audit logging Enable RDS audit logs — know what they queried and when
Credential rotation If using passwords: Secrets Manager with 30-day auto-rotation
VPC Flow Logs Monitor the network traffic — detect unusual patterns
Access review schedule Revisit third-party access quarterly. Revoke if no longer needed
NACLs as extra layer Subnet-level deny rules as defence in depth

Decision Flowchart

Does the partner need database access?
    │
    ├── Is the data sensitive (PII, payments)?
    │    │
    │    YES → Use PrivateLink (Option 3) or VPN (Option 2)
    │    │
    │    NO → IP Whitelist (Option 1) may be acceptable for non-sensitive reads
    │
    ├── Is the partner on AWS?
    │    │
    │    YES → PrivateLink (Option 3) + IAM Role (Option 5) = best combination
    │    │
    │    NO → Site-to-Site VPN (Option 2) or Direct Connect (Option 4)
    │
    ├── Is this a long-term integration?
    │    │
    │    YES → PrivateLink or VPN (invest in proper setup)
    │    │
    │    NO → IP Whitelist with TLS (temporary, quick)
    │
    └── Is high bandwidth needed (50+ GB/day)?
         │
         YES → Direct Connect (Option 4)
         │
         NO → VPN or PrivateLink

Summary: Ranked by Security

Rank Method Security Effort Monthly Cost
1 (Best) PrivateLink + RDS Proxy No internet, provider-controlled High initial, low ongoing ~$25 (NLB + processing)
2 Site-to-Site VPN Encrypted tunnel, no internet Medium ~$36
3 Direct Connect Private fibre, no internet High (physical setup) $220+
4 Cross-Account IAM Role Temp credentials, no passwords Medium Free
5 (Basic) IP Whitelist + TLS Internet exposure (encrypted) Low Free

Key Takeaway

“I would never recommend keeping the database in a public subnet. Move it to a private subnet and expose it via AWS PrivateLink with an NLB and RDS Proxy — traffic stays on AWS’s private network, never touches the internet. If PrivateLink isn’t feasible, a Site-to-Site VPN is the next best option. As a minimum, whitelist their static IP in the security group and enforce TLS — but that’s the least secure since traffic still crosses the internet. On top of any method: enforce least-privilege DB access, enable audit logging, rotate credentials, and review access quarterly.”


Questions about securing database access? Connect with me on LinkedIn.