Project 9: ZTNA App Tunnel (No More VPNs!)

Project 9: ZTNA App Tunnel (No More VPNs!)

Core Zero Trust Principle: “Connect users to applications, never to networks.” This project teaches you to build the modern replacement for VPNs - a client agent that selectively tunnels traffic to specific applications while leaving all other internet traffic untouched.


Project Overview

Attribute Value
Difficulty Level 4: Expert
Time Estimate 2 Weeks
Main Language Rust or Go
Alternative Languages C
Knowledge Area L4/L7 Tunneling
Key Technologies SOCKS5, HTTP CONNECT, mTLS, HTTP/2
Main Book “TCP/IP Illustrated, Volume 1” by W. Richard Stevens
Prerequisites Project 1 (Identity-Aware Proxy), Project 4 (mTLS Mesh)

What You’re Building: A client agent that intercepts traffic only for specific internal URLs (e.g., internal.corp.com) and tunnels it over a secure mTLS connection to your Project 1 Proxy. It DOES NOT touch the user’s other internet traffic.

Why It Matters: Traditional VPNs are fundamentally incompatible with Zero Trust because they place the user’s entire device “inside” the network. ZTNA App Tunnels flip this model - they bring the application to the user, not the user to the network. This eliminates the primary vector for lateral movement after device compromise.


Learning Objectives

By completing this project, you will be able to:

  1. Implement a SOCKS5 proxy from scratch - Parse RFC 1928 messages, handle authentication, and support TCP connections
  2. Design selective traffic routing - Build a domain-based routing engine that splits tunnel traffic from direct traffic
  3. Prevent DNS leaks - Understand and implement DNS interception to protect user privacy
  4. Establish mTLS upstream connections - Reuse concepts from Project 4 to secure the tunnel
  5. Implement HTTP/2 stream multiplexing - Optimize tunnel performance for multiple concurrent connections
  6. Inject identity into tunneled requests - Carry user and device identity through the tunnel to the gateway
  7. Articulate ZTNA vs VPN tradeoffs - Explain why application-level access is superior to network-level access

The Core Question You’re Answering

“How can I replace traditional VPNs with application-specific, identity-aware tunnels that provide better security and user experience?”

Traditional VPNs operate on a fundamentally flawed assumption: that network location equals trust. When a user connects via VPN, their device is virtually “inside” the corporate network with full Layer 3 connectivity to potentially thousands of hosts. This is the digital equivalent of giving someone a master key to your building when they only need access to one office.

ZTNA flips this model entirely. Instead of bringing the user to the network, ZTNA brings the application to the user. The user’s device never gains network-level access - it only receives access to specific, authorized applications through a secure tunnel. Even if the user’s device is compromised, the attacker can only reach the 2-3 applications that user was authorized for, not scan the entire 10.0.0.0/8 network for vulnerable hosts.

The security implications are profound:

  • VPN attack surface: Millions of IP:port combinations accessible after connection
  • ZTNA attack surface: Only the specific application endpoints the user needs
  • Lateral movement potential: VPN enables it; ZTNA prevents it by design

Concepts You Must Understand First

Before implementing a ZTNA App Tunnel, ensure you have a solid grasp of these foundational concepts:

1. SOCKS5 Proxy Protocol (RFC 1928)

SOCKS5 is a Layer 4 (Transport) proxy protocol that can tunnel any TCP or UDP traffic. Unlike HTTP proxies which only understand HTTP, SOCKS5 is protocol-agnostic - it simply forwards raw bytes between client and destination.

Key mechanisms:

  • Authentication negotiation (version, methods)
  • CONNECT command for TCP connections
  • UDP ASSOCIATE for UDP traffic
  • Address types: IPv4, IPv6, and domain names

Why it matters: SOCKS5 lets your ZTNA client tunnel SSH, database connections, RDP, and any custom protocol - not just web traffic.

2. Split Tunneling and Domain-Based Routing

Split tunneling means selectively routing some traffic through the tunnel while letting other traffic go directly to the internet. For ZTNA, this is the default (not an option).

Key mechanisms:

  • Domain whitelist matching (exact, wildcard, suffix)
  • IP range matching (CIDR notation)
  • PAC (Proxy Auto-Configuration) files
  • Routing table manipulation

Why it matters: You only tunnel what needs protection. Internet traffic stays fast, tunnel bandwidth stays low, and user privacy is preserved.

3. mTLS for Tunnel Security

Mutual TLS (mTLS) ensures both the client and gateway cryptographically prove their identity. The tunnel cannot be intercepted, and both endpoints are authenticated.

Key mechanisms:

  • Client certificate containing user/device identity
  • Server certificate containing gateway identity
  • Certificate chain validation against trusted CA
  • SPIFFE IDs for workload identity

Why it matters: Without mTLS, an attacker could impersonate either the client or the gateway. With mTLS, the tunnel is cryptographically authenticated in both directions.

4. DNS Leak Prevention

A DNS leak occurs when DNS queries for internal domains (e.g., jira.internal) are sent to public DNS servers instead of being routed through the secure tunnel.

Key mechanisms:

  • SOCKS5h (DNS resolved by proxy)
  • Split-horizon DNS
  • OS-level DNS interception (Network Extension, WFP, eBPF)
  • DNS-over-HTTPS/TLS through tunnel

Why it matters: DNS leaks expose your internal infrastructure to anyone watching the network. An attacker learns which internal services exist before even attempting to breach them.

5. HTTP/2 Multiplexing

HTTP/2 allows multiple streams (requests/responses) to share a single TCP connection. For tunneling, this means one mTLS handshake can serve dozens of concurrent application connections.

Key mechanisms:

  • Stream identifiers for parallel requests
  • Flow control per stream
  • Header compression (HPACK)
  • Server push (less relevant for tunneling)

Why it matters: Without multiplexing, each new application connection requires a new mTLS handshake (100-300ms). With multiplexing, the overhead is near zero after the first connection.

6. Identity Propagation Through Tunnels

The tunnel must carry user and device identity from the client to the gateway, so the gateway can inject identity headers into requests to internal services.

Key mechanisms:

  • Identity in TLS client certificate (CN, SPIFFE ID)
  • Custom headers in tunnel protocol (X-ZT-Identity)
  • Identity extraction at gateway
  • Trust relationship between gateway and internal services

Why it matters: This enables “login-free” access to internal applications. The user’s identity is already verified by the tunnel - no need for additional authentication at each app.


Questions to Guide Your Design

Before writing code, work through these architectural questions:

Tunnel Architecture

  1. Client-side interception: How will you intercept traffic on the user’s machine? System proxy settings, PAC file, transparent proxy (iptables/pf), or TUN/TAP interface?

  2. Protocol choice: Will you use SOCKS5, HTTP CONNECT, or both? What are the tradeoffs for your use case?

  3. Gateway integration: How does your tunnel client communicate with the ZTNA gateway (Project 1)? HTTP/2 CONNECT? gRPC? Custom protocol?

  4. Connection lifecycle: How do you handle gateway disconnection? Do you queue requests, fail fast, or retry transparently?

Routing Decisions

  1. Domain matching: How do you efficiently match domains against patterns like *.internal, *.corp.com, and 10.0.0.0/8?

  2. Decision caching: Should you cache routing decisions? What’s the invalidation strategy?

  3. Configuration updates: Can you update the domain whitelist without restarting the client?

Security Considerations

  1. DNS handling: How do you ensure DNS for internal domains goes through the tunnel? What about apps that don’t use SOCKS5?

  2. Identity sourcing: Where does the client get the user and device identity? Certificate? Config file? OS-level attestation?

  3. Certificate lifecycle: How do you handle certificate expiration and rotation without dropping active connections?


Thinking Exercise

Before implementing, trace a request through your ZTNA tunnel on paper. This exercise will reveal gaps in your understanding.

Exercise: Trace a Request to jira.internal

Grab a piece of paper and draw the flow for this scenario:

Setup:

  • User’s browser configured to use SOCKS5 proxy at 127.0.0.1:1080
  • ZTNA client running, connected to gateway at gateway.corp.com:8443
  • Domain whitelist includes *.internal
  • User types https://jira.internal/dashboard in browser

Trace each step:

  1. What happens in the browser before any network traffic?
  2. What SOCKS5 messages are exchanged with your client?
  3. How does your client decide to tunnel vs bypass?
  4. What does the tunnel traffic look like on the wire?
  5. How does the gateway know the user’s identity?
  6. What headers does jira.internal receive?
  7. How does the response flow back?

Consider edge cases:

  • What if the gateway is unreachable?
  • What if the certificate is expired?
  • What if DNS for jira.internal leaks to public DNS before reaching your SOCKS5 server?
  • What if 10 browser tabs open simultaneously?

After completing this exercise, you should be able to draw a complete sequence diagram with all the protocol details.


Hints in Layers

Use these hints progressively if you get stuck. Try to solve each challenge yourself before revealing the next hint.

Hint 1: SOCKS5 Implementation Starting Point

Start by implementing a “pass-through” SOCKS5 proxy that connects directly to any target. This lets you test the SOCKS5 protocol handling before adding routing logic.

Click to reveal structure hint

The SOCKS5 handshake has two phases:

  1. Authentication negotiation (client sends methods, server picks one)
  2. Connection request (client sends target, server connects and responds)

After the handshake succeeds, you just forward bytes bidirectionally - no more protocol parsing needed.

Hint 2: Domain Matching Efficiency

For efficient domain matching, don’t iterate through patterns for every request. Pre-process your whitelist into data structures optimized for lookup.

Click to reveal algorithm hint
  • Use a hash set for exact matches (O(1) lookup)
  • For wildcard patterns like *.internal, extract the suffix (.internal) and check if the domain ends with it
  • For IP ranges, parse CIDR into network/mask and check containment
  • Consider a trie structure for very large whitelists

Hint 3: HTTP/2 Connection Reuse

The key to efficient multiplexing is reusing the same http.Client (Go) or connection pool for all tunnel streams.

Click to reveal implementation hint

In Go:

transport := &http2.Transport{
    TLSClientConfig: tlsConfig,
}
client := &http.Client{Transport: transport}
// Reuse this client for ALL tunnel connections

Each http.NewRequest("CONNECT", ...) becomes a new stream on the same HTTP/2 connection.

Hint 4: DNS Leak Prevention Strategy

The easiest DNS leak prevention is using socks5h:// (note the ‘h’) which tells the client to send the domain name to the SOCKS5 proxy for resolution.

Click to reveal complete strategy
  1. Configure clients to use socks5h://127.0.0.1:1080 (DNS through proxy)
  2. In your SOCKS5 server, receive the domain name (ATYP 0x03), not an IP
  3. For internal domains, resolve through the tunnel
  4. For external domains, you can either resolve locally or let the gateway resolve

For apps that don’t support SOCKS5h, you’ll need OS-level DNS interception:

  • macOS: Network Extension framework
  • Linux: eBPF or iptables REDIRECT to local DNS server
  • Windows: WFP (Windows Filtering Platform)

Hint 5: Graceful Gateway Reconnection

Don’t fail requests immediately when the gateway disconnects. Give the reconnection logic a chance to succeed.

Click to reveal reconnection pattern
  1. Maintain a connection state: CONNECTED, CONNECTING, DISCONNECTED
  2. When a request comes in and state is CONNECTED: send immediately
  3. When state is CONNECTING: queue the request with a timeout
  4. When state is DISCONNECTED: start reconnection, queue the request
  5. Use exponential backoff for reconnection attempts
  6. After max retries, fail queued requests with clear error message
  7. Keep the SOCKS5 server running - it can still handle bypassed traffic

Deep Theoretical Foundation

VPN vs ZTNA: The Fundamental Paradigm Shift

Understanding why ZTNA exists requires understanding why VPNs are fundamentally broken for modern security.

+--------------------------------------------------------------------------+
|                    Traditional VPN Model                                  |
+--------------------------------------------------------------------------+

                         CORPORATE NETWORK
                  +---------------------------+
                  |                           |
  [Remote User]   |   [ Jira ]   [ GitLab ]   |
       |          |      |           |        |
       |          |   [ Database Server ]     |
       |          |      |           |        |
  [ VPN Client ]  |   [ All Internal Hosts ]  |
       |          |           |               |
       +----------+----[ Firewall ]-----+-----+
                              |
                  +-----------+-----------+
                  |                       |
             [ Internet ]            [ VPN Tunnel ]
                  |                       |
                  |                       |
              [ User's Laptop ]           |
              ALL traffic now             |
              goes through VPN   <--------+


THE PROBLEM:
- User's laptop is now INSIDE the corporate network
- Malware on the laptop can scan 10.0.0.0/8
- Compromised user has NETWORK access, not just APP access
- Even traffic to google.com goes through the VPN (latency!)
- VPN concentrator becomes a single point of failure

+--------------------------------------------------------------------------+

+--------------------------------------------------------------------------+
|                    Zero Trust Network Access (ZTNA) Model                 |
+--------------------------------------------------------------------------+

              [ User's Laptop ]
                    |
        +-----------+-----------+
        |                       |
  [ ZTNA Client Agent ]    [ Normal Internet ]
        |                       |
        |                       +---> google.com (direct)
        |                       +---> youtube.com (direct)
        |                       +---> github.com (direct)
        |
        | (ONLY internal app traffic)
        |
  [ mTLS Tunnel ]
        |
        v
  +-----+-----+
  |           |
  |  ZTNA     |      The ZTNA Gateway is typically
  |  Gateway  |      your Identity-Aware Proxy (Project 1)
  |  (PEP)    |
  |           |
  +-----+-----+
        |
        | (Authenticated, authorized request)
        |
        v
  [ jira.internal ]  <-- User can ONLY reach specific apps
                         NOT the network!


THE ADVANTAGE:
- User's laptop remains OUTSIDE the corporate network
- Only APPLICATION traffic is tunneled, not network traffic
- No ability to scan internal IP ranges
- Compromise is limited to specific authorized applications
- Internet traffic stays fast (no VPN hop)
- No single point of failure (distributed gateways)

+--------------------------------------------------------------------------+

Key Insight: In VPN, trust is established by network location (you’re “inside”). In ZTNA, trust is established by identity (who you are + what app you’re accessing + what device you’re using).

The Attack Surface Comparison

+--------------------------------------------------------------------------+
|                    Attack Surface: VPN vs ZTNA                            |
+--------------------------------------------------------------------------+

VPN ATTACK SURFACE (After Connection):
+------------------------------------------------------------------+
|                                                                   |
|  User gets: Full Layer 3 connectivity to 10.0.0.0/8              |
|                                                                   |
|    [ Laptop ] -----> [ Any host in 10.0.0.0/8 ]                  |
|                                                                   |
|    Attacker can:                                                  |
|    - Port scan 10.0.0.1 to 10.255.255.254                        |
|    - Connect to any open port on any host                        |
|    - Exploit unpatched internal services                         |
|    - Pivot through compromised hosts                             |
|    - Access databases directly (bypass app)                      |
|    - Exfiltrate data to any internal host                        |
|                                                                   |
|    Attack surface: MILLIONS of IP:PORT combinations              |
|                                                                   |
+------------------------------------------------------------------+

ZTNA ATTACK SURFACE (After Connection):
+------------------------------------------------------------------+
|                                                                   |
|  User gets: Access to authorized applications ONLY               |
|                                                                   |
|    [ Laptop ] --X--> [ Cannot reach 10.0.0.0/8 ]                 |
|                                                                   |
|    [ Laptop ] -----> [ jira.internal:443 ] (if authorized)       |
|                -----> [ gitlab.internal:443 ] (if authorized)    |
|                --X--> [ database:5432 ] (NOT authorized)         |
|                                                                   |
|    Attacker can:                                                  |
|    - Access only the specific apps the user is authorized for    |
|    - That's it.                                                  |
|                                                                   |
|    Attack surface: 2-3 specific application endpoints            |
|                                                                   |
+------------------------------------------------------------------+

REDUCTION FACTOR: 99.999%+ reduction in attack surface

Split Tunneling: The Default for Zero Trust

Split tunneling means some traffic goes through the tunnel while other traffic goes directly to the internet. This is the default for ZTNA, while traditional VPNs often use full tunneling.

+--------------------------------------------------------------------------+
|                    Full Tunnel vs Split Tunnel                            |
+--------------------------------------------------------------------------+

FULL TUNNEL (Traditional VPN Default):
----------------------------------------

  [ User's Laptop ]
        |
        | ALL TRAFFIC
        |
        v
  [ VPN Tunnel ] ---------> [ VPN Server ] ---------> [ Internet ]
                                   |
                                   +---------> [ Internal Apps ]

  - google.com goes through VPN (latency++)
  - netflix.com goes through VPN (bandwidth!!)
  - Corporate traffic goes through VPN (intended)

  Problems:
  - VPN bandwidth saturated by streaming
  - Increased latency for all traffic
  - User experience degrades
  - VPN server sees all user traffic (privacy)


SPLIT TUNNEL (ZTNA Default):
-----------------------------

  [ User's Laptop ]
        |
        +----- [ ZTNA Agent ] -----> [ ZTNA Gateway ] --> [ Internal Apps ]
        |           |
        |      Domain Match?
        |           |
        |      *.internal? --> TUNNEL
        |      *.corp.com? --> TUNNEL
        |      else       --> BYPASS
        |
        +----- [ Direct Connection ] -----> [ Internet ]

  - google.com goes DIRECT (fast!)
  - netflix.com goes DIRECT (no bandwidth on tunnel)
  - jira.internal goes through TUNNEL (intended)

  Advantages:
  - Optimal user experience
  - Minimal tunnel bandwidth
  - No visibility into personal traffic
  - Scales to millions of users

+--------------------------------------------------------------------------+
|                    Security Implications                                  |
+--------------------------------------------------------------------------+

Q: "Isn't split tunneling less secure? Traffic isn't protected."

A: This reveals a fundamental misunderstanding.

   The PURPOSE of ZTNA is to protect INTERNAL applications from
   external threats, NOT to protect users from the internet.

   If you need to protect users from malicious sites, use:
   - DNS filtering (Pi-hole, Cloudflare Gateway)
   - Secure Web Gateway (SWG)
   - Browser isolation

   ZTNA protects the castle. Other tools protect the knight.

+--------------------------------------------------------------------------+

The SOCKS5 Protocol

SOCKS5 (RFC 1928) is a Layer 4 (Transport Layer) proxy protocol. Unlike HTTP proxies, it can handle any TCP/UDP traffic, not just HTTP.

+--------------------------------------------------------------------------+
|                    SOCKS5 Protocol Overview (RFC 1928)                    |
+--------------------------------------------------------------------------+

WHY SOCKS5 FOR ZTNA?

  Layer 7 Proxy (HTTP):
  - Understands HTTP methods (GET, POST, etc.)
  - Can only proxy HTTP/HTTPS traffic
  - Cannot proxy SSH, database connections, or custom protocols

  Layer 4 Proxy (SOCKS5):
  - Works at the TCP/UDP level
  - Can proxy ANY protocol: HTTP, SSH, PostgreSQL, RDP, etc.
  - Perfect for "Application Tunnel" use case

+--------------------------------------------------------------------------+
|                    SOCKS5 Connection Flow                                 |
+--------------------------------------------------------------------------+

  Client                           SOCKS5 Proxy                    Target
    |                                   |                              |
    | 1. Authentication Negotiation     |                              |
    | --------------------------------> |                              |
    |   VER | NMETHODS | METHODS        |                              |
    |   0x05| 0x01     | 0x00           |                              |
    |   (v5)| (1 method)| (no auth)     |                              |
    |                                   |                              |
    | <-------------------------------- |                              |
    |   VER | METHOD                    |                              |
    |   0x05| 0x00                      |                              |
    |   (v5)| (no auth selected)        |                              |
    |                                   |                              |
    | 2. Connection Request             |                              |
    | --------------------------------> |                              |
    |   VER|CMD|RSV|ATYP| DST.ADDR     | DST.PORT                     |
    |   0x05|0x01|0x00|0x03| [domain]   | [port]                       |
    |   (v5)|(connect)   |               |                              |
    |                                   |                              |
    |                                   | 3. Proxy connects to target  |
    |                                   | ----------------------------->|
    |                                   |                              |
    | <-------------------------------- |                              |
    |   VER|REP|RSV|ATYP| BND.ADDR     | BND.PORT                     |
    |   0x05|0x00|0x00|0x01| [ip]       | [port]                       |
    |   (v5)|(success)   |               |                              |
    |                                   |                              |
    | 4. Data Transfer (bidirectional)  |                              |
    | <================================>|<============================>|
    |   Raw TCP bytes, any protocol     |   Raw TCP bytes              |
    |                                   |                              |

+--------------------------------------------------------------------------+
|                    SOCKS5 Packet Formats                                  |
+--------------------------------------------------------------------------+

AUTHENTICATION NEGOTIATION REQUEST:
+----+----------+----------+
|VER | NMETHODS | METHODS  |
+----+----------+----------+
| 1  |    1     | 1-255    |  bytes
+----+----------+----------+

  VER: 0x05 (SOCKS version 5)
  NMETHODS: Number of authentication methods supported
  METHODS: List of method identifiers
    0x00: No authentication required
    0x01: GSSAPI
    0x02: Username/Password (RFC 1929)
    0x03-0x7F: IANA assigned
    0x80-0xFE: Reserved for private use
    0xFF: No acceptable methods

AUTHENTICATION NEGOTIATION RESPONSE:
+----+--------+
|VER | METHOD |
+----+--------+
| 1  |   1    |  bytes
+----+--------+

  Server picks one method from client's list
  0xFF = no acceptable method (close connection)


CONNECTION REQUEST:
+----+-----+-------+------+----------+----------+
|VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
+----+-----+-------+------+----------+----------+
| 1  |  1  | X'00' |  1   | Variable |    2     |  bytes
+----+-----+-------+------+----------+----------+

  CMD:
    0x01: CONNECT (establish TCP connection)
    0x02: BIND (for protocols that need inbound connections)
    0x03: UDP ASSOCIATE (for UDP traffic)

  ATYP (Address Type):
    0x01: IPv4 (4 bytes)
    0x03: Domain name (1 byte length + name)
    0x04: IPv6 (16 bytes)

  DST.ADDR:
    For 0x01: 4 bytes IPv4 address
    For 0x03: 1 byte length + domain string
    For 0x04: 16 bytes IPv6 address

  DST.PORT: 2 bytes, big-endian


CONNECTION RESPONSE:
+----+-----+-------+------+----------+----------+
|VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
+----+-----+-------+------+----------+----------+
| 1  |  1  | X'00' |  1   | Variable |    2     |  bytes
+----+-----+-------+------+----------+----------+

  REP (Reply):
    0x00: Succeeded
    0x01: General SOCKS server failure
    0x02: Connection not allowed by ruleset
    0x03: Network unreachable
    0x04: Host unreachable
    0x05: Connection refused
    0x06: TTL expired
    0x07: Command not supported
    0x08: Address type not supported

+--------------------------------------------------------------------------+

HTTP CONNECT Method

HTTP CONNECT is the Layer 7 alternative to SOCKS5 for TCP tunneling. It’s how browsers tunnel HTTPS through HTTP proxies.

+--------------------------------------------------------------------------+
|                    HTTP CONNECT Method                                    |
+--------------------------------------------------------------------------+

When a browser wants to access https://example.com through a proxy,
it can't just send the GET request - the proxy can't decrypt HTTPS.

Instead, the browser asks the proxy to establish a raw TCP tunnel:

Client                              HTTP Proxy                      Target
  |                                     |                              |
  | CONNECT example.com:443 HTTP/1.1    |                              |
  | Host: example.com:443               |                              |
  | ----------------------------------> |                              |
  |                                     |                              |
  |                                     | (proxy connects to target)   |
  |                                     | ---------------------------> |
  |                                     |                              |
  | <---------------------------------- | <--------------------------- |
  | HTTP/1.1 200 Connection Established |                              |
  |                                     |                              |
  | (TLS handshake begins)              |                              |
  | <================================================================> |
  | (now raw TLS bytes flow through)    |                              |
  |                                     |                              |
  | GET / HTTP/1.1                      |                              |
  | (encrypted, proxy can't read)       |                              |
  | <================================================================> |
  |                                     |                              |

+--------------------------------------------------------------------------+

SOCKS5 vs HTTP CONNECT:
-----------------------

| Feature            | SOCKS5              | HTTP CONNECT           |
|--------------------|---------------------|------------------------|
| Layer              | 4 (Transport)       | 7 (Application)        |
| Protocol Support   | Any TCP/UDP         | Only TCP               |
| UDP Support        | Yes (UDP ASSOCIATE) | No                     |
| Complexity         | Higher              | Lower                  |
| Browser Support    | Via extension/PAC   | Native                 |
| Non-HTTP protocols | SSH, DB, RDP, etc.  | Only via TCP tunnel    |
| Authentication     | Multiple methods    | HTTP auth headers      |

For ZTNA, SOCKS5 is preferred because it supports:
- SSH connections to internal servers
- Database connections (PostgreSQL, MySQL)
- RDP for remote desktop
- Any custom internal protocol

+--------------------------------------------------------------------------+

Layer 4 vs Layer 7 Proxies: What Each Can See

Understanding the difference is crucial for designing the right proxy for your use case.

+--------------------------------------------------------------------------+
|                    Layer 4 vs Layer 7 Visibility                          |
+--------------------------------------------------------------------------+

THE OSI MODEL (Simplified):

  Layer 7: Application   [ HTTP, SSH, DNS, PostgreSQL ]
  Layer 6: Presentation  [ TLS/SSL ]
  Layer 5: Session       [ Session management ]
  Layer 4: Transport     [ TCP, UDP ]
  Layer 3: Network       [ IP ]
  Layer 2: Data Link     [ Ethernet, WiFi ]
  Layer 1: Physical      [ Cables, Radio ]


LAYER 4 PROXY (SOCKS5, TCP Load Balancer):
------------------------------------------

  [ Client ] --> [ L4 Proxy ] --> [ Server ]

  What the proxy sees:
  +------------------------------------------+
  | Source IP: 192.168.1.100                 |
  | Destination IP: 10.0.1.50 (or domain)    |
  | Source Port: 52431                       |
  | Destination Port: 443                    |
  | Protocol: TCP                            |
  | Raw bytes: 0x16 0x03 0x01 0x02 0x00 ...  |  <- TLS handshake (opaque)
  +------------------------------------------+

  What the proxy CANNOT see:
  - HTTP method (GET, POST, etc.)
  - HTTP headers (Host, Cookie, Authorization)
  - HTTP body (JSON, form data)
  - URL path (/api/users)
  - TLS certificate details (SNI only)

  Perfect for: Tunneling any TCP protocol


LAYER 7 PROXY (HTTP Proxy, Reverse Proxy):
------------------------------------------

  [ Client ] --> [ L7 Proxy ] --> [ Server ]

  What the proxy sees (for HTTP):
  +------------------------------------------+
  | Method: GET                              |
  | Path: /api/users/123                     |
  | Host: api.example.com                    |
  | Headers:                                 |
  |   Authorization: Bearer eyJhbG...        |
  |   Content-Type: application/json         |
  |   Cookie: session=abc123                 |
  | Body: {"name": "douglas"}                |
  +------------------------------------------+

  What the proxy can do:
  - Inspect and modify headers
  - Inject identity headers (X-ZT-Identity)
  - Route based on URL path
  - Cache responses
  - Transform requests

  Cannot proxy: SSH, PostgreSQL, RDP, or any non-HTTP protocol


FOR ZTNA APP TUNNEL:
-------------------

  We use BOTH layers:

  1. SOCKS5 (L4) on the client side:
     - Accepts connections for ANY protocol
     - Routes based on domain/IP only

  2. The tunnel itself:
     - Carries raw TCP bytes to the gateway
     - Uses HTTP/2 or QUIC for multiplexing

  3. HTTP Reverse Proxy (L7) on the gateway:
     - Unwraps the tunnel
     - Inspects HTTP traffic for identity injection
     - Forwards to internal services

+--------------------------------------------------------------------------+

mTLS for Tunnel Security

The tunnel between the client agent and the ZTNA gateway MUST use mTLS. This ensures both ends cryptographically prove their identity.

+--------------------------------------------------------------------------+
|                    mTLS in ZTNA Tunnel                                    |
+--------------------------------------------------------------------------+

WITHOUT mTLS:
-------------

  [ Attacker ] -----> [ ZTNA Gateway ]
                 |
                 +-- "I'm the legitimate client agent"
                 +-- (No proof required)
                 +-- (Attacker can spoof)


WITH mTLS:
----------

  [ Client Agent ]                           [ ZTNA Gateway ]
        |                                          |
        | ClientHello + Client Certificate         |
        | ---------------------------------------->|
        |                                          |
        |                 ServerHello + Server Certificate
        | <----------------------------------------|
        |                                          |
        | Certificate Verify (signed with          |
        | client private key)                      |
        | ---------------------------------------->|
        |                                          |
        |         Gateway verifies:                |
        |         - Client cert signed by trusted CA
        |         - SPIFFE ID matches allowed clients
        |         - Device ID is registered
        |                                          |
        |         If verification fails: CLOSE CONNECTION
        |                                          |
        |                 Encrypted Tunnel         |
        | <=======================================>|
        |                                          |


WHY mTLS IS ESSENTIAL:
----------------------

1. CLIENT AUTHENTICATION:
   - Gateway knows WHICH client agent is connecting
   - Certificate contains user identity (e.g., douglas@corp.com)
   - Certificate contains device identity (e.g., macbook-42)

2. SERVER AUTHENTICATION:
   - Client knows it's connecting to the REAL gateway
   - Prevents man-in-the-middle attacks
   - Certificate pinning prevents CA compromise attacks

3. ENCRYPTION:
   - Traffic is encrypted against Wi-Fi sniffing
   - Even on hostile networks (hotel, airport)

4. REPLAY PREVENTION:
   - Each TLS session has unique keys
   - Captured traffic cannot be replayed


CONNECTION TO PROJECT 4:
------------------------

The mTLS concepts you learned in Project 4 apply directly here:
- Use short-lived certificates (1-hour)
- Rotate certificates automatically
- Include SPIFFE ID in certificate SAN
- Trust only your organizational CA

+--------------------------------------------------------------------------+

DNS Leaks: The Critical Security Vulnerability

DNS leaks are one of the most common and dangerous vulnerabilities in split-tunnel implementations. If internal DNS queries go to external DNS servers, an attacker can learn what internal services exist.

+--------------------------------------------------------------------------+
|                    DNS Leaks Explained                                    |
+--------------------------------------------------------------------------+

WHAT IS A DNS LEAK?
-------------------

  User wants to access: jira.internal

  CORRECT BEHAVIOR (No Leak):

    Browser                 ZTNA Agent               Internal DNS
      |                         |                         |
      | "Resolve jira.internal" |                         |
      | ----------------------->|                         |
      |                         | "Resolve jira.internal" |
      |                         | ----------------------->|
      |                         |                         |
      |                         | <-----------------------|
      |                         | "10.0.1.50"             |
      | <-----------------------|                         |
      | "10.0.1.50"             |                         |
      |                         |                         |

    The DNS query went through the secure tunnel.
    External observers see NOTHING.


  DNS LEAK (Vulnerable):

    Browser                                     Public DNS (8.8.8.8)
      |                                              |
      | "Resolve jira.internal"                      |
      | -------------------------------------------->|
      |                                              |
      | <--------------------------------------------|
      | "NXDOMAIN" (domain doesn't exist)            |
      |                                              |

    But the DNS server now knows:
    - This user's IP address
    - They tried to access "jira.internal"
    - This company uses Jira internally
    - The naming convention is *.internal

    This information can be used for:
    - Targeted phishing attacks
    - Reconnaissance for network attacks
    - Building a map of internal services


+--------------------------------------------------------------------------+
|                    Types of DNS Leaks                                     |
+--------------------------------------------------------------------------+

1. WEBRTC LEAK:
   Browser uses WebRTC to get public IP
   Bypasses proxy settings entirely

2. IPv6 LEAK:
   Tunnel handles IPv4 only
   IPv6 DNS queries go direct

3. SYSTEM DNS LEAK:
   OS sends DNS query before checking proxy
   Common on macOS and Windows

4. TIMING LEAK:
   Parallel DNS queries to multiple servers
   Internal query times differ from external


+--------------------------------------------------------------------------+
|                    Solutions for DNS Leak Prevention                      |
+--------------------------------------------------------------------------+

SOLUTION 1: DNS-over-HTTPS (DoH) / DNS-over-TLS (DoT)
-----------------------------------------------------

  All DNS queries encrypted through the tunnel:

  Browser -> ZTNA Agent -> mTLS Tunnel -> Gateway -> Internal DNS
       \                                              /
        \---- DoH to gateway.corp.com/dns-query -----/


SOLUTION 2: Split-Horizon DNS
-----------------------------

  Different DNS responses based on source:

  External DNS:  jira.internal -> NXDOMAIN
  Internal DNS:  jira.internal -> 10.0.1.50

  Configure ZTNA client to:
  1. Check if domain matches internal patterns (*.internal, *.corp.com)
  2. If yes, route DNS through tunnel
  3. If no, use local DNS server


SOLUTION 3: DNS Interception
----------------------------

  The ZTNA agent intercepts DNS at the OS level:

  1. Hook into OS DNS resolution
  2. Check if domain matches whitelist
  3. If internal: forward through tunnel
  4. If external: allow normal resolution

  Implementation:
  - macOS: Network Extension framework
  - Windows: WFP (Windows Filtering Platform)
  - Linux: eBPF or iptables REDIRECT


SOLUTION 4: PAC File with DNS Resolve
-------------------------------------

  Proxy Auto-Config (PAC) files can control routing:

  function FindProxyForURL(url, host) {
    // Check if internal domain
    if (shExpMatch(host, "*.internal") ||
        shExpMatch(host, "*.corp.com")) {
      // Also resolve DNS through proxy
      return "SOCKS5 127.0.0.1:1080";
    }
    return "DIRECT";
  }

  NOTE: PAC files have limitations - they work at connection time,
  not DNS resolution time. Use with DNS interception.


+--------------------------------------------------------------------------+
|                    Testing for DNS Leaks                                  |
+--------------------------------------------------------------------------+

TEST 1: Wireshark Capture
$ sudo tcpdump -i en0 port 53

# Then access jira.internal
# If you see the query on port 53, you have a leak


TEST 2: Check DNS Query Destination
$ dig jira.internal
# Check if it goes to internal or external DNS


TEST 3: Online Tools (for external leaks)
https://www.dnsleaktest.com/
https://ipleak.net/

+--------------------------------------------------------------------------+

Traffic Interception Techniques

How does the ZTNA client actually intercept traffic? There are several approaches with different tradeoffs.

+--------------------------------------------------------------------------+
|                    Traffic Interception Methods                           |
+--------------------------------------------------------------------------+

METHOD 1: System Proxy Settings
-------------------------------

  Configure OS to use SOCKS5 proxy at 127.0.0.1:1080

  Advantages:
  - No special privileges needed
  - Works with all apps that respect system proxy
  - Easy to implement

  Disadvantages:
  - Some apps ignore system proxy (curl -x, wget, etc.)
  - User can bypass by disabling proxy
  - DNS might still leak

  Implementation:
  - macOS: networksetup -setsocksproxy Wi-Fi 127.0.0.1 1080
  - Windows: Registry or netsh
  - Linux: environment variables + /etc/environment


METHOD 2: Proxy Auto-Configuration (PAC) File
----------------------------------------------

  JavaScript file that determines routing per-request:

  function FindProxyForURL(url, host) {
    // Internal domains go through tunnel
    if (shExpMatch(host, "*.internal")) {
      return "SOCKS5 127.0.0.1:1080";
    }
    if (shExpMatch(host, "*.corp.com")) {
      return "SOCKS5 127.0.0.1:1080";
    }

    // Everything else goes direct
    return "DIRECT";
  }

  Advantages:
  - Fine-grained routing rules
  - No need to tunnel all traffic
  - Widely supported

  Disadvantages:
  - Only works for browser traffic
  - PAC file must be hosted somewhere
  - No DNS control


METHOD 3: Transparent Proxy (iptables/pf)
-----------------------------------------

  Redirect traffic at the network layer without app awareness:

  Linux (iptables):
  $ iptables -t nat -A OUTPUT -p tcp --dport 443 \
      -m owner --uid-owner $UID \
      -j REDIRECT --to-ports 1080

  macOS (pf):
  $ echo "rdr pass on lo0 proto tcp to any port 443 -> 127.0.0.1 port 1080" \
      >> /etc/pf.anchors/ztna

  Advantages:
  - Works with ALL applications
  - No app configuration needed
  - Can intercept DNS

  Disadvantages:
  - Requires root/admin
  - OS-specific implementation
  - Can be complex to configure correctly


METHOD 4: TUN/TAP Virtual Interface
------------------------------------

  Create a virtual network interface and route traffic to it:

  [ Application ]
        |
  [ Routing Table ]
        |
        +-- 10.0.0.0/8 --> tun0 (ZTNA agent)
        |
        +-- 0.0.0.0/0 --> en0 (physical interface)

  [ tun0 ] -----> [ ZTNA Agent ] -----> [ mTLS Tunnel ]

  Advantages:
  - Full control over all traffic
  - Can implement any routing logic
  - Works at Layer 3 (IP level)

  Disadvantages:
  - Requires kernel privileges
  - Complex implementation
  - OS-specific (tun on Linux/macOS, TAP on Windows)


RECOMMENDED APPROACH FOR ZTNA:
------------------------------

  Combine methods:

  1. SOCKS5 proxy on 127.0.0.1:1080 (for apps that support it)
  2. PAC file for browser configuration (split-tunnel rules)
  3. DNS interception at OS level (prevent leaks)
  4. Optional: TUN interface for full control (enterprise deployments)

+--------------------------------------------------------------------------+

HTTP/2 Multiplexing for Tunnel Efficiency

When a user opens 10 browser tabs to internal apps, you don’t want 10 separate mTLS handshakes. HTTP/2 solves this with stream multiplexing.

+--------------------------------------------------------------------------+
|                    HTTP/2 Multiplexing for Tunnels                        |
+--------------------------------------------------------------------------+

THE PROBLEM WITHOUT MULTIPLEXING:
---------------------------------

  Tab 1 (jira.internal)   ----[ mTLS Handshake 1 ]----> Gateway
  Tab 2 (gitlab.internal) ----[ mTLS Handshake 2 ]----> Gateway
  Tab 3 (wiki.internal)   ----[ mTLS Handshake 3 ]----> Gateway
  Tab 4 (jira.internal)   ----[ mTLS Handshake 4 ]----> Gateway
  ...

  Each handshake: ~100-300ms
  10 tabs = 1-3 seconds of latency!


WITH HTTP/2 MULTIPLEXING:
-------------------------

  Tab 1 ─┐
  Tab 2 ─┼── [ Single mTLS Tunnel ] ──[ Multiplexed Streams ]──> Gateway
  Tab 3 ─┤          |                                              |
  Tab 4 ─┘          |                                              |
                    v                                              v
           Stream 1 (jira:443)      ╔══════════════════════════════╗
           Stream 2 (gitlab:443)    ║  HTTP/2 Connection           ║
           Stream 3 (wiki:443)      ║  - Stream 1: jira            ║
           Stream 4 (jira:443)      ║  - Stream 2: gitlab          ║
                                    ║  - Stream 3: wiki            ║
                                    ║  - Stream 4: jira (reuse)    ║
                                    ╚══════════════════════════════╝

  One handshake: ~100-300ms
  10 tabs = still ~100-300ms!


+--------------------------------------------------------------------------+
|                    HTTP/2 Stream Structure                                |
+--------------------------------------------------------------------------+

  +-----------------------------------------------+
  |           HTTP/2 Connection                   |
  +-----------------------------------------------+
  |  Stream 1  |  Stream 2  |  Stream 3  | ...    |
  |  (jira)    |  (gitlab)  |  (wiki)    |        |
  +------------+------------+------------+--------+
  |            |            |            |        |
  | HEADERS    | HEADERS    | HEADERS    |        |
  | DATA       | DATA       | DATA       |        |
  | DATA       | DATA       | HEADERS    |        |
  | END_STREAM | DATA       | DATA       |        |
  |            | END_STREAM | END_STREAM |        |
  +-----------------------------------------------+

  All streams share ONE TCP connection
  Frames are interleaved on the wire


+--------------------------------------------------------------------------+
|                    Head-of-Line Blocking Problem                          |
+--------------------------------------------------------------------------+

HTTP/2 still uses TCP, which has head-of-line blocking:

  [ Stream 1 Frame ] [ Stream 2 Frame ] [ X Packet Loss ] [ Stream 3 Frame ]
                                              |
                                              v
                          TCP retransmits before delivering Stream 3
                          Even though Stream 3's packets arrived!


SOLUTION: HTTP/3 (QUIC)
-----------------------

  QUIC uses UDP with per-stream reliability:

  [ Stream 1 ] ────────> Independent delivery
  [ Stream 2 ] ────────> Independent delivery
  [ Stream 3 ] ────────> Independent delivery

  Packet loss in Stream 1 doesn't block Stream 2 or 3.

  For ZTNA: HTTP/3 is ideal but complex.
  Start with HTTP/2, consider QUIC for production.

+--------------------------------------------------------------------------+
|                    Implementing Multiplexing in the Tunnel                |
+--------------------------------------------------------------------------+

ARCHITECTURE:

  Client Agent:
  +------------------------------------------------------------------+
  |                                                                   |
  |  [ SOCKS5 Server ]                                               |
  |        |                                                          |
  |        v                                                          |
  |  [ Stream Manager ]                                               |
  |        |                                                          |
  |        +-- Stream 1: jira.internal:443 -> gRPC/HTTP2 Stream 1    |
  |        +-- Stream 2: gitlab.internal:443 -> gRPC/HTTP2 Stream 2  |
  |        +-- Stream 3: wiki.internal:443 -> gRPC/HTTP2 Stream 3    |
  |        |                                                          |
  |        v                                                          |
  |  [ HTTP/2 Client ] ---- mTLS ----> Gateway                       |
  |                                                                   |
  +------------------------------------------------------------------+


PSEUDO-CODE (Go):

  // Create HTTP/2 transport
  transport := &http2.Transport{
    TLSClientConfig: &tls.Config{
      Certificates: []tls.Certificate{clientCert},
      RootCAs:      caCertPool,
    },
  }

  // Reuse connection for all streams
  client := &http.Client{Transport: transport}

  func handleSOCKS5Connection(conn net.Conn) {
    target := parseSocks5Request(conn)

    // Use HTTP/2 CONNECT for each stream
    req, _ := http.NewRequest("CONNECT",
      fmt.Sprintf("https://gateway.corp.com/tunnel/%s", target), nil)

    resp, _ := client.Do(req)

    // Bidirectional copy
    go io.Copy(conn, resp.Body)
    io.Copy(resp.Body, conn)
  }

+--------------------------------------------------------------------------+

Identity Propagation Through the Tunnel

The tunnel must carry the user’s identity to the gateway so the gateway can inject identity headers into requests to internal services.

+--------------------------------------------------------------------------+
|                    Identity Flow Through ZTNA Tunnel                      |
+--------------------------------------------------------------------------+

IDENTITY SOURCES:
-----------------

  1. User Identity (from certificate):
     - Common Name: douglas@corp.com
     - SPIFFE ID: spiffe://corp.com/user/douglas

  2. Device Identity (from certificate or attestation):
     - Device ID: macbook-42
     - Device Posture: SECURE / AT_RISK

  3. Session Context:
     - Client IP: 203.0.113.42
     - Session ID: sess-abc123
     - Connection Time: 2024-12-27T10:00:00Z


IDENTITY PROPAGATION:
---------------------

  [ User's Browser ]
        |
        | Request: GET https://jira.internal/dashboard
        |
        v
  [ ZTNA Client Agent ]
        |
        | Wrap in tunnel with identity:
        | - Identity: douglas@corp.com
        | - Device: macbook-42
        | - Posture: SECURE
        |
        v
  [ mTLS Tunnel ]
        |
        | TLS Client Certificate contains:
        | - CN=douglas@corp.com
        | - SAN URI=spiffe://corp.com/user/douglas
        | - SAN URI=spiffe://corp.com/device/macbook-42
        |
        v
  [ ZTNA Gateway (Project 1 Proxy) ]
        |
        | Extract identity from TLS cert
        | Check policy (Project 2) for this user+device+app
        | Inject headers:
        |   X-ZT-Identity: douglas@corp.com
        |   X-ZT-Device: macbook-42
        |   X-ZT-Posture: SECURE
        |   X-ZT-Session: sess-abc123
        |
        v
  [ jira.internal ]
        |
        | Receives request with identity headers
        | No login required - identity already verified!
        |
        v
  [ Response flows back through tunnel ]


+--------------------------------------------------------------------------+
|                    Header Injection by Gateway                            |
+--------------------------------------------------------------------------+

The gateway (Project 1) does the actual header injection:

  INCOMING REQUEST (from tunnel):
  +------------------------------------------+
  | CONNECT jira.internal:443 HTTP/2         |
  | X-ZT-Client-IP: 203.0.113.42             |
  | (TLS cert contains user identity)        |
  +------------------------------------------+

  AFTER TERMINATING mTLS:
  +------------------------------------------+
  | Gateway extracts from cert:              |
  | - Subject CN: douglas@corp.com           |
  | - SPIFFE ID: spiffe://corp.com/user/...  |
  +------------------------------------------+

  FORWARDED TO INTERNAL SERVICE:
  +------------------------------------------+
  | GET /dashboard HTTP/1.1                  |
  | Host: jira.internal                      |
  | X-ZT-Identity: douglas@corp.com          |
  | X-ZT-Roles: admin,developer              |
  | X-ZT-Device: macbook-42                  |
  | X-ZT-Posture: SECURE                     |
  | X-ZT-Session: sess-abc123                |
  | X-ZT-Verified: true                      |
  | X-Forwarded-For: 203.0.113.42            |
  +------------------------------------------+


+--------------------------------------------------------------------------+
|                    Making Internal Apps "Login-Free"                      |
+--------------------------------------------------------------------------+

  BEFORE ZTNA:

    1. User connects VPN
    2. User opens jira.internal
    3. Jira shows login page
    4. User enters username/password
    5. User accesses dashboard


  AFTER ZTNA:

    1. User has ZTNA agent running
    2. User opens jira.internal
    3. ZTNA tunnels request with identity
    4. Jira receives X-ZT-Identity header
    5. Jira trusts header (from known gateway)
    6. User directly sees dashboard - NO LOGIN!


  INTERNAL APP CONFIGURATION:

    Jira (or any internal app) must:
    1. Trust the X-ZT-* headers from the gateway
    2. Map X-ZT-Identity to internal user
    3. Skip normal authentication when headers present

    Security: App must ONLY accept these headers from
    the gateway. Usually done via network rules or
    checking that request came from gateway IP.

+--------------------------------------------------------------------------+

Project Specification

Functional Requirements

ID Requirement Acceptance Criteria
FR-1 Accept SOCKS5 connections on localhost Listen on 127.0.0.1:1080, respond to SOCKS5 handshake
FR-2 Route based on domain whitelist Match *.internal, *.corp.com; bypass all others
FR-3 Establish mTLS tunnel to gateway Connect with client certificate, verify gateway cert
FR-4 Tunnel traffic through gateway Forward TCP bytes bidirectionally
FR-5 Multiplex multiple connections Use HTTP/2 or gRPC for stream multiplexing
FR-6 Prevent DNS leaks Intercept DNS for internal domains
FR-7 Log all routing decisions Show tunnel/bypass decisions in terminal
FR-8 Load whitelist from config file YAML configuration for domains and gateway URL
FR-9 Graceful shutdown Complete active streams before exit
FR-10 Pass identity through tunnel Include user/device info in tunnel metadata

Non-Functional Requirements

ID Requirement Target
NFR-1 Connection latency overhead < 50ms added latency per connection
NFR-2 Throughput > 100 Mbps through tunnel
NFR-3 Concurrent connections Handle 100+ simultaneous streams
NFR-4 Memory usage < 50MB under normal load
NFR-5 Certificate rotation Support rotation without connection drops
NFR-6 Reconnection Auto-reconnect on tunnel failure

Architecture Diagram

+--------------------------------------------------------------------------+
|                    ZTNA App Tunnel Architecture                           |
+--------------------------------------------------------------------------+

                           [ User's Machine ]
  +------------------------------------------------------------------+
  |                                                                   |
  |  [ Browser ]  [ SSH Client ]  [ DB Client ]  [ Any App ]         |
  |       |             |              |              |               |
  |       v             v              v              v               |
  |  +----------------------------------------------------------+    |
  |  |                   ZTNA Client Agent                      |    |
  |  |                                                          |    |
  |  |  +------------------+    +-------------------------+     |    |
  |  |  | SOCKS5 Server    |    | Domain Router           |     |    |
  |  |  | 127.0.0.1:1080   |    | - Check whitelist       |     |    |
  |  |  |                  |    | - TUNNEL or BYPASS      |     |    |
  |  |  +------------------+    +-------------------------+     |    |
  |  |           |                         |                    |    |
  |  |           v                         v                    |    |
  |  |  +------------------+    +-------------------------+     |    |
  |  |  | Stream Manager   |    | Direct Connection       |     |    |
  |  |  | (HTTP/2 Mux)     |    | (for non-internal)      |     |    |
  |  |  +------------------+    +-------------------------+     |    |
  |  |           |                         |                    |    |
  |  |           v                         |                    |    |
  |  |  +------------------+               |                    |    |
  |  |  | mTLS Client      |               |                    |    |
  |  |  | (Project 4 cert) |               |                    |    |
  |  |  +------------------+               |                    |    |
  |  |           |                         |                    |    |
  |  +-----------|-------------------------|--------------------+    |
  |              |                         |                         |
  +--------------|-------------------------|-------------------------+
                 |                         |
                 v                         v
      [ Secure mTLS Tunnel ]        [ Direct Internet ]
                 |                         |
                 v                         v
      +----------------------+       [ Public Web ]
      | ZTNA Gateway         |
      | (Project 1 Proxy)    |
      |                      |
      | - Terminates mTLS    |
      | - Injects identity   |
      | - Checks policy (P2) |
      +----------+-----------+
                 |
                 v
      +----------------------+
      | Internal Services    |
      | - jira.internal      |
      | - gitlab.internal    |
      | - wiki.internal      |
      +----------------------+

+--------------------------------------------------------------------------+

Component Breakdown

Component Responsibility Key APIs
Config Loader Parse YAML config, load domain whitelist LoadConfig(path) -> Config
SOCKS5 Server Accept SOCKS5 connections, parse requests Listen(), Accept(), HandleConnection()
Domain Router Decide tunnel vs bypass based on domain ShouldTunnel(domain) -> bool
Stream Manager Multiplex streams over HTTP/2 CreateStream(target) -> Stream
mTLS Client Manage mTLS connection to gateway Connect(), Reconnect()
DNS Interceptor Capture DNS for internal domains InterceptDNS(), ResolveThroughTunnel()
Identity Provider Load and provide user/device identity GetIdentity() -> Identity

Solution Architecture

Configuration Schema

# corp-apps.yaml
gateway:
  url: "https://gateway.corp.com:8443"
  certificate: "/path/to/client.crt"
  key: "/path/to/client.key"
  ca: "/path/to/ca.crt"

identity:
  user: "douglas@corp.com"
  device: "macbook-42"

tunneled_domains:
  - "*.internal"
  - "*.corp.com"
  - "jira.internal"
  - "gitlab.internal"
  - "wiki.internal"
  - "10.0.0.0/8"  # Optional: tunnel by IP range

socks5:
  listen: "127.0.0.1:1080"

dns:
  intercept: true
  internal_resolver: "10.0.0.1:53"

logging:
  level: "debug"
  format: "json"

Domain Matching Engine

The domain router needs efficient matching for patterns like *.internal:

+--------------------------------------------------------------------------+
|                    Domain Matching Logic                                  |
+--------------------------------------------------------------------------+

INPUT: "jira.internal"

RULES:
  1. Exact match: "jira.internal" matches "jira.internal"
  2. Wildcard:    "jira.internal" matches "*.internal"
  3. Suffix:      "jira.internal" matches ".internal"
  4. IP Range:    "10.0.1.50" matches "10.0.0.0/8"

ALGORITHM:

  func ShouldTunnel(domain string) bool {
    // 1. Check exact match
    if _, exists := exactMatches[domain]; exists {
      return true
    }

    // 2. Check wildcard patterns
    for _, pattern := range wildcardPatterns {
      if matchWildcard(domain, pattern) {
        return true
      }
    }

    // 3. Check IP ranges (if domain is IP)
    if ip := net.ParseIP(domain); ip != nil {
      for _, cidr := range ipRanges {
        if cidr.Contains(ip) {
          return true
        }
      }
    }

    return false  // BYPASS - go direct
  }

+--------------------------------------------------------------------------+

Local SOCKS5 Server

The SOCKS5 server is the entry point for client traffic:

+--------------------------------------------------------------------------+
|                    SOCKS5 Server Flow                                     |
+--------------------------------------------------------------------------+

  Accept Connection
        |
        v
  Read Auth Negotiation (VER, NMETHODS, METHODS)
        |
        v
  Send Auth Response (VER, 0x00 = no auth)
        |
        v
  Read Connection Request (VER, CMD, ATYP, ADDR, PORT)
        |
        +------ CMD = 0x01 (CONNECT) ?
        |               |
        |          Yes  |           No
        |               v            |
        |       Extract target       v
        |       domain and port   Send error (0x07)
        |               |           Close
        |               v
        |       ShouldTunnel(domain)?
        |               |
        |          Yes  |           No
        |               v            |
        |       Create tunnel       v
        |       stream         Direct connect
        |               |           to target
        |               v            |
        +--------> Send success (0x00)
                        |
                        v
                  Bidirectional copy
                  (client <-> target)
                        |
                        v
                  Connection closed

+--------------------------------------------------------------------------+

mTLS Connection Manager

Maintains a persistent mTLS connection to the gateway:

+--------------------------------------------------------------------------+
|                    mTLS Connection Manager                                |
+--------------------------------------------------------------------------+

  +-------------------------------------------------------------+
  |                   Connection Manager                         |
  +-------------------------------------------------------------+
  |                                                              |
  |  State: CONNECTED / CONNECTING / DISCONNECTED                |
  |                                                              |
  |  [ Certificate Manager ]  <-- From Project 4                 |
  |  | - Current cert                                            |
  |  | - Rotation timer                                          |
  |  | - CA trust store                                          |
  |                                                              |
  |  [ HTTP/2 Client ]                                           |
  |  | - Connection pool (1 connection typically)                |
  |  | - Stream multiplexer                                      |
  |  | - Keepalive (PING frames)                                 |
  |                                                              |
  |  [ Reconnection Logic ]                                      |
  |  | - Exponential backoff                                     |
  |  | - Max retries                                             |
  |  | - Connection health monitoring                            |
  |                                                              |
  +-------------------------------------------------------------+

  API:

  func (cm *ConnectionManager) CreateStream(target string) (Stream, error)
  func (cm *ConnectionManager) Close() error
  func (cm *ConnectionManager) IsHealthy() bool

+--------------------------------------------------------------------------+

Stream Multiplexer

Uses HTTP/2 (or gRPC) to multiplex multiple TCP connections over one mTLS tunnel:

+--------------------------------------------------------------------------+
|                    Stream Multiplexer Design                              |
+--------------------------------------------------------------------------+

  Protocol Choice: HTTP/2 with CONNECT or gRPC bidirectional streams


  OPTION 1: HTTP/2 CONNECT
  -------------------------

    Client sends:

      CONNECT jira.internal:443 HTTP/2
      Host: jira.internal:443
      X-ZT-Stream-ID: stream-001
      X-ZT-Identity: douglas@corp.com

    Gateway responds:

      HTTP/2 200 OK
      X-ZT-Bound-To: jira.internal:443

    Then: Raw TCP bytes flow over HTTP/2 DATA frames


  OPTION 2: gRPC Bidirectional Stream
  -----------------------------------

    proto:

      service Tunnel {
        rpc Connect(stream TunnelData) returns (stream TunnelData);
      }

      message TunnelData {
        oneof message {
          ConnectRequest connect = 1;
          ConnectResponse connect_response = 2;
          DataChunk data = 3;
          CloseStream close = 4;
        }
      }

      message ConnectRequest {
        string target = 1;  // "jira.internal:443"
        string identity = 2;
      }


  RECOMMENDATION:
  ---------------

    Start with HTTP/2 CONNECT:
    - Simpler to implement
    - Standard protocol
    - Gateway (Project 1) can be a standard HTTP/2 proxy

    Consider gRPC for:
    - Multiple streams per request
    - Custom metadata per stream
    - Better error handling

+--------------------------------------------------------------------------+

Phased Implementation Guide

Phase 1: Basic SOCKS5 Proxy (Pass-Through)

Goal: Build a functional SOCKS5 proxy that directly connects to any target.

Deliverables:

  • SOCKS5 server listening on 127.0.0.1:1080
  • Handles SOCKS5 authentication negotiation (no auth)
  • Handles CONNECT command for domain and IP targets
  • Bidirectional data forwarding

Steps:

  1. Parse SOCKS5 authentication negotiation
  2. Parse SOCKS5 connection request
  3. Resolve target domain (using system DNS)
  4. Connect to target directly
  5. Forward data bidirectionally

Verification:

# Start your SOCKS5 proxy
$ ./ztna-tunnel --socks5-only

# Test with curl
$ curl -x socks5h://127.0.0.1:1080 https://example.com
# Should return example.com's HTML

# Test with Firefox (set network.proxy.socks to 127.0.0.1:1080)

Phase 2: Domain-Based Routing (Split Tunnel)

Goal: Route internal domains through the tunnel, bypass others.

Deliverables:

  • Load domain whitelist from config file
  • Domain matching engine (exact, wildcard, suffix)
  • Route matching domains to tunnel stub
  • Route non-matching domains directly

Steps:

  1. Implement config loader (YAML)
  2. Build domain matcher with wildcard support
  3. Add routing decision after SOCKS5 request parsing
  4. For now, return error for tunneled domains (no gateway yet)

Verification:

# Configure whitelist for *.internal
$ ./ztna-tunnel --config ./corp-apps.yaml

# Test tunneled domain (should fail - no gateway yet)
$ curl -x socks5h://127.0.0.1:1080 https://jira.internal
# Expected: Connection refused (tunnel not implemented)

# Test bypassed domain (should work)
$ curl -x socks5h://127.0.0.1:1080 https://example.com
# Expected: Success (direct connection)

Phase 3: mTLS Upstream Connection

Goal: Establish secure mTLS tunnel to the gateway (Project 1).

Deliverables:

  • Load client certificate and key
  • Connect to gateway with mTLS
  • Verify gateway certificate
  • Handle connection errors gracefully

Steps:

  1. Load certificates from config paths
  2. Configure TLS with client cert and CA trust
  3. Establish HTTP/2 connection to gateway
  4. Implement health check (PING/keepalive)
  5. Implement reconnection with backoff

Verification:

# Start Project 1 gateway with mTLS
$ ./zta-proxy --mtls --listen :8443

# Start tunnel client
$ ./ztna-tunnel --config ./corp-apps.yaml

# Check tunnel connection
# Look for: [INFO] Connected to gateway.corp.com

Phase 4: Tunnel Traffic Through Gateway

Goal: Route internal domain traffic through the mTLS tunnel.

Deliverables:

  • Create HTTP/2 CONNECT stream for each connection
  • Forward SOCKS5 connection through tunnel stream
  • Bidirectional data forwarding through tunnel
  • Stream cleanup on connection close

Steps:

  1. Implement HTTP/2 CONNECT request to gateway
  2. Map SOCKS5 connection to HTTP/2 stream
  3. Forward data: client -> tunnel -> gateway -> target
  4. Forward data: target -> gateway -> tunnel -> client
  5. Handle stream errors and cleanup

Verification:

# Start Project 1 gateway with backend on 8081
$ ./zta-proxy --backend http://localhost:8081 --mtls

# Start simple backend
$ python3 -m http.server 8081

# Start tunnel
$ ./ztna-tunnel --config ./corp-apps.yaml

# Test tunneled request
$ curl -x socks5h://127.0.0.1:1080 http://localhost.internal:8081/
# Should return Python server directory listing

Phase 5: Identity Injection

Goal: Pass user and device identity through the tunnel.

Deliverables:

  • Include identity in tunnel handshake/metadata
  • Gateway extracts and injects X-ZT-* headers
  • Identity visible in backend logs

Steps:

  1. Add identity to CONNECT request headers
  2. Update gateway to extract identity from tunnel
  3. Gateway injects X-ZT-Identity, X-ZT-Device headers
  4. Verify headers arrive at backend

Verification:

# Start backend that echoes headers
$ python3 -c "
from http.server import *
class H(SimpleHTTPRequestHandler):
    def do_GET(self):
        print(self.headers)
        self.send_response(200)
        self.end_headers()
HTTPServer(('', 8081), H).serve_forever()
"

# Tunnel request - check backend logs for X-ZT-* headers
$ curl -x socks5h://127.0.0.1:1080 http://internal.local:8081/
# Backend should show: X-ZT-Identity: douglas@corp.com

Phase 6: DNS Leak Prevention

Goal: Ensure DNS for internal domains goes through tunnel.

Deliverables:

  • Intercept DNS queries at SOCKS5 level
  • Forward internal domain DNS through tunnel
  • Allow external domain DNS to go direct

Steps:

  1. Use SOCKS5h (DNS through proxy) mode
  2. For internal domains, resolve through tunnel
  3. Implement DNS resolution endpoint on gateway
  4. Cache DNS responses for performance

Verification:

# Run Wireshark or tcpdump
$ sudo tcpdump -i en0 port 53

# Make tunneled request
$ curl -x socks5h://127.0.0.1:1080 https://jira.internal/

# Check tcpdump - should NOT see jira.internal DNS query
# The 'h' in socks5h means DNS is resolved by the proxy

Phase 7: HTTP/2 Multiplexing Optimization

Goal: Efficiently handle multiple concurrent connections.

Deliverables:

  • Single HTTP/2 connection for all streams
  • Proper stream lifecycle management
  • Connection health monitoring
  • Graceful degradation under load

Steps:

  1. Verify HTTP/2 multiplexing is working
  2. Add stream tracking and limits
  3. Implement connection health monitoring
  4. Handle gateway disconnection gracefully

Verification:

# Open multiple concurrent connections
$ for i in {1..10}; do
    curl -x socks5h://127.0.0.1:1080 https://jira.internal/ &
  done
  wait

# Check tunnel logs - should show:
# - Single mTLS handshake (not 10)
# - 10 streams created on same connection

Testing Strategy

Unit Tests

Component Test Case Expected Result
SOCKS5 Parser Valid auth negotiation Parse correctly
SOCKS5 Parser Invalid version (0x04) Return error
SOCKS5 Parser CONNECT with domain Extract domain and port
SOCKS5 Parser CONNECT with IPv4 Extract IP and port
SOCKS5 Parser CONNECT with IPv6 Extract IP and port
Domain Matcher Exact match “jira.internal” Return true
Domain Matcher Wildcard “*.internal” matches “jira.internal” Return true
Domain Matcher Wildcard “*.internal” doesn’t match “jira.com” Return false
Domain Matcher IP range “10.0.0.0/8” contains “10.0.1.50” Return true
Config Loader Valid YAML Load all fields
Config Loader Missing required field Return error

Integration Tests

Scenario Setup Expected Behavior
Tunneled HTTP Gateway running, internal backend Request succeeds, identity headers present
Tunneled HTTPS Gateway with TLS backend TLS works end-to-end
Bypassed traffic No gateway needed Direct connection works
Gateway down Gateway offline Graceful error, retry logic
Gateway reconnect Gateway restarts Auto-reconnect, pending requests wait
Multiple streams 20 concurrent requests All succeed, multiplexed
Large transfer 100MB file download Completes without memory issues

Security Tests

Attack Vector Test Method Expected Defense
DNS leak tcpdump for internal DNS No internal DNS on external interface
Tunnel bypass Direct connection to internal IP Connection refused (no route)
Identity spoofing Send fake X-ZT-* headers Headers stripped by gateway
Certificate mismatch Use cert from different CA Connection refused
Expired certificate Use expired cert Connection refused
Man-in-the-middle Intercept tunnel traffic TLS failure

Performance Tests

Latency Test:

# Measure overhead
$ time curl -x socks5h://127.0.0.1:1080 https://jira.internal/ping
# Compare to direct (when VPN would be used)

Throughput Test:

# Download large file
$ curl -x socks5h://127.0.0.1:1080 https://internal.local/100mb.bin -o /dev/null
# Should achieve >100Mbps on local network

Concurrency Test:

# 100 concurrent connections
$ hey -n 1000 -c 100 -x socks5h://127.0.0.1:1080 https://jira.internal/

Common Pitfalls and Debugging

Pitfall 1: DNS Leaks

Symptom: Internal domain names visible on public DNS logs or tcpdump

Cause: Using socks5:// instead of socks5h://, or DNS resolution happening before SOCKS5

Debugging:

$ sudo tcpdump -i en0 port 53
# Make request and watch for internal domain queries

Solution:

  • Always use socks5h:// (DNS through proxy)
  • Implement DNS interception at OS level for non-SOCKS apps
  • Configure browser to use remote DNS resolution

Pitfall 2: Certificate Errors

Symptom: “certificate signed by unknown authority” or similar TLS errors

Cause: CA not trusted, certificate expired, or wrong certificate chain

Debugging:

# Check certificate details
$ openssl s_client -connect gateway.corp.com:8443 \
    -cert client.crt -key client.key -CAfile ca.crt

# Verify certificate chain
$ openssl verify -CAfile ca.crt client.crt

Solution:

  • Verify CA certificate is correct
  • Check certificate expiration dates
  • Ensure full certificate chain is provided

Pitfall 3: Browser Proxy Configuration

Symptom: Browser doesn’t use the SOCKS5 proxy

Cause: Browser settings override, PAC file issues, or extension conflicts

Debugging:

# Test with curl first (known to work)
$ curl -x socks5h://127.0.0.1:1080 https://jira.internal/

# Check browser proxy settings
# Chrome: chrome://settings/system -> Proxy settings
# Firefox: about:preferences -> Network Settings

Solution:

  • Use a PAC file for automatic configuration
  • Disable proxy extensions that might conflict
  • For testing, use Firefox’s built-in SOCKS5 settings

Pitfall 4: Non-HTTP Protocol Issues

Symptom: SSH or database connections fail through tunnel

Cause: Protocol expects specific behavior that tunnel doesn’t handle

Debugging:

# Test SSH through tunnel
$ ssh -o ProxyCommand='nc -x 127.0.0.1:1080 %h %p' internal-server

# Check for SOCKS5 UDP support (needed for some protocols)

Solution:

  • Ensure SOCKS5 CONNECT command is fully implemented
  • For UDP protocols, implement SOCKS5 UDP ASSOCIATE (0x03)
  • Some protocols may need additional tunnel configuration

Pitfall 5: Connection Pooling Issues

Symptom: “too many open files” or connection refused after many requests

Cause: Not properly closing streams or connections

Debugging:

# Check open connections
$ lsof -i -P | grep ztna-tunnel

# Check for connection leaks
$ netstat -an | grep 1080

Solution:

  • Implement proper stream cleanup on completion
  • Add connection limits
  • Implement idle timeout for unused streams

Pitfall 6: Multiplexing Not Working

Symptom: Multiple mTLS handshakes visible for concurrent requests

Cause: HTTP/2 not properly configured, or new connection per request

Debugging:

# Check HTTP/2 is negotiated
$ curl -v --http2 https://gateway.corp.com:8443/

# Wireshark: filter for TLS handshakes
# Should see only 1 handshake for multiple streams

Solution:

  • Ensure HTTP/2 is enabled on both client and gateway
  • Reuse http.Client with connection pooling
  • Check for transport configuration that forces HTTP/1.1

Extensions and Challenges

Extension 1: UDP Support (SOCKS5 UDP ASSOCIATE)

Implement the SOCKS5 UDP ASSOCIATE command for protocols like DNS and some games.

Challenge: UDP doesn’t have connections - you’ll need to relay packets.

RFC Reference: RFC 1928, Section 7

Extension 2: Mobile Client (iOS/Android)

Build a mobile ZTNA client using platform-specific networking APIs.

iOS: Network Extension framework for VPN-like functionality Android: VpnService API for creating a virtual network interface

Extension 3: Browser Extension

Instead of system proxy, build a browser extension that intercepts requests.

Advantage: Works without system-level changes Challenge: Limited to browser traffic only

Extension 4: QUIC/HTTP3 Support

Replace HTTP/2 with HTTP/3 (QUIC) for better performance.

Advantage: No head-of-line blocking, faster handshakes Challenge: QUIC is more complex to implement

Extension 5: Kubernetes Sidecar Deployment

Deploy the ZTNA client as a sidecar proxy in Kubernetes pods.

Use Case: Outbound traffic from pods to internal services Integration: Works with service mesh concepts

Extension 6: Policy Integration (Project 2)

Have the tunnel check with the Policy Decision Engine before tunneling.

Flow: User requests app -> Tunnel asks PDP -> PDP approves/denies


Real-World Connections

How Commercial Products Implement This

Product Architecture Key Differentiator
Cloudflare Access / WARP Global edge PoPs, WARP client Anycast routing, massive scale
Zscaler ZPA App Connectors inside network Outbound-only connections (no inbound)
Google BeyondCorp Remote Access IAP (Identity-Aware Proxy) Deep GCP integration
Tailscale WireGuard mesh network Peer-to-peer where possible
Palo Alto Prisma Access Cloud-delivered SASE Full security stack

What Makes Commercial Products Different

  1. Global Points of Presence (PoPs)
    • Your tunnel: Single gateway
    • Commercial: 200+ PoPs worldwide for low latency
  2. Mobile Device Management (MDM)
    • Your tunnel: Basic device identity
    • Commercial: Full MDM integration, device posture checks
  3. Threat Prevention
    • Your tunnel: Tunneling only
    • Commercial: Inline malware scanning, DLP, CASB
  4. High Availability
    • Your tunnel: Single gateway
    • Commercial: Automatic failover, load balancing
  5. Compliance Features
    • Your tunnel: Basic logging
    • Commercial: Audit trails, compliance reports, SIEM integration

Interview Questions

Conceptual Questions

Q1: What is ZTNA and how does it differ from a VPN?

Expected Answer:

  • VPN connects user’s device to a NETWORK
  • ZTNA connects user to a specific APPLICATION
  • VPN: “You’re inside the network, access anything”
  • ZTNA: “You can access jira.internal, nothing else”
  • Attack surface with VPN: entire network
  • Attack surface with ZTNA: specific apps only

Q2: Explain split tunneling and why it’s the default for Zero Trust.

Expected Answer:

  • Split tunnel: Only internal traffic goes through tunnel
  • Full tunnel: All traffic goes through VPN
  • Split tunnel advantages:
    • Better performance (no VPN hop for internet)
    • Less bandwidth on tunnel
    • Privacy (personal traffic not inspected)
  • Zero Trust doesn’t care about protecting users from the internet
  • It cares about protecting apps from users

Q3: What is a DNS leak and why is it dangerous?

Expected Answer:

  • DNS query for internal domain (jira.internal) goes to public DNS
  • Public DNS now knows internal service names
  • This reveals:
    • Company infrastructure details
    • Naming conventions
    • Which services exist
  • Attackers can use this for reconnaissance
  • Prevention: Route internal DNS through tunnel

Q4: Why use SOCKS5 instead of HTTP proxy for ZTNA?

Expected Answer:

  • SOCKS5 is Layer 4 (Transport)
  • HTTP proxy is Layer 7 (Application)
  • SOCKS5 can tunnel ANY TCP/UDP traffic
  • HTTP proxy only works for HTTP/HTTPS
  • ZTNA needs to support: SSH, databases, RDP, custom protocols
  • SOCKS5 is protocol-agnostic

Q5: How does mTLS secure the tunnel?

Expected Answer:

  • mTLS = Mutual TLS = both sides authenticate
  • Client proves identity with certificate
  • Server proves identity with certificate
  • Both are verified by trusted CA
  • If attacker intercepts tunnel: Can’t impersonate either side
  • If attacker compromises client: Certificate revoked, no access
  • Connection to Project 4 concepts

Technical Deep-Dives

Q6: Walk through what happens when a user accesses jira.internal through your tunnel.

Expected Answer:

  1. Browser configured to use SOCKS5 proxy at 127.0.0.1:1080
  2. Browser sends SOCKS5 CONNECT request for jira.internal:443
  3. ZTNA client checks domain against whitelist - matches *.internal
  4. Client creates HTTP/2 CONNECT stream to gateway
  5. Gateway verifies mTLS client certificate
  6. Gateway establishes connection to jira.internal
  7. Gateway injects X-ZT-Identity headers
  8. Response flows back through tunnel to browser
  9. All on a single mTLS connection (multiplexed)

Q7: How would you handle the case where the gateway becomes unreachable?

Expected Answer:

  1. Detect connection failure (read/write error, timeout)
  2. Mark connection as unhealthy
  3. Queue new requests (don’t fail immediately)
  4. Attempt reconnection with exponential backoff
  5. After reconnection, resume queued requests
  6. If max retries exceeded, fail pending requests gracefully
  7. Show user-friendly error: “Cannot reach internal services”
  8. Continue bypassed traffic normally

Q8: How do you prevent the tunnel from becoming a vector for lateral movement?

Expected Answer:

  • Tunnel only allows specific domains, not IP ranges
  • Each application requires explicit authorization
  • Gateway (Project 1) enforces policy per-request
  • Policy engine (Project 2) can require re-authentication
  • Device posture (Project 5) blocks compromised devices
  • No ability to scan network - only reach authorized apps
  • Even if agent is compromised, blast radius is limited

Q9: What are the tradeoffs between HTTP/2 and gRPC for the tunnel protocol?

Expected Answer:

  • HTTP/2 CONNECT:
    • Standard protocol
    • Gateway can be any HTTP/2 proxy
    • Simpler to implement
    • Works with existing tooling
  • gRPC:
    • Better error handling (status codes)
    • Easier streaming semantics
    • Built-in serialization
    • More flexibility for metadata
  • Recommendation: Start with HTTP/2 for simplicity

Q10: If you could only implement one DNS leak prevention method, which would it be?

Expected Answer:

  • Use SOCKS5h (DNS resolved by proxy)
  • Why: It works at the application level
  • Browser resolves DNS through the proxy, not locally
  • No special OS privileges needed
  • Covers the most common case (browser traffic)
  • Limitation: Doesn’t protect non-SOCKS-aware apps
  • For full protection: Also implement OS-level DNS interception

Resources and Self-Assessment

Essential Reading

Resource Focus Chapter/Section
“TCP/IP Illustrated, Volume 1” by Stevens TCP, UDP, and DNS fundamentals Chapters 17-18 (TCP), Chapter 14 (DNS)
“Zero Trust Networks” by Gilman & Barth ZTNA architecture Chapters 9-10
“Computer Networks” by Tanenbaum Networking fundamentals, VPNs Section 8.8 (VPNs)
“TCP/IP Sockets in C” by Donahoo Socket programming Full book
RFC 1928 SOCKS5 Protocol Complete RFC
RFC 7230 HTTP/1.1 Message Syntax CONNECT method
RFC 9113 HTTP/2 Streams and multiplexing

RFCs to Reference

  • RFC 1928 - SOCKS Protocol Version 5
  • RFC 1929 - Username/Password Authentication for SOCKS5
  • RFC 7230 - HTTP/1.1 Message Syntax (CONNECT method)
  • RFC 9113 - HTTP/2
  • RFC 8446 - TLS 1.3
  • RFC 9000 - QUIC (if implementing HTTP/3)

Tools for Development and Testing

  • curl - Testing SOCKS5 with -x socks5h://
  • Wireshark - Analyzing TLS handshakes and HTTP/2 streams
  • tcpdump - Detecting DNS leaks
  • openssl s_client - Testing mTLS connections
  • Firefox - Built-in SOCKS5 proxy settings
  • Proxifier (Windows/macOS) - Force apps through proxy

Self-Assessment Checklist

Conceptual Understanding:

  • I can explain why ZTNA is more secure than VPN
  • I can describe split tunneling and its security implications
  • I understand the SOCKS5 protocol at the packet level
  • I can explain DNS leaks and prevention methods
  • I understand HTTP/2 multiplexing and its benefits
  • I can articulate how mTLS secures the tunnel

Implementation Verification:

  • My SOCKS5 server correctly handles authentication negotiation
  • My SOCKS5 server correctly parses CONNECT requests
  • Domain-based routing correctly splits tunnel/bypass traffic
  • mTLS connection to gateway establishes successfully
  • Traffic flows bidirectionally through the tunnel
  • Identity is passed through tunnel and injected by gateway
  • DNS for internal domains does NOT leak

Security Verification:

  • No internal DNS queries visible on public network
  • Direct connections to internal IPs are blocked
  • Identity headers cannot be spoofed by client
  • Invalid certificates are rejected
  • Connection failures are handled gracefully

Production Readiness:

  • Configuration is loaded from file
  • Logs are structured and informative
  • Reconnection logic handles gateway restarts
  • Performance meets latency and throughput targets
  • Memory usage is bounded under load

Conclusion

This project brings together networking fundamentals, cryptography, and Zero Trust principles to build a modern replacement for VPNs. By building your own ZTNA App Tunnel, you understand:

  • Why network-level access (VPN) is dangerous
  • How application-level access (ZTNA) limits blast radius
  • The details of SOCKS5 protocol for protocol-agnostic tunneling
  • The critical importance of DNS leak prevention
  • How mTLS provides bidirectional authentication
  • Why split tunneling is the Zero Trust default

The skills from this project apply directly to:

  • Enterprise security architecture
  • Service mesh implementations
  • Modern access control design
  • Security engineering interviews at ZTNA companies

After completing this project, you’ll understand how products like Cloudflare Access, Zscaler ZPA, and Tailscale work under the hood.


This guide was expanded from ZERO_TRUST_ARCHITECTURE_DEEP_DIVE.md. For the complete learning path, see the project index.