Back to RDS & Database
Q1Question 1 of 2

Securely Connecting an Application to RDS and Cross-Account Access

Key Concepts
  • Place RDS in private subnets with public access disabled so it has no public IP and cannot be reached from the internet.
  • The database security group allows inbound only from the application's security group, referenced by ID, not by a wide IP range.
  • Use Secrets Manager with automatic rotation or IAM database authentication (short-lived tokens) instead of hardcoded passwords.
  • Force TLS on the connection and verify the server certificate to prevent interception and impersonation.
  • Cross-account access requires network connectivity (VPC peering or PrivateLink) plus permission (cross-account role or shared secret).
  • Prefer PrivateLink over VPC peering when exposing only a single database endpoint, because peering joins whole networks.

Definition

Keep RDS private, let in only the app's security group, use Secrets Manager or IAM auth instead of hardcoded passwords, force TLS, and for cross-account use VPC peering or PrivateLink plus a security-group reference and a temporary role.
  • Private subnet placement – the RDS instance goes in a private subnet with "publicly accessible" set to no, giving it no public IP.
  • Security group reference – the database security group allows inbound only from the application's security group by ID, not by IP range.
  • Secrets Manager – stores and rotates database credentials automatically on a schedule using a rotation Lambda.
  • IAM database authentication – the application requests a short-lived token from IAM instead of using a long-lived password.
  • TLS – encrypts the connection in transit and verifies the server certificate.
  • Cross-account access – VPC peering or PrivateLink for network connectivity, plus a cross-account IAM role or shared secret for authorization.

Imagine the ledger database is the vault at National Bank of India. Putting it in a private subnet is keeping the vault in a windowless inner room with no door to the street. The security group is a guard who lets in only employees wearing a specific badge. Using Secrets Manager is making sure the vault combination is never written on a sticky note -- it is handed out freshly each time and changed regularly. TLS is sending documents through a sealed, tamper-proof tube. Reaching a vault in another branch (another account) is building a private, guarded tunnel between the two buildings and giving the visiting clerk a temporary pass.

Understanding the Concepts

How do you place RDS in a private subnet?

The RDS instance goes in private subnets with "publicly accessible" set to no, so it has no public IP and cannot be reached from the internet. It lives in a DB subnet group spanning private subnets in multiple Availability Zones for Multi-AZ support.

JAVA
// The app connects over a private endpoint inside the VPC
package com.bank.persistence;

import java.sql.Connection;
import java.sql.DriverManager;

public class PrivateRdsConnection {
    // Private endpoint: no public IP, no internet access
    private static final String URL =
        "jdbc:postgresql://nbi-ledger.abcdef.ap-south-1.rds.amazonaws.com:5432/ledger"
        + "?ssl=true&sslmode=verify-full";

    public Connection connect(String user, String password) throws Exception {
        return DriverManager.getConnection(URL, user, password);
    }
}

How do security groups protect the database?

The database's security group allows inbound on the database port (for example 5432 for PostgreSQL) only from the application's security group, referenced by its security group ID rather than by an IP range. This way only resources in that application security group can even open a connection.

JAVA
// Security group rule concept
package com.bank.infrastructure;

public class SecurityGroupRule {
    private final String direction;      // "inbound"
    private final int port;              // 5432
    private final String sourceType;     // "security-group"
    private final String sourceValue;    // "sg-0app123"

    public SecurityGroupRule(String direction, int port, String sourceType, String sourceValue) {
        this.direction = direction;
        this.port = port;
        this.sourceType = sourceType;
        this.sourceValue = sourceValue;
    }

    public void explain() {
        System.out.println("Allow " + direction + " on port " + port);
        System.out.println("Source: " + sourceType + " = " + sourceValue);
        System.out.println("Only resources in sg-0app123 can reach the database");
    }
}

How does Secrets Manager handle database credentials?

Hardcoding a password in code or config is the classic mistake. With Secrets Manager, the password is stored encrypted with KMS, the application fetches it at runtime with its IAM role, and Secrets Manager rotates it automatically on a schedule using a rotation Lambda that changes both the secret and the database password.

JAVA
// Fetching rotated credentials from Secrets Manager
package com.bank.persistence;

import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Map;

public class RotatedCredentialProvider {
    private final SecretsManagerClient sm = SecretsManagerClient.create();
    private final ObjectMapper mapper = new ObjectMapper();

    public Map<String, String> getCredentials() throws Exception {
        String secretJson = sm.getSecretValue(
            GetSecretValueRequest.builder()
                .secretId("prod/nbi-ledger/credentials")
                .build()
        ).secretString();
        return mapper.readValue(secretJson, Map.class);
        // Returns {"username": "admin", "password": "rotated-value"}
    }
}

How does IAM database authentication work?

With IAM database authentication, the application does not use a password at all. It requests a short-lived authentication token from IAM (valid about fifteen minutes) and uses that token to log in. There is no long-lived secret to steal.

JAVA
// Generating an IAM auth token for RDS
package com.bank.persistence;

import software.amazon.awssdk.services.rds.RdsUtilities;
import software.amazon.awssdk.services.rds.model.GenerateAuthenticationTokenRequest;
import software.amazon.awssdk.regions.Region;

public class IamAuthProvider {
    public String generateToken() {
        RdsUtilities utilities = RdsUtilities.builder()
            .region(Region.AP_SOUTH_1).build();
        return utilities.generateAuthenticationToken(
            GenerateAuthenticationTokenRequest.builder()
                .hostname("nbi-ledger.abcdef.ap-south-1.rds.amazonaws.com")
                .port(5432)
                .username("iam_user")
                .build());
    }
}

How do you force TLS and verify the certificate?

Require SSL on the connection string and verify the server certificate so no one can intercept or impersonate the database. Download the RDS CA certificate bundle and configure the JDBC driver to verify it.

JAVA
// Connection URL forcing TLS with certificate verification
package com.bank.persistence;

public class TlsConnectionConfig {
    // sslmode=verify-full: verify server cert AND hostname
    private static final String URL =
        "jdbc:postgresql://nbi-ledger.abcdef.ap-south-1.rds.amazonaws.com:5432/ledger"
        + "?ssl=true&sslmode=verify-full&sslrootcert=/opt/certs/rds-combined-ca-bundle.pem";

    public void explain() {
        System.out.println("ssl=true: force encrypted connection");
        System.out.println("sslmode=verify-full: verify certificate AND hostname");
        System.out.println("sslrootcert: path to the RDS CA bundle");
    }
}

How does cross-account access work?

An EC2 instance in account A reaching RDS in account B needs two things: network reachability and permission. Network reachability comes from VPC peering, PrivateLink, or a Transit Gateway. The database security group in account B must allow the source. Permission depends on the auth method: IAM database authentication uses a cross-account assumed role; Secrets Manager uses a resource policy to share the secret.

JAVA
// Cross-account access setup summary
package com.bank.infrastructure;

public class CrossAccountRdsAccess {
    public static void printSteps() {
        System.out.println("Step 1: Network connectivity");
        System.out.println("  - VPC peering, PrivateLink, or Transit Gateway");
        System.out.println();
        System.out.println("Step 2: Security group");
        System.out.println("  - DB SG in account B allows source from account A");
        System.out.println("  - With peering: can reference peer SG or allow CIDR");
        System.out.println();
        System.out.println("Step 3: Authentication");
        System.out.println("  - IAM auth: account A assumes cross-account role");
        System.out.println("  - Secrets Manager: resource policy shares secret");
    }
}

Prefer PrivateLink when you only need to expose a single database endpoint, because peering joins whole networks and increases blast radius. PrivateLink exposes exactly one service privately through an interface endpoint. VPC peering is appropriate when multiple services in both VPCs need to communicate.

JAVA
// Decision helper for network connectivity
package com.bank.infrastructure;

public class CrossAccountNetworkDecision {
    public static String recommend(int servicesNeeded) {
        if (servicesNeeded == 1) {
            return "PrivateLink: expose exactly one endpoint, minimal blast radius";
        }
        return "VPC Peering or Transit Gateway: many services need connectivity";
    }
}

What are common mistakes?

The first mistake is enabling "publicly accessible" on the RDS instance, giving it a public IP. The second is using a wide CIDR (like 0.0.0.0/0) in the security group instead of referencing the app's security group. The third is hardcoding the database password in application config committed to source control. The fourth is skipping TLS, allowing connections to be intercepted.

JAVA
// Wrong vs Correct security group configuration
package com.bank.infrastructure;

public class SecurityGroupMistakes {
    public static void showMistakes() {
        System.out.println("WRONG: Allow 5432 from 0.0.0.0/0");
        System.out.println("  Anyone on the internet can attempt to connect");
        System.out.println();
        System.out.println("WRONG: Allow 5432 from 10.0.0.0/16");
        System.out.println("  Entire VPC CIDR, too broad");
        System.out.println();
        System.out.println("CORRECT: Allow 5432 from sg-0app123");
        System.out.println("  Only resources in the app security group");
    }
}

Visual Overview

DIAGRAM
            Secure RDS Connection Architecture

  ┌──────────────────── VPC ────────────────────────┐
  │                                                  │
  │  ┌─────────── Private Subnet (App) ──────────┐  │
  │  │                                            │  │
  │  │  ┌────────────┐   SG: sg-app               │  │
  │  │  │ ECS Task   │   Allows outbound 5432     │  │
  │  │  │ (Transfer  │                             │  │
  │  │  │  Service)  │──────────┐                  │  │
  │  │  └────────────┘          │                  │  │
  │  │                          │                  │  │
  │  └──────────────────────────┼──────────────────┘  │
  │                             │                     │
  │  ┌─────────── Private Subnet (DB) ────────────┐  │
  │  │                          ▼                  │  │
  │  │  ┌────────────────────────────────────────┐ │  │
  │  │  │ RDS PostgreSQL (Multi-AZ)              │ │  │
  │  │  │ SG: sg-db                              │ │  │
  │  │  │ Inbound: 5432 from sg-app ONLY         │ │  │
  │  │  │ Publicly accessible: NO                │ │  │
  │  │  │ Encryption: TLS + verify-full          │ │  │
  │  │  └────────────────────────────────────────┘ │  │
  │  │                                             │  │
  │  └─────────────────────────────────────────────┘  │
  │                                                   │
  └───────────────────────────────────────────────────┘

  Credentials:
    App → Secrets Manager (rotated) → RDS
    or
    App → IAM Auth Token (15 min) → RDS

Key Takeaway

I connect an application to RDS securely in layers. First, network placement: the database goes in private subnets with public access turned off, so it has no public IP and cannot be reached from the internet. Second, the security group: the database's security group allows inbound on the database port only from the application's security group, referenced by security-group ID rather than a wide IP range. Third, credentials: I never hardcode a password -- I either store it in Secrets Manager and let it rotate automatically, or I use IAM database authentication with short-lived tokens. Fourth, TLS: I require SSL and verify the server certificate.

For cross-account access, an EC2 instance in one account can reach RDS in another by solving two problems separately. The first is network reachability through VPC peering, PrivateLink, or a Transit Gateway. The second is permission, where the database security group must allow the peer source and the caller must be authorized through a cross-account IAM role or a shared Secrets Manager secret.

The senior nuance I always raise is to prefer PrivateLink over broad VPC peering when exposing only a single database endpoint, because peering joins entire networks and widens the blast radius. I also scope security groups to a referenced security group rather than a CIDR block, so access is pinned to exactly the intended instances.

At National Bank of India the ledger database lived in private subnets with public access off, its security group accepted traffic only from the transfer service's security group, and credentials came from Secrets Manager with automatic rotation, all over TLS with full certificate verification. When a separate fraud-analytics account needed read access, I set up VPC peering for the network path, gave the analytics role a cross-account role to assume, and shared the secret with a resource policy.

Remember one sentence: place RDS in private subnets, restrict the security group to only the application's security group, use Secrets Manager or IAM auth instead of hardcoded passwords, and force TLS -- layered controls so no single misconfiguration exposes the database.

---

Share
Use ← → arrow keys to navigate