mirror of
https://github.com/cloudflare/cloudflared.git
synced 2026-10-12 02:12:14 +00:00
chore: Avoid logging request body
Raw response bodies were being logged by quick tunnel provisioner. This avoids logging the raw body, which could contain sensitive data, while preserving structured Worker errors.
This commit is contained in:
@@ -82,26 +82,14 @@ func RunQuickTunnel(sc *subcommandContext) error {
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
// This will read the entire response into memory so we can print it in case of error
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to read quick-tunnel response")
|
||||
}
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
var data QuickTunnelResponse
|
||||
if err := json.Unmarshal(respBody, &data); err == nil && len(data.Errors) > 0 {
|
||||
return fmt.Errorf("quick tunnel provisioning failed with status %d: %s", resp.StatusCode, formatQuickTunnelErrors(data.Errors))
|
||||
}
|
||||
return fmt.Errorf("quick tunnel provisioning failed with status %d: %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
var data QuickTunnelResponse
|
||||
if err := json.Unmarshal(respBody, &data); err != nil {
|
||||
respString := string(respBody)
|
||||
fields := map[string]interface{}{"status_code": resp.Status}
|
||||
sc.log.Err(err).Fields(fields).Msgf("Error unmarshaling QuickTunnel response: %s", respString)
|
||||
return errors.Wrap(err, "failed to unmarshal quick Tunnel")
|
||||
data, err := decodeQuickTunnelProvisioningResponse(resp.StatusCode, respBody)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(TUN-10791): Add CLI-level coverage that provisioning errors are logged to users.
|
||||
@@ -179,6 +167,21 @@ func RunQuickTunnel(sc *subcommandContext) error {
|
||||
)
|
||||
}
|
||||
|
||||
func decodeQuickTunnelProvisioningResponse(statusCode int, response []byte) (QuickTunnelResponse, error) {
|
||||
var data QuickTunnelResponse
|
||||
if statusCode < http.StatusOK || statusCode >= http.StatusMultipleChoices {
|
||||
if err := json.Unmarshal(response, &data); err == nil && len(data.Errors) > 0 {
|
||||
return QuickTunnelResponse{}, fmt.Errorf("quick tunnel provisioning failed with status %d: %s", statusCode, formatQuickTunnelErrors(data.Errors))
|
||||
}
|
||||
return QuickTunnelResponse{}, fmt.Errorf("quick tunnel provisioning failed with status %d", statusCode)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(response, &data); err != nil {
|
||||
return QuickTunnelResponse{}, errors.Wrap(err, "failed to unmarshal quick Tunnel")
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
type QuickTunnelResponse struct {
|
||||
Success bool
|
||||
Result QuickTunnel
|
||||
|
||||
@@ -2,6 +2,7 @@ package tunnel
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -27,6 +28,20 @@ func TestBuildQuickTunnelRequestBody_ProtectedMode(t *testing.T) {
|
||||
assert.Equal(t, quickTunnelAuthModeOTP, result[quickTunnelAuthModeField])
|
||||
}
|
||||
|
||||
func TestDecodeQuickTunnelProvisioningResponseDoesNotExposeBody(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const credential = "sensitive-tunnel-credential"
|
||||
malformedSuccess := []byte(`{"success":true,"result":{"secret":"` + credential + `"}} trailing`)
|
||||
_, err := decodeQuickTunnelProvisioningResponse(http.StatusOK, malformedSuccess)
|
||||
require.Error(t, err)
|
||||
assert.NotContains(t, err.Error(), credential)
|
||||
|
||||
_, err = decodeQuickTunnelProvisioningResponse(http.StatusBadGateway, []byte(credential))
|
||||
require.Error(t, err)
|
||||
assert.NotContains(t, err.Error(), credential)
|
||||
}
|
||||
|
||||
func TestFormatQuickTunnelErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user