Skip to main content
thanhpham.site
ArchitectureHybrid NetworkJul 6, 2026  ·  22 min read
Site-to-Site VPNSite-to-Site VPNOn-PremisesOn-PremisesstrongSwanstrongSwanFRRFRRRoute 53 ResolverRoute 53 ResolverAuroraAuroraPostgreSQLPostgreSQLTerraformTerraformKMSKMS

Site-to-Site VPN — On-Premises ⇄ AWS with BGP HA, Hybrid DNS, and Credential-Vended Aurora Access

A real on-premises server joined to a fully-private AWS VPC: two IPsec tunnels with BGP failover, DNS that works in both directions, and a database session that never needed a stored password — plus the routing lessons every quickstart skips.

The Problem

A real on-premises application needs to use a managed database hosted in AWS — an Aurora PostgreSQL cluster — securely: the database must never be exposed to the public internet, and the on-prem host must not hold long-lived AWS or database credentials. This is the everyday shape of hybrid integration. And hybrid traffic is rarely one-way: sooner or later something in the cloud needs data that still lives on-prem, so the design has to carry the reverse path too.

The naive answer — point the app at a public RDS endpoint with a static database password over TLS — fails on two axes an architect cares about. Network: the database is reachable from the public internet, exactly the exposure a security review will flag. Identity:a long-lived password and AWS access key sitting on an on-prem box are standing secrets — copyable from a config dump, a backup, or an ex-employee’s notes — whose rotation is a manual, therefore rare, event.

The architecture in this post composes a small set of controls instead. An IPsec Site-to-Site VPN — two tunnels, BGP running over both, active/active — provides a private, self-healing path into a fully-private VPC: no internet gateway, no NAT; the tunnels are the only way in, VPC interface endpoints the only way out to AWS APIs. Aurora is reached over that path via its private network interface. Credentials are vended at runtime: the only durable secret on-prem is a bootstrap principal that can do nothing but sts:AssumeRole a tightly-scoped role, which reads exactly one secret or mints a 15-minute RDS IAM auth token. And because no private name resolves across the boundary by default, hybrid DNS — Route 53 Resolver inbound and outbound endpoints — is first-class infrastructure, not an afterthought.

The lab deliberately departs from the reference tutorials in three ways. It uses a real, independent on-premises server (Rocky Linux 9, its own public IP, outside AWS) running strongSwan as a software Customer Gateway with FRR speaking BGP — not a second VPC pretending to be an office. It targets a stateful database session, which exercises routing, cross-VPN DNS, and security-group depth that an S3 API call never touches. And it is built to production posture — both tunnels genuinely active, pinned crypto, audit alarms on — with the few lab-shaped shortcuts documented as accepted risks rather than hand-waved.

Everything AWS-side is Terraform in ap-southeast-1; everything on-prem is one Linux server configured from a runbook. What follows is the architecture, the live evidence, and the decisions — including the two failures that reshaped the design.

Architecture & Data Flow

The whole system in plain words first. Imagine an office and a private cloud connected by two redundant encrypted pipes. Neither side is reachable from the internet — the pipes are the only way in. Each side runs a small phone bookso it can look up the other side’s names. When a program in the office wants the cloud database, it first trades its low-value key for a short-lived pass, uses the pass to fetch what it needs, and the pass expires minutes later. When a program in the cloud wants the office database, it looks the name up through its phone book and connects straight through the pipe. If one pipe is cut, traffic moves to the other within seconds — nobody reconfigures anything.

Infrastructure diagram — on-premises server (strongSwan, FRR, dnsmasq, PostgreSQL) connected via two IPsec tunnels to a fully-private AWS VPC with Route 53 Resolver endpoints, interface endpoints, Aurora, and an SSM-only EC2 initiator
The numbered arrows ① … ⑦ are the main data paths, in the same order as the walkthrough below. The two thick tunnel lines are the ONLY entry into the VPC.

Concept — BGP in one paragraph

BGP (Border Gateway Protocol) is how two routers that don’t share an administrator introduce the networks they ownto each other. Each side has an ASN (an ID for “my network”: AWS 64512, on-prem 65000); over a session, each advertises routes (“I can reach 10.100.0.0/24”) and learnsthe other’s. Liveness is a heartbeat: a keepalive every 10 seconds, a hold time of 30 — “if I hear nothing for 30 seconds, I declare you dead, withdraw your routes, and send traffic another way.” That last sentence is the entire high-availability story of this design.

① The bridge — two tunnels, BGP over both

strongSwan negotiates two IKEv2 tunnels (crypto pinned to AES-256 / SHA-256 / DH group 20 on both ends) to AWS’s two tunnel endpoints. Each tunnel is route-based: its traffic selectors are wide open and the tunnel is bound by an if_id tag to a plain Linux XFRM interface — a virtual network card that routes can point at. Each tunnel also carries a tiny /30“inside” network (link-local 169.254.x.x) whose only job is to host a BGP session — an intercom line drilled through the pipe. FRR advertises the on-prem LAN and learns the VPC range over bothtunnels; the kernel installs the best path, and if that tunnel dies, BGP’s 30-second hold time withdraws it — traffic moves to the other tunnel with no human action. On the AWS side, VGW route propagation injects the learned on-prem route into the VPC route tables automatically.

② ⑥ Hybrid DNS — the two phone books

The VPC’s built-in resolver is not reachable across a Site-to-Site VPN (an AWS restriction) — that single fact is why the two Route 53 Resolver endpoints exist. On-prem → AWS: dnsmasq forwards exactly three AWS zones (RDS, Secrets Manager, STS) through the tunnel to the Resolver inbound endpoint, so the Aurora endpoint name resolves to its private IP from outside AWS. AWS → on-prem: the Resolver outbound endpoint plus a forwarding rule send any *.onprem.lab query from inside the VPC to the same dnsmasq. Both directions, one pair of phone books — and the endpoint IPs are pinned in Terraform so the on-prem forwarder config can never silently go stale.

③ ④ ⑤ Credential vending — on-prem reads Aurora, no stored password

The on-prem CLI profile calls AssumeRole; the call travels through the tunnel to the STS interface endpoint(an ENI in the private subnet — AWS’s public API brought inside the VPC). The role’s trust policy carries an aws:SourceVpce condition — only callers arriving through this exact endpoint are accepted, so a stolen bootstrap key used from anywhere else on the internet is refused. With the resulting 1-hour temporary credentials the host can do exactly two things: read one secret via the Secrets Manager endpoint, or mint a 15-minute RDS IAM auth token that Aurora accepts in place of a password. psqlthen connects to Aurora’s private endpoint with TLS enforced end-to-end: sslmode=verify-fullon the client (check the certificate’s name, not just that it has one), rds.force_ssl=1 on the server.

⑥ ⑦ The reverse path — AWS reads the on-prem database

An EC2 “initiator” inside the VPC — no SSH key, no inbound ports, reachable only through SSM Session Manager via interface endpoints — resolves db.onprem.lab through the outbound resolver and opens a psql session across the tunnel to PostgreSQL 16 on the on-premises server. That server’s client-access config allows exactly one remote rule: one database, one user, only from the VPC range, with scram-sha-256authentication. The result: each side consumes the other’s database over a path that never touches the internet.

Results & Evidence

The screenshots below show the system running end-to-end in a live AWS account (ap-southeast-1) and on the on-premises server itself. Each view validates a specific claim from the Architecture section above.

The bridge — two tunnels, both alive

The money shot for the whole design: the VPN connection’s Tunnel details tab with both tunnels UP. AWS provisions the two tunnels on independent endpoints so it can perform maintenance on one at a time — a design you only benefit from if your side actually keeps both alive.

AWS Site-to-Site VPN console showing both tunnel endpoints with status UP and dynamic (BGP) routing
Site-to-Site VPN — both tunnels UP with dynamic routing. Not a compliance checkbox: both carry a live BGP session.

The on-prem view of the same fact. swanctl --list-sas shows two ESTABLISHED IKE security associations — one per AWS endpoint — each with the pinned AES_CBC-256/HMAC_SHA2_256 proposals and its own child SA carrying traffic.

swanctl --list-sas output on the on-premises server showing two ESTABLISHED IKE SAs with AES-256/SHA-256 proposals
strongSwan on the on-premises server — two ESTABLISHED SAs, crypto matching the Terraform-pinned proposals exactly.

And the routing layer on top: FRR’s BGP summary shows two neighbors in Established state — the VGW inside addresses across tunnel 1 and tunnel 2 — each having learned exactly one prefix (PfxRcd=1 — the VPC range) while advertising the on-prem LAN back to AWS.

FRR show ip bgp summary output with two established BGP neighbors over the tunnel inside addresses
FRR — BGP established over BOTH tunnels. Two independent routing conversations; the kernel installs the best path.

The AWS side proves BGP did real work: the private route table’s route to the on-prem network shows Propagated = Yes — nobody typed that route; the VGW injected it from what BGP learned.

VPC route table showing the on-premises CIDR route via the Virtual Private Gateway with Propagated = Yes
Private route table — the 10.100.0.0/24 route arrived by BGP route propagation, not by hand.

Failover — kill a tunnel, keep the sessions

To prove the HA story, tunnel 2 was deliberately held down. The CloudWatch TunnelState alarm graph tells the whole timeline: the dip is the outage window (metric drops below 1, alarm goes to ALARM), during which BGP withdrew the dead path within its 30-second hold time and traffic flipped to tunnel 1 with no human intervention. When the tunnel was re-initiated, the metric recovered and the alarm returned to OK — visible proof of both the failure and the hands-off recovery.

CloudWatch TunnelState alarm graph showing the metric dip during the deliberate tunnel outage and recovery to OK
The failover, as the alarm saw it — tunnel down (dip), traffic on the surviving tunnel, then recovery to OK.

Hybrid DNS — private names across the boundary

From the on-premises server, one dig answers both directions of the DNS story: the Aurora cluster endpoint resolves to its private VPC address (something no public resolver can produce), and db.onprem.lab resolves locally — the same name the cloud side will ask for through the outbound endpoint.

dig output on the on-premises server resolving the Aurora endpoint to a private VPC IP address
The Aurora endpoint resolving to a private 192.168.x.x address from OUTSIDE AWS — the inbound resolver at work.

The machinery behind that answer: the Resolver inbound endpoint with one ENI per AZ, IPs pinned in Terraform (192.168.0.10 / 192.168.1.10) so the on-prem forwarder config never goes stale.

Route 53 Resolver inbound endpoint with two ENIs at pinned IP addresses across two availability zones
Route 53 Resolver inbound endpoint — the two pinned ENI IPs that on-prem dnsmasq forwards AWS zones to.

Credential vending — Aurora with no stored password

The trust policy is where the vending chain gets its teeth. The access role trusts only the bootstrap user, and only when the call arrives through the lab’s own STS endpoint — the aws:SourceVpce condition. A leaked bootstrap key replayed from anywhere else fails this check before IAM even evaluates permissions.

IAM access role trust policy showing the bootstrap user principal and the aws:SourceVpce condition
The network-fenced trust policy — sts:AssumeRole allowed only via the specific VPC endpoint ID.

The vend itself, end to end in one terminal: assume the role, mint a 15-minute RDS IAM token (piped straight into PGPASSWORD — never printed), and connect with sslmode=verify-full. The query returns current_user = app_iam from the Aurora writer’s private address — a database session with no password stored anywhere on the host.

Terminal on the on-premises server assuming the access role and connecting to Aurora with an RDS IAM auth token as app_iam
AssumeRole → 15-minute IAM token → psql over the tunnel — connected as app_iam, TLS verify-full, zero stored credentials.

And the audit trail: CloudTrail records the AssumeRole event with source IP 10.100.0.1 — the on-prem LAN address, proof the call rode the tunnel into the VPC endpoint rather than the public internet. This exact event pattern (non-service principals assuming this role) is what the vending alarm is scoped to.

CloudTrail AssumeRole event for the bootstrap user with source IP 10.100.0.1
CloudTrail — the vend as an auditable event; source IP 10.100.0.1 proves the private path.

The reverse path — cloud reads the office database

The reverse-path traffic source is deliberately boring: an EC2 instance launched with no key pair at alland no inbound security-group rules. In a VPC whose whole point is “no ingress but the tunnel”, the operator door is SSM Session Manager — not SSH.

EC2 initiator instance summary — private IP in the VPC, SSM-managed, no public address
The initiator in its private subnet — launched with no key pair and no open inbound ports; SSM Session Manager is the only way in.

Through that SSM session, psql -h db.onprem.lab resolves via the outbound resolver and lands on the on-prem PostgreSQL: inet_server_addr() = 10.100.0.1 — the cloud side reading the office database across the same two tunnels, in the opposite direction.

SSM session on the EC2 initiator running psql against db.onprem.lab and returning the on-premises server address
Reverse path proven — psql from the VPC to db.onprem.lab returns inet_server_addr() = 10.100.0.1.

Guardrails on

Steady state: the whole alarm fleet — two tunnel-state alarms, Aurora CPU, and the four CloudTrail metric-filter alarms (credential vending, secret reads, root usage, trail tampering) — sitting green. During the failover demo and every vend, the relevant alarm fired and emailed; the rest never false-alarmed, because the vending filters are scoped to non-service principals hitting this role, not AWS housekeeping noise.

CloudWatch alarms list showing tunnel state, Aurora CPU, and CloudTrail metric-filter alarms all in OK state
The full alarm fleet in OK — the tunnel-state and credential-vending alarms were exercised during the demos and returned to green.

Design Decisions

A real on-premises server, not a second VPC

A second VPC is simpler — one console, one firewall model, trivial teardown — but it never exercises what actually breaks real hybrid VPNs: provider network policy, NAT traversal, an external firewall, no single-pane visibility. The real server is precisely what surfaced the two hardest lessons below.

Concept — policy-based vs route-based IPsec

A policy-basedtunnel is a bouncer with a guest list: rules (“selectors”) declare which source/destination pairs get encrypted, and no network interface exists — nothing to route over, nothing to tcpdump. A route-based tunnel turns the tunnel into what looks like an ordinary network cable: a virtual interface (on Linux, an XFRM interface) that the routing table — including routes a BGP daemon installs — can point at. Selectors go wide open because routing, not IPsec policy, now decides what enters the tunnel.

Route-based (XFRM) + BGP, not policy-based + static

The lab started policy-based/static and migrated. The forcing fact: policy-based IPsec cannot run two tunnels with identical selectors — the second 0.0.0.0/0 connection is rejected, capping you at one active tunnel, i.e. guaranteed downtime whenever AWS maintains that tunnel endpoint. Route-based tunnels move the “what goes in” decision to the routing table, which is what lets BGP run over both tunnels and deliver ~30-second automatic failover. Static routing cannot express “prefer whichever path is alive.”

strongSwan, not Libreswan — a capability decision

Libreswan 4.x failed route-based HA on this exact design twice: with XFRM interfaces it refuses the second wide-open connection (route conflict); with VTI, inbound traffic requires a packet mark that wire packets don’t carry — everything decrypts, then silently drops. strongSwan’s per-connection if_id binding (each tunnel tagged to its own interface) avoids both, and strongSwan is AWS’s documented customer-gateway software for this design.

Verify the hosting network passes raw ESP — before you build

ESP is IPsec’s data protocol (IP protocol 50 — no ports), and some hosting networks silently drop it. The first hosting location did exactly that: IKE completed, AWS showed “IPSEC IS UP”, and not one decrypted packet ever arrived — every dashboard green, nothing working. The fix was moving the server to a host that passes ESP cleanly. Promote this to a pre-check: probe ESP reachability before trusting any site with an IPsec workload.

Hybrid DNS via Resolver endpoints, not endpoint-URL overrides

Hard-coding endpoint URLs and hosts-file entries avoids standing infrastructure, but it doesn’t survive contact with strict TLS (verify-full needs real names), RDS token minting (the signer embeds the hostname), or the reverse path. Resolver inbound + outbound endpoints with Terraform-pinned ENI IPs are the production answer — at an honest price (see Cost: this is the most expensive line in the whole lab).

Aurora over the VPN needs routing, not PrivateLink

A managed database’s data plane is just a private IP in your subnet — route to it over the tunnel like any other host. PrivateLink interface endpoints are for AWS service APIs, which is why STS and Secrets Manager get endpoints and Aurora does not. Conflating the two is a common design error.

Fence the trust policy with aws:SourceVpce

The access role only accepts AssumeRole calls arriving through the lab’s own STS endpoint — the right key at the wrong door is refused. It converts a credential control into a network control: even a leaked bootstrap key is useless from outside the tunnel. Cheap to add, disproportionate value.

RDS IAM auth tokens over fetched passwords

Reading the master secret proves the vending chain, but the cleaner end state — the primary path here — is rds-db:connect: exchange the IAM identity for a 15-minute signed token; no database password exists on-prem at all. One sharp edge: the IAM resource ARN uses a colon before the DB user (dbuser:app_iam) — a slash produces not an IAM error but a generic PAM authentication failure. Hours of misdirection.

Concept — MTU, MSS, and the black hole

Every link has an MTU (the biggest packet it can carry — 1500 bytes on typical Ethernet). Encryption wraps packets in extra headers, so a previously full-size packet no longer fits and is often dropped silently. The symptom is maddening: ping works, the tunnel shows UP, any real transfer hangs forever. The fix is clamping the TCP MSS (the payload size both ends agree on at handshake) so packets survive the wrapping.

MSS clamp + source-pinning — the two lines that carry the system

Tunnel interfaces run at MTU 1400, so TCP MSS is clamped to 1360 — without it, even BGP itself flapped once its route updates exceeded one packet. And because BGP speaks over link-local /30 addresses, Linux would stamp on-prem→VPC packets with a 169.254 source AWS can’t answer; a one-line route-map pins the source to the LAN IP instead. Neither line appears in any quickstart. Both are mandatory.

SSM-only access for the cloud-side initiator

No key pair, no inbound rules, no internet path — Session Manager via the SSM interface-endpoint trio is the only door, and every session keystroke streams to an encrypted log group. In a VPC whose entire point is “no ingress but the tunnel”, opening SSH — even from the VPN — would undercut the design.

Extended Knowledge

The two-phase IKE handshake, in plain terms

Before any data flows, the two ends must (1) prove who they are and agree on a private management channel, then (2) agree on the keys that encrypt the actual traffic. That two-step is IKE (Internet Key Exchange); its output is a pair of Security Associations — one-way “contracts” describing exactly how each direction of traffic is encrypted. Phase 1 authenticates (pre-shared key here) via a Diffie-Hellman exchange — mathematics that lets two parties agree on a shared secret while never transmitting it. Phase 2 negotiates the data-plane contracts ESP uses. AWS offers a broad menu of legacy-compatible proposals; pin the strong end (IKEv2, AES-256, SHA-256, DH 20, DPD) in IaC and mirror it exactly on the gateway — a proposal mismatch is a negotiation failure by design.

ESP, NAT-T, and the provider that eats protocol 50

Concept — ESP and NAT-T

IPsec’s data packets travel as ESP (Encapsulating Security Payload) — its own IP protocol, number 50, not TCP or UDP, meaning it has no port numbers. NAT devices track connections by ports, so ESP through NAT simply breaks. NAT-T (NAT Traversal) detects the NAT during key exchange and wraps ESP inside UDP 4500, which NAT can track.

The real-world catch is different: some hosting networks silently drop protocol-50 packets altogether — and that failure looks exactly like a healthy tunnel that carries no traffic. IKE (UDP) completes, status pages go green, and nothing forwards. It cost this lab a full server migration to learn: the hosting network’s protocol policy is part of your architecture.

Each AWS tunnel comes with a tiny two-host network from the link-local range. It carries no user data — it exists purely so each end of the tunnel has an IP address the other can talk BGP to. One subtle interplay: BGP’s route updates are TCP, so an unclamped MSS can make the routing protocol itself flap while small pings still pass — a false-healthy state that is thoroughly misleading to debug.

Interface vs gateway endpoints — and who can reach them

Both keep AWS API traffic off the internet; their reachability differs decisively. A gateway endpoint (S3, DynamoDB) is a route-table shortcut — free, but only usable from inside the VPC, so it can never serve on-prem callers over a VPN. An interface endpoint is an ENI with a private IP in your subnet — billed hourly, but reachable by anything that can route to it, including on-prem. That is why STS and Secrets Manager are interface endpoints here, while a free S3 gateway endpoint lets the no-internet EC2 instance reach OS package repos.

Private DNS across the boundary — both directions

Private hosted zones and endpoint DNS names answer only resolvers inside the VPC, and the VPC’s native resolver is unreachable over Site-to-Site VPN. The inbound endpoint exists precisely for outside forwarders; the mirror problem takes the outbound endpoint + a forwarding rule. Two production details from this build: pin the resolver ENI IPs in IaC so the on-prem forwarder cannot silently go stale, and firewall the on-prem DNS service to accept queries only from the outbound endpoint ENIs — a DNS server reachable from the whole tunnel is unnecessary surface.

Throughput ceilings — when a Transit Gateway earns its cost

Each VPN tunnel caps at ~1.25 Gbps, and a VGW’s two BGP tunnels are active/active for availability, not added bandwidth. Exceeding the per-tunnel ceiling, or fanning one VPN out to many VPCs, is the signal to move to a Transit Gateway (ECMP across tunnels). Below that, the VGW is the cheaper, simpler choice — and with BGP it already delivers the failover story.

Security & Cost Posture

Security highlights

  • Encryption in depth — pinned IPsec on the wire; TLS end-to-end for the database (rds.force_ssl=1 server-side, verify-full client-side); KMS at rest for Aurora, secrets, logs, and Terraform state
  • Identity short-lived and fenced — assume-only bootstrap principal, one-secret access role, aws:SourceVpce trust condition, 15-minute DB tokens instead of passwords
  • Structural network isolation — no IGW, no NAT, no public subnets; Aurora admits 5432 only from the on-prem range; on-prem firewall default-deny with three narrow allows
  • Watched, not just walled — CloudTrail metric alarms scoped to real vending events, secret reads, root usage, trail tampering; tunnel + DB alarms; Flow Logs, Resolver query logs, SSM session logs → one SNS topic
  • Residual risks named, not hidden — PSKs and the bootstrap key exist in Terraform state (state bucket treated as sensitive even after teardown); AWS-managed KMS keys; single-AZ Aurora — each accepted for a teardown-friendly lab with its production precondition documented

The cost surprise

Verified against the AWS Pricing API and the live deployed state — not list-price guesses. Running 24×7 this design costs ~$18.85/day ≈ $573/mo, and the ranking surprises most people: the VPN is nearly the cheapest line.

Line itemConfigEst./month
Route 53 Resolver endpointsinbound + outbound, 4 ENIs (AWS minimum)~$365
VPC interface endpoints8 AZ-endpoints (STS, SM, SSM trio, Logs)~$76
Aurora PostgreSQL1× db.t4g.medium, <1 GB storage~$85
Site-to-Site VPN1 connection, both tunnels included~$36.50
EC2 initiatort4g.micro + 8 GiB gp3~$8.50
CloudWatch / KMS / SNS / logsmisc~$3
Total~$18.85/day~$573

Two structural lessons. Transparent two-way DNS is the most expensive convenience in a hybrid design — the Resolver endpoints alone are ~64% of the bill. And the top two lines are AWS architectural minimums, so the only real lever is teardown discipline — the design is deliberately teardown-friendly (no deletion protection, no final snapshot, zero-day secret recovery).

Takeaways

  1. The hosting network is part of the design. Probe raw ESP (IPsec’s port-less data protocol) before trusting any site with an IPsec workload — a provider that silently drops it produces green dashboards and zero traffic.
  2. Policy-based IPsec caps you at one active tunnel. Route-based tunnels (each one a routable interface) with BGP over both turn AWS’s “two tunnels” into ~30-second hands-off failover.
  3. The IPsec software choice is a capability decision. strongSwan’s per-tunnel if_idinterface binding makes two parallel route-based tunnels work where Libreswan’s mechanisms both fail.
  4. A managed database needs routing, not PrivateLink. Aurora is already a private IP in your subnet; only the AWS service APIs (STS first, then Secrets Manager) need interface endpoints — and every hop must be private or the chain fails.
  5. Hybrid DNS is mandatory machinery — and the biggest line item. The VPC’s native resolver is unreachable over the VPN, strict TLS and token minting need real hostnames, and the Resolver endpoints that solve it cost more than Aurora and the VPN combined. Budget DNS as a first-class component.
  6. Two lines of config carry the whole system. An MSS clamp (without it, even BGP flaps — a green-dashboards-nothing-works failure) and source-pinning (always stamp outgoing traffic with a return address the other side can route back to).
  7. Credential vending removes the standing secret. Assume-only key + aws:SourceVpce fence + 15-minute RDS IAM tokens = no database password and no usable long-lived AWS key on the host, with every vend audited and alarmed.
  8. Everything here bills by the hour whether traffic flows or not. For a lab, teardown beats every other cost optimization.

References

Have a topic in mind?

Always up for a good technical conversation.

Suggest a topic, share a war story, or ask the question — happy to dig into the details.