Skip to content

feat: Implement in-protocol TLS handling in proxy instead of parsers#3743

Open
Sarthak160 wants to merge 10 commits intomainfrom
tls-refactor
Open

feat: Implement in-protocol TLS handling in proxy instead of parsers#3743
Sarthak160 wants to merge 10 commits intomainfrom
tls-refactor

Conversation

@Sarthak160
Copy link
Member

@Sarthak160 Sarthak160 commented Feb 8, 2026

This pull request introduces a significant refactor to how in-protocol TLS (SSL) negotiation is handled for database protocols (PostgreSQL and MySQL) in the proxy. The main improvement is centralizing the SSL/TLS upgrade logic at the proxy layer, so protocol parsers always receive plaintext byte streams, simplifying their implementation and improving maintainability.

Centralization of in-protocol TLS handling:

  • Added a new pkg/agent/proxy/tls/proto_tls.go file with helpers for detecting and handling PostgreSQL and MySQL SSL negotiation at the proxy level, including functions like IsPostgresSSLRequest, HandlePostgresSSL, UpgradeMySQLConnections, and more. This ensures that both client and server connections are upgraded to TLS before passing them to protocol parsers.
  • Updated the main proxy connection handler (pkg/agent/proxy/proxy.go) to detect PostgreSQL SSLRequest packets and perform the SSL negotiation and connection upgrades at the proxy layer. After negotiation, the parser receives the next protocol packet as plaintext.

Abstraction and interface improvements:

  • Introduced the StreamConn struct in pkg/agent/proxy/integrations/integrations.go, which wraps a network connection with a custom reader. This allows the proxy to prepend already-read bytes (such as after SSL negotiation) so parsers always see the full protocol stream. The Integrations interface and its documentation were updated to clarify that parsers receive StreamConn objects with plaintext streams.

MySQL protocol handling enhancements:

  • Refactored MySQL SSL negotiation in the recorder to use the new centralized TLS upgrade helper (UpgradeMySQLConnections). This removes the previous in-parser TLS logic and ensures consistent handling with the proxy-level approach.
  • Clarified with comments in the MySQL recorder and replayer how SSL negotiation is handled differently in record and replay modes, and that the centralized TLS helpers are now used. [1] [2]

These changes make the proxy more robust, easier to maintain, and ensure all protocol parsers work with plaintext streams regardless of whether the underlying protocol uses in-band SSL/TLS negotiation.

Describe the changes that are made

Links & References

Closes: #[issue number that will be closed through this PR]

  • NA (if very small change like typo, linting, etc.)

🔗 Related PRs

  • NA

🐞 Related Issues

  • NA

📄 Related Documents

  • NA

What type of PR is this? (check all applicable)

  • 📦 Chore
  • 🍕 Feature
  • 🐞 Bug Fix
  • 📝 Documentation Update
  • 🎨 Style
  • 🧑‍💻 Code Refactor
  • 🔥 Performance Improvements
  • ✅ Test
  • 🔁 CI
  • ⏩ Revert

Added e2e test pipeline?

  • 👍 yes
  • 🙅 no, because they aren't needed
  • 🙋 no, because I need help

Added comments for hard-to-understand areas?

  • 👍 yes
  • 🙅 no, because the code is self-explanatory

Added to documentation?

  • 📜 README.md
  • 📓 Wiki
  • 🙅 no documentation needed

Are there any sample code or steps to test the changes?

  • 👍 yes, mentioned below
  • 🙅 no, because it is not needed

Self Review done?

  • ✅ yes
  • ❌ no, because I need help

Any relevant screenshots, recordings or logs?

  • NA

🧠 Semantics for PR Title & Branch Name

Please ensure your PR title and branch name follow the Keploy semantics:

📌 PR Semantics Guide
📌 Branch Semantics Guide

Examples:

  • PR Title: fix: patch MongoDB document update bug
  • Branch Name: feat/#1-login-flow (You may skip mentioning the issue number in the branch name if the change is small and the PR description clearly explains it.)

Additional checklist:

Copilot AI review requested due to automatic review settings February 8, 2026 20:13
@Sarthak160 Sarthak160 changed the title feat: Implement in-protocol TLS handling for PostgreSQL and MySQL in … feat: Implement in-protocol TLS handling in proxy instead of parsers Feb 8, 2026

// Upgrade server connection (act as TLS client to the real server)
tlsConfig := &tls.Config{
InsecureSkipVerify: true,

Check failure

Code scanning / CodeQL

Disabled TLS certificate check High

InsecureSkipVerify should not be used in production code.

Copilot Autofix

AI 7 days ago

In general, the fix is to stop disabling TLS verification and instead configure tls.Config so that the client validates the server certificate and hostname. If custom CAs or self‑signed certs are needed, they should be added to a RootCAs pool instead of using InsecureSkipVerify.

In this specific file, there are two tls.Config literals that set InsecureSkipVerify: true when upgrading the PostgreSQL and MySQL server connections. We should remove InsecureSkipVerify: true from both while preserving ServerName, which allows the default verification logic to validate the certificate’s DNS name. No other behavior needs to change: the connections should still be upgraded to TLS using tls.Client and Handshake() exactly as before. Because we are simply changing TLS options, and crypto/tls is already imported, no new imports or helper methods are required. The edits are localized to the tlsConfig initializations around lines 103–107 and 155–159 in pkg/agent/proxy/tls/proto_tls.go.

Suggested changeset 1
pkg/agent/proxy/tls/proto_tls.go

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/pkg/agent/proxy/tls/proto_tls.go b/pkg/agent/proxy/tls/proto_tls.go
--- a/pkg/agent/proxy/tls/proto_tls.go
+++ b/pkg/agent/proxy/tls/proto_tls.go
@@ -102,8 +102,7 @@
 
 	// Upgrade server connection (act as TLS client to the real server)
 	tlsConfig := &tls.Config{
-		InsecureSkipVerify: true,
-		ServerName:         dstURL,
+		ServerName: dstURL,
 	}
 	tlsServerConn := tls.Client(serverConn, tlsConfig)
 	if err := tlsServerConn.Handshake(); err != nil {
@@ -154,8 +153,7 @@
 
 	// Upgrade server connection (act as TLS client to the real server)
 	tlsConfig := &tls.Config{
-		InsecureSkipVerify: true,
-		ServerName:         serverName,
+		ServerName: serverName,
 	}
 	tlsServerConn := tls.Client(serverConn, tlsConfig)
 	if err := tlsServerConn.Handshake(); err != nil {
EOF
@@ -102,8 +102,7 @@

// Upgrade server connection (act as TLS client to the real server)
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
ServerName: dstURL,
ServerName: dstURL,
}
tlsServerConn := tls.Client(serverConn, tlsConfig)
if err := tlsServerConn.Handshake(); err != nil {
@@ -154,8 +153,7 @@

// Upgrade server connection (act as TLS client to the real server)
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
ServerName: serverName,
ServerName: serverName,
}
tlsServerConn := tls.Client(serverConn, tlsConfig)
if err := tlsServerConn.Handshake(); err != nil {
Copilot is powered by AI and may make mistakes. Always verify output.

// Upgrade server connection (act as TLS client to the real server)
tlsConfig := &tls.Config{
InsecureSkipVerify: true,

Check failure

Code scanning / CodeQL

Disabled TLS certificate check High

InsecureSkipVerify should not be used in production code.

Copilot Autofix

AI 7 days ago

In general, the fix is to stop disabling certificate verification and instead configure the TLS client with proper trust roots and hostname verification. That means removing InsecureSkipVerify: true and letting Go’s default verification run, or—if non‑public CAs or self‑signed certs are used—supplying an appropriate RootCAs pool or a VerifyConnection callback that performs strict checks.

Within UpgradeMySQLServerToTLS in pkg/agent/proxy/tls/proto_tls.go, the minimal, behavior‑preserving but secure change is:

  • Remove InsecureSkipVerify: true from the tls.Config.
  • Keep ServerName: serverName so hostname verification will be performed using that value.
  • Rely on the process’ default root CAs (tls.Config{} with no RootCAs set uses x509.SystemCertPool() by default) so that standard CA‑signed upstream certificates work. If the deployment uses private CAs, that configuration must occur elsewhere (we cannot invent it here).

Concretely, we only need to change the tlsConfig initialization block to stop setting InsecureSkipVerify, leaving everything else intact.

Suggested changeset 1
pkg/agent/proxy/tls/proto_tls.go

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/pkg/agent/proxy/tls/proto_tls.go b/pkg/agent/proxy/tls/proto_tls.go
--- a/pkg/agent/proxy/tls/proto_tls.go
+++ b/pkg/agent/proxy/tls/proto_tls.go
@@ -154,8 +154,7 @@
 
 	// Upgrade server connection (act as TLS client to the real server)
 	tlsConfig := &tls.Config{
-		InsecureSkipVerify: true,
-		ServerName:         serverName,
+		ServerName: serverName,
 	}
 	tlsServerConn := tls.Client(serverConn, tlsConfig)
 	if err := tlsServerConn.Handshake(); err != nil {
EOF
@@ -154,8 +154,7 @@

// Upgrade server connection (act as TLS client to the real server)
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
ServerName: serverName,
ServerName: serverName,
}
tlsServerConn := tls.Client(serverConn, tlsConfig)
if err := tlsServerConn.Handshake(); err != nil {
Copilot is powered by AI and may make mistakes. Always verify output.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds proxy-level handling for database protocols that negotiate TLS in-protocol (not via an immediate TLS ClientHello), with the goal of letting downstream parsers operate on plaintext streams while still supporting TLS to/from the real DB.

Changes:

  • Introduces PostgreSQL SSLRequest detection + proxy-side TLS upgrade helper(s) (and shared MySQL helpers) in pkg/agent/proxy/tls/.
  • Updates the main proxy connection flow to intercept PostgreSQL SSLRequest in record mode and perform the SSL/TLS upgrade before invoking parsers.
  • Refactors MySQL recorder TLS upgrade to use a centralized TLS upgrade helper; adds explanatory comments in MySQL replayer.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
pkg/agent/proxy/tls/proto_tls.go Adds protocol-specific SSL/TLS negotiation helpers for PostgreSQL/MySQL (in-protocol upgrades).
pkg/agent/proxy/proxy.go Detects PostgreSQL SSLRequest (record mode) and attempts proxy-level SSL/TLS upgrade before parsing.
pkg/agent/proxy/integrations/mysql/replayer/conn.go Clarifies replay-mode TLS handling behavior via comments.
pkg/agent/proxy/integrations/mysql/recorder/conn.go Switches MySQL TLS upgrade logic to centralized helper(s).
pkg/agent/proxy/integrations/integrations.go Adds StreamConn wrapper type and expands interface documentation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +623 to +627
// Dial the destination server first
dstConn, err = net.Dial("tcp", dstAddr)
if err != nil {
utils.LogError(logger, err, "failed to dial PostgreSQL server for SSL negotiation")
return err
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HandlePostgresSSL can return an already dialed and TLS-upgraded dstConn, but later in handleConnection the normal non-TLS path still re-dials dstConn because isTLS remains false. That will overwrite the negotiated connection and break Postgres-over-TLS. Consider setting a flag (or updating isTLS) to skip the later dial and reuse the dstConn returned here.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant