Repository: syncthing/syncthing (language: go)

Issue:
traefik no longer url escape X-Forwarded-Tls-Client-Cert header
stdiscosrv has the ability to handle certificates passed by traefik proxy server in `X-Forwarded-Tls-Client-Cert` header.

Previously, traefik url encodes the content but it stopped doing so since version v2.9.4.
This becomes troublesome for setups where stdiscosrv sits behind newer traefik versions,
as now stdiscosrv tries to url [decode](https://github.com/syncthing/syncthing/blob/main/cmd/stdiscosrv/apisrv.go#L356) the header content when it is not encoded.

The symptom can be very confusing, as if some client's certificate's pem text happens to not contain `+`, etc, then it works perfectly, but other clients will always fail announcement with 403 error.

More details can be found at https://github.com/traefik/traefik/issues/9669.

I'm not sure what should be the best strategy here to support both old and new traefik versions... but wanted to at least raise attention.

stdiscosrv version: 1.24.0



Discussion / hints:

Fix patch:
diff --git a/cmd/stdiscosrv/apisrv.go b/cmd/stdiscosrv/apisrv.go
index 0f39f5d1fd9..87266870a8e 100644
--- a/cmd/stdiscosrv/apisrv.go
+++ b/cmd/stdiscosrv/apisrv.go
@@ -354,13 +354,14 @@ func certificateBytes(req *http.Request) ([]byte, error) {
 		bs = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: hdr})
 	} else if hdr := req.Header.Get("X-Forwarded-Tls-Client-Cert"); hdr != "" {
 		// Traefik 2 passtlsclientcert
-		// The certificate is in PEM format with url encoding but without newlines
-		// and start/end statements. We need to decode, reinstate the newlines every 64
+		//
+		// The certificate is in PEM format, maybe with URL encoding
+		// (depends on Traefik version) but without newlines and start/end
+		// statements. We need to decode, reinstate the newlines every 64
 		// character and add statements for the PEM decoder
-		hdr, err := url.QueryUnescape(hdr)
-		if err != nil {
-			// Decoding failed
-			return nil, err
+
+		if unesc, err := url.QueryUnescape(hdr); err == nil {
+			hdr = unesc
 		}
 
 		for i := 64; i < len(hdr); i += 65 {
@@ -368,7 +369,7 @@ func certificateBytes(req *http.Request) ([]byte, error) {
 		}
 
 		hdr = "-----BEGIN CERTIFICATE-----\n" + hdr
-		hdr = hdr + "\n-----END CERTIFICATE-----\n"
+		hdr += "\n-----END CERTIFICATE-----\n"
 		bs = []byte(hdr)
 	}
 


Test patch: