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
3 changes: 3 additions & 0 deletions cli/linter/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,9 @@
"enable_detailed_recording": {
"type": "boolean"
},
"allow_unsafe_detailed_logs": {
"type": "boolean"
},
"purge_interval": {
"type": "number"
},
Expand Down
4 changes: 4 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,10 @@ type AnalyticsConfigConfig struct {
// This setting can be overridden with an organization flag, enabed at an API level, or on individual Key level.
EnableDetailedRecording bool `json:"enable_detailed_recording"`

// AllowUnsafeDetailedLogs controls whether sensitive headers (Authorization) are obfuscated when detailed recording is enabled.
// Setting this to true restores the legacy behavior where raw requests are captured as-is, which may expose sensitive tokens in plain text.
AllowUnsafeDetailedLogs bool `json:"allow_unsafe_detailed_logs"`

// Tyk can store GeoIP information based on MaxMind DB’s to enable GeoIP tracking on inbound request analytics. Set this value to `true` and assign a DB using the `geo_ip_db_path` setting.
EnableGeoIP bool `json:"enable_geo_ip"`

Expand Down
95 changes: 95 additions & 0 deletions gateway/analytics_helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package gateway

import (
"bytes"
"encoding/base64"
"net/http"

"github.com/TykTechnologies/tyk/apidef"
"github.com/TykTechnologies/tyk/ctx"
"github.com/TykTechnologies/tyk/header"
"github.com/TykTechnologies/tyk/internal/httputil"
"github.com/TykTechnologies/tyk/user"
)

const obfuscationToken = "***"

func getRawRequest(r *http.Request, spec *APISpec) string {
var wireFormatReq bytes.Buffer

var originalHeaders http.Header
if !spec.GlobalConfig.AnalyticsConfig.AllowUnsafeDetailedLogs {
originalHeaders = obfuscateAuthorizationHeaders(r, spec)
defer func() {
r.Header = originalHeaders
}()
}

r.Write(&wireFormatReq)

Check failure on line 28 in gateway/analytics_helper.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `r.Write` is not checked (errcheck)

Check warning on line 28 in gateway/analytics_helper.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Error return value of `r.Write` is not checked

See more on https://sonarcloud.io/project/issues?id=TykTechnologies_tyk&issues=AZ-EsiVxF5cMOLvWFDv0&open=AZ-EsiVxF5cMOLvWFDv0&pullRequest=8500
rawRequest := base64.StdEncoding.EncodeToString(wireFormatReq.Bytes())

return rawRequest
}

func obfuscateAuthorizationHeaders(r *http.Request, spec *APISpec) http.Header {
original := r.Header.Clone()
authNames := make([]string, 0)

addAuthHeader := func(config apidef.AuthConfig) {
if config.DisableHeader {
return
}

name := config.AuthHeaderName
if name == "" {
name = header.Authorization
}
authNames = append(authNames, http.CanonicalHeaderKey(name))
}

addAuthHeader(spec.Auth)

for _, authConfig := range spec.AuthConfigs {
addAuthHeader(authConfig)
}

for _, authName := range authNames {
if _, ok := r.Header[authName]; ok {
r.Header.Set(authName, obfuscationToken)
}
}

return original
}

func recordDetail(r *http.Request, spec *APISpec) bool {
// when streaming in grpc, we do not record the request
if httputil.IsStreamingRequest(r) {
return false

Check warning on line 68 in gateway/analytics_helper.go

View check run for this annotation

probelabs / Visor: performance

performance Issue

The list of authorization headers to obfuscate is recalculated on every request when detailed logging is enabled. This computation involves creating a new slice, iterating over the API's authentication configuration map, and performing string manipulations for each header. Since this list is derived from the API definition, which is static between reloads, it can be pre-computed and cached to reduce per-request CPU and memory overhead.
Raw output
Pre-compute the list of canonicalized authorization header names when the `APISpec` is loaded and store it in a new field within the `APISpec` struct. The `obfuscateAuthorizationHeaders` function should then use this cached list, avoiding repeated computations and allocations in the request path. This will improve performance for APIs that have detailed request logging enabled.
}

return recordDetailUnsafe(r, spec)
}

func recordDetailUnsafe(r *http.Request, spec *APISpec) bool {
if spec.EnableDetailedRecording {
return true
}

if session := ctxGetSession(r); session != nil {
if session.EnableDetailedRecording || session.EnableDetailRecording { // nolint:staticcheck // Deprecated DetailRecording
return true
}
}

// decide based on org session.
if spec.GlobalConfig.EnforceOrgDataDetailLogging {
session, ok := r.Context().Value(ctx.OrgSessionContext).(*user.SessionState)
if ok && session != nil {
return session.EnableDetailedRecording || session.EnableDetailRecording // nolint:staticcheck // Deprecated DetailRecording
}
}

// no org session found, use global config
return spec.GraphQL.Enabled || spec.GlobalConfig.AnalyticsConfig.EnableDetailedRecording
}
Loading
Loading