Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions internal/gatewayapi/securitypolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2013,6 +2013,11 @@ func (t *Translator) buildBasicAuth(
usersSecret.Namespace, usersSecret.Name)
}

// Normalize CRLF to LF to handle .htpasswd files generated on Windows.
// Without this, the \r character gets included in the hash string, causing
// Envoy to reject the xDS config with "invalid SHA hash length" errors.
usersSecretBytes = []byte(strings.ReplaceAll(string(usersSecretBytes), "\r\n", "\n"))
Comment on lines +2016 to +2019
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

This normalization only replaces CRLF (\r\n). If the secret contains stray \r characters (e.g., mixed line endings or a final line ending with \r only), they will still be passed through to Envoy and can trigger the same rejection. Consider also stripping standalone \r (e.g., replace all \r with empty) or normalizing more generally.

Suggested change
// Normalize CRLF to LF to handle .htpasswd files generated on Windows.
// Without this, the \r character gets included in the hash string, causing
// Envoy to reject the xDS config with "invalid SHA hash length" errors.
usersSecretBytes = []byte(strings.ReplaceAll(string(usersSecretBytes), "\r\n", "\n"))
// Strip carriage returns to handle .htpasswd files generated on Windows.
// Without this, the \r character gets included in the hash string, causing
// Envoy to reject the xDS config with "invalid SHA hash length" errors.
usersSecretBytes = []byte(strings.ReplaceAll(string(usersSecretBytes), "\r", ""))

Copilot uses AI. Check for mistakes.

Comment on lines +2016 to +2020
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

usersSecretBytes normalization currently converts []byte -> string -> []byte, which always makes at least two full copies even when there are no CRLFs. Consider using bytes.ReplaceAll (or a bytes.Contains guard) to avoid unconditional allocations/copies for large secrets.

Copilot uses AI. Check for mistakes.
// Validate the htpasswd format
if err := validateHtpasswdFormat(usersSecretBytes); err != nil {
return nil, err
Expand Down
5 changes: 5 additions & 0 deletions internal/gatewayapi/securitypolicy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,11 @@ func Test_validateHtpasswdFormat(t *testing.T) {
htpasswd: "user1:{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=\nuser2:$apr1$hashed_user2_password",
wantError: true,
},
{
name: "valid htpasswd with CRLF line endings (Windows-style)",
htpasswd: "user1:{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=\r\nuser2:{SHA}qUqP5cyxm6YcTAhz05Hph5gvu9M=\r\n",
wantError: false,
},
Comment on lines +722 to +726
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

The new CRLF test case doesn’t actually validate the PR’s behavior change. validateHtpasswdFormat already calls strings.TrimSpace(line), which trims a trailing \r, so this test would pass even without the CRLF normalization in buildBasicAuth. Consider adding a unit test that exercises buildBasicAuth (or the IR/xDS generation) and asserts the emitted Users data contains no \r characters.

Copilot uses AI. Check for mistakes.
}

for _, tt := range tests {
Expand Down
Loading