Skip to content
Merged
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
59 changes: 51 additions & 8 deletions internal/extauthz/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ package extauthz

import (
"context"
"errors"
"fmt"
"net/http"
"strings"

"github.com/go-andiamo/splitter"
"github.com/openkcm/common-sdk/pkg/auth"
"github.com/openkcm/common-sdk/pkg/fingerprint"

Expand Down Expand Up @@ -273,15 +273,58 @@ func (srv *Server) extractSessionDetails(ctx context.Context, httpreq *envoyauth
// splitCertHeader splits the XFCC header on , in case there are multiple certificates.
// https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-forwarded-client-cert
func splitCertHeader(certHeader string) ([]string, error) {
// split on , preserving quoted values
spl, err := splitter.NewSplitter(',', splitter.DoubleQuotes)
if err != nil {
return nil, err
// Handle empty string case
if certHeader == "" {
return []string{}, nil
}

fields, err := spl.Split(certHeader)
if err != nil {
return nil, err
// Manual parsing to preserve quotes and handle XFCC format correctly
Comment thread
alienvspredator marked this conversation as resolved.
var fields []string
var current strings.Builder
inQuote := false
escaped := false

for i := range len(certHeader) {
ch := certHeader[i]

if inQuote && escaped {
// Previous character was a backslash, so write this character as-is
current.WriteByte(ch)
escaped = false
continue
}

switch ch {
case '\\':
if inQuote {
// Mark that the next character is escaped (only inside quotes per XFCC spec)
escaped = true
}
current.WriteByte(ch)
case '"':
// Toggle quote state
inQuote = !inQuote
current.WriteByte(ch)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
case ',':
if inQuote {
// Inside quotes, comma is part of the value
current.WriteByte(ch)
} else {
// Outside quotes, comma is a separator
fields = append(fields, current.String())
current.Reset()
}
default:
current.WriteByte(ch)
}
}

// Add the last field
fields = append(fields, current.String())

// Check for unclosed quotes
if inQuote {
return nil, errors.New("unclosed quote in header")
}

return fields, nil
Expand Down
10 changes: 9 additions & 1 deletion internal/extauthz/check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ func TestSplitCertHeader(t *testing.T) {
name: "zero values",
input: ``,
wantError: false,
want: []string{``},
want: []string{},
}, {
name: "one cert",
input: `A=b;C="d";E=f`,
Expand All @@ -419,6 +419,14 @@ func TestSplitCertHeader(t *testing.T) {
name: "two certs",
input: `A=b;C="d";E=f,1=2;3="4";5=6`,
want: []string{`A=b;C="d";E=f`, `1=2;3="4";5=6`},
}, {
name: "two certs with escaped quotes",
input: `A=b;C="A quote \"inside\" quotes";E=f,1=2;3="4";5=6`,
want: []string{`A=b;C="A quote \"inside\" quotes";E=f`, `1=2;3="4";5=6`},
}, {
name: "two certs with a single escaped quote",
input: `A=b;C="A single \" inside";E=f,1=2;3="4";5=6`,
want: []string{`A=b;C="A single \" inside";E=f`, `1=2;3="4";5=6`},
}, {
name: "quoted spaces",
input: `A=b;C="d,";E=f,1=2;3="4,";5=6`,
Expand Down
Loading