diff --git a/.gitignore b/.gitignore index a1af91a75c8..afd1f677571 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,7 @@ session_state_gen_test.go __pycache__/ tyk.test tyk-gateway.pid +*.go-e tyk_linux_* /dist/ diff --git a/cli/linter/schema.json b/cli/linter/schema.json index ce17033a275..2f77cd046eb 100644 --- a/cli/linter/schema.json +++ b/cli/linter/schema.json @@ -648,6 +648,15 @@ "type": "string", "enum": ["", "standard", "json"] }, + "access_logs": { + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean" + } + } + }, "enable_http_profiler": { "type": "boolean" }, diff --git a/config/config.go b/config/config.go index 115324b6d1e..6bb89c07839 100644 --- a/config/config.go +++ b/config/config.go @@ -246,6 +246,12 @@ type AnalyticsConfigConfig struct { SerializerType string `json:"serializer_type"` } +// AccessLogsConfig defines the type of transactions logs printed to stdout +type AccessLogsConfig struct { + // Enable the transaction logs. Default: false + Enabled bool `json:"enabled"` +} + type HealthCheckConfig struct { // Setting this value to `true` will enable the health-check endpoint on /Tyk/health. EnableHealthChecks bool `json:"enable_health_checks"` @@ -1003,6 +1009,10 @@ type Config struct { // If not set or left empty, it will default to `standard`. LogFormat string `json:"log_format"` + // You can configure the transaction logs to be turned on + // If not set or left empty, it will default to 'false' + AccessLogs AccessLogsConfig `json:"access_logs"` + // Section for configuring OpenTracing support // Deprecated: use OpenTelemetry instead. Tracer Tracer `json:"tracing"` diff --git a/gateway/handler_error.go b/gateway/handler_error.go index 0cda01903a8..cd240f12a07 100644 --- a/gateway/handler_error.go +++ b/gateway/handler_error.go @@ -311,6 +311,13 @@ func (e *ErrorHandler) HandleError(w http.ResponseWriter, r *http.Request, errMs log.WithError(err).Error("could not store analytic record") } } + + // Print the transaction logs for error situations if enabled. Success transaction + // logs will be handled by the "handler_success.go" + if e.Spec.GlobalConfig.AccessLogs.Enabled { + e.recordAccessLog(r, response, nil) + } + // Report in health check reportHealthValue(e.Spec, BlockedRequestLog, "-1") } diff --git a/gateway/handler_error_test.go b/gateway/handler_error_test.go index 72cd761966d..76fb1dccb78 100644 --- a/gateway/handler_error_test.go +++ b/gateway/handler_error_test.go @@ -9,6 +9,8 @@ import ( "strings" "testing" + "github.com/TykTechnologies/tyk/config" + "github.com/TykTechnologies/tyk/header" "github.com/TykTechnologies/tyk/test" ) @@ -122,3 +124,52 @@ func TestHandleDefaultErrorJSON(t *testing.T) { }) } + +func BenchmarkErrorLogTransaction(b *testing.B) { + b.Run("AccessLogs enabled with Hashkeys set to true", func(b *testing.B) { + conf := func(globalConf *config.Config) { + globalConf.HashKeys = true + globalConf.AccessLogs.Enabled = true + } + benchmarkErrorLogTransaction(b, conf) + + }) + b.Run("AccessLogs enabled with Hashkeys set to false", func(b *testing.B) { + conf := func(globalConf *config.Config) { + globalConf.HashKeys = false + globalConf.AccessLogs.Enabled = true + } + benchmarkErrorLogTransaction(b, conf) + }) + + b.Run("AccessLogs disabled with Hashkeys set to true", func(b *testing.B) { + conf := func(globalConf *config.Config) { + globalConf.HashKeys = true + globalConf.AccessLogs.Enabled = false + } + benchmarkErrorLogTransaction(b, conf) + }) + + b.Run("AccessLogs disabled with Hashkeys set to false", func(b *testing.B) { + conf := func(globalConf *config.Config) { + globalConf.HashKeys = false + globalConf.AccessLogs.Enabled = false + } + benchmarkErrorLogTransaction(b, conf) + }) +} + +func benchmarkErrorLogTransaction(b *testing.B, conf func(globalConf *config.Config)) { + b.ReportAllocs() + b.Helper() + b.ResetTimer() + + ts := StartTest(conf) + defer ts.Close() + + for i := 0; i < b.N; i++ { + ts.Run(b, test.TestCase{ + Code: http.StatusNotFound, + }) + } +} diff --git a/gateway/handler_success.go b/gateway/handler_success.go index 0f24aadc3f9..a2ec9fedb73 100644 --- a/gateway/handler_success.go +++ b/gateway/handler_success.go @@ -10,9 +10,8 @@ import ( "strings" "time" - graphqlinternal "github.com/TykTechnologies/tyk/internal/graphql" - "github.com/TykTechnologies/tyk/apidef" + graphqlinternal "github.com/TykTechnologies/tyk/internal/graphql" "github.com/TykTechnologies/tyk/internal/httputil" "github.com/TykTechnologies/tyk-pump/analytics" @@ -382,8 +381,15 @@ func (s *SuccessHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) *http Upstream: int64(DurationToMillisecond(resp.UpstreamLatency)), } s.RecordHit(r, latency, resp.Response.StatusCode, resp.Response, false) + + // Don't print a transaction log there is no "resp", that indicates an error. + // In error situations, transaction log is already printed by "handler_error.go" + if s.Spec.GlobalConfig.AccessLogs.Enabled { + s.recordAccessLog(r, resp.Response, &latency) + } } log.Debug("Done proxy") + return nil } diff --git a/gateway/handler_success_test.go b/gateway/handler_success_test.go index 9f1c4a1d9d3..95713917a0e 100644 --- a/gateway/handler_success_test.go +++ b/gateway/handler_success_test.go @@ -326,3 +326,58 @@ func TestAnalyticsIgnoreSubgraph(t *testing.T) { ) assert.NoError(t, err) } + +func BenchmarkSuccessLogTransaction(b *testing.B) { + b.Run("AccessLogs enabled with Hashkeys set to true", func(b *testing.B) { + conf := func(globalConf *config.Config) { + globalConf.HashKeys = true + globalConf.AccessLogs.Enabled = true + } + benchmarkSuccessLogTransaction(b, conf) + + }) + b.Run("AccessLogs enabled with Hashkeys set to false", func(b *testing.B) { + conf := func(globalConf *config.Config) { + globalConf.HashKeys = false + globalConf.AccessLogs.Enabled = true + } + benchmarkSuccessLogTransaction(b, conf) + }) + b.Run("AccessLogs disabled with Hashkeys set to true", func(b *testing.B) { + conf := func(globalConf *config.Config) { + globalConf.HashKeys = true + globalConf.AccessLogs.Enabled = false + } + benchmarkSuccessLogTransaction(b, conf) + }) + b.Run("AccessLogs disabled with Hashkeys set to false", func(b *testing.B) { + conf := func(globalConf *config.Config) { + globalConf.HashKeys = false + globalConf.AccessLogs.Enabled = false + } + benchmarkSuccessLogTransaction(b, conf) + }) +} + +func benchmarkSuccessLogTransaction(b *testing.B, conf func(globalConf *config.Config)) { + b.ReportAllocs() + b.Helper() + b.ResetTimer() + + ts := StartTest(conf) + defer ts.Close() + + API := BuildAPI(func(spec *APISpec) { + spec.Name = "test-api" + spec.APIID = "test-api-id" + spec.Proxy.ListenPath = "/" + })[0] + + ts.Gw.LoadAPI(API) + + for i := 0; i < b.N; i++ { + ts.Run(b, test.TestCase{ + Code: http.StatusOK, + }) + } +} diff --git a/gateway/middleware.go b/gateway/middleware.go index 91e84d960bc..7e57f45aaba 100644 --- a/gateway/middleware.go +++ b/gateway/middleware.go @@ -12,8 +12,11 @@ import ( "strconv" "time" + "github.com/TykTechnologies/tyk-pump/analytics" + "github.com/TykTechnologies/tyk/internal/cache" "github.com/TykTechnologies/tyk/internal/event" + "github.com/TykTechnologies/tyk/internal/httputil/accesslog" "github.com/TykTechnologies/tyk/internal/otel" "github.com/TykTechnologies/tyk/internal/policy" "github.com/TykTechnologies/tyk/rpc" @@ -358,6 +361,19 @@ func (t *BaseMiddleware) ApplyPolicies(session *user.SessionState) error { return store.Apply(session) } +// recordAccessLog is only used for Success/Error handler +func (t *BaseMiddleware) recordAccessLog(req *http.Request, resp *http.Response, latency *analytics.Latency) { + hashKeys := t.Gw.GetConfig().HashKeys + + accessLog := accesslog.NewRecord(t.Spec.APIID, t.Spec.OrgID) + accessLog.WithAuthToken(req, hashKeys, t.Gw.obfuscateKey) + accessLog.WithLatency(latency) + accessLog.WithRequest(req) + accessLog.WithResponse(resp) + + t.Logger().WithFields(accessLog.Fields()).Info() +} + func copyAllowedURLs(input []user.AccessSpec) []user.AccessSpec { if input == nil { return nil diff --git a/internal/crypto/hash.go b/internal/crypto/hash.go new file mode 100644 index 00000000000..9b13905eea7 --- /dev/null +++ b/internal/crypto/hash.go @@ -0,0 +1,53 @@ +package crypto + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "hash" + + "github.com/TykTechnologies/murmur3" +) + +const ( + HashSha256 = "sha256" + HashMurmur32 = "murmur32" + HashMurmur64 = "murmur64" + HashMurmur128 = "murmur128" +) + +func hashFunction(algorithm string) (hash.Hash, error) { + switch algorithm { + case HashSha256: + return sha256.New(), nil + case HashMurmur64: + return murmur3.New64(), nil + case HashMurmur128: + return murmur3.New128(), nil + case "", HashMurmur32: + return murmur3.New32(), nil + default: + return murmur3.New32(), fmt.Errorf("Unknown key hash function: %s. Falling back to murmur32.", algorithm) + } +} + +func HashStr(in string, withAlg ...string) string { + var algo string + if len(withAlg) > 0 && withAlg[0] != "" { + algo = withAlg[0] + } else { + algo = TokenHashAlgo(in) + } + + h, _ := hashFunction(algo) + h.Write([]byte(in)) + return hex.EncodeToString(h.Sum(nil)) +} + +func HashKey(in string, hashKey bool) string { + if !hashKey { + // Not hashing? Return the raw key + return in + } + return HashStr(in) +} diff --git a/internal/crypto/token.go b/internal/crypto/token.go new file mode 100644 index 00000000000..47edbe8330e --- /dev/null +++ b/internal/crypto/token.go @@ -0,0 +1,83 @@ +package crypto + +import ( + "encoding/base64" + "encoding/hex" + "fmt" + "strings" + + "github.com/buger/jsonparser" + + "github.com/TykTechnologies/tyk/internal/uuid" +) + +// `{"` in base64 +const B64JSONPrefix = "ey" + +const DefaultHashAlgorithm = "murmur64" + +const MongoBsonIdLength = 24 + +// GenerateToken generates a token. +// If hashing algorithm is empty, it uses legacy key generation. +func GenerateToken(orgID, keyID, hashAlgorithm string) (string, error) { + if keyID == "" { + keyID = uuid.NewHex() + } + + if hashAlgorithm != "" { + _, err := hashFunction(hashAlgorithm) + if err != nil { + hashAlgorithm = DefaultHashAlgorithm + } + + jsonToken := fmt.Sprintf(`{"org":"%s","id":"%s","h":"%s"}`, orgID, keyID, hashAlgorithm) + return base64.StdEncoding.EncodeToString([]byte(jsonToken)), err + } + + // Legacy keys + return orgID + keyID, nil +} + +func TokenHashAlgo(token string) string { + // Legacy tokens not b64 and not JSON records + if strings.HasPrefix(token, B64JSONPrefix) { + if jsonToken, err := base64.StdEncoding.DecodeString(token); err == nil { + hashAlgo, _ := jsonparser.GetString(jsonToken, "h") + return hashAlgo + } + } + + return "" +} + +func TokenID(token string) (id string, err error) { + jsonToken, err := base64.StdEncoding.DecodeString(token) + if err != nil { + return "", err + } + + return jsonparser.GetString(jsonToken, "id") +} + +func TokenOrg(token string) string { + if strings.HasPrefix(token, B64JSONPrefix) { + if jsonToken, err := base64.StdEncoding.DecodeString(token); err == nil { + // Checking error in case if it is a legacy tooken which just by accided has the same b64JSON prefix + if org, err := jsonparser.GetString(jsonToken, "org"); err == nil { + return org + } + } + } + + // 24 is mongo bson id length + if len(token) > MongoBsonIdLength { + newToken := token[:MongoBsonIdLength] + _, err := hex.DecodeString(newToken) + if err == nil { + return newToken + } + } + + return "" +} diff --git a/internal/httputil/Taskfile.yml b/internal/httputil/Taskfile.yml index 8434634a752..1e3bfeaf620 100644 --- a/internal/httputil/Taskfile.yml +++ b/internal/httputil/Taskfile.yml @@ -3,13 +3,14 @@ version: "3" vars: testArgs: -v + coverpkg: ./...,github.com/TykTechnologies/tyk/internal/httputil/... tasks: test: desc: "Run tests (requires redis)" cmds: - task: fmt - - go test {{.testArgs}} -count=1 -cover -coverprofile=rate.cov -coverpkg=./... ./... + - go test {{.testArgs}} -count=1 -cover -coverprofile=rate.cov {{.coverpkg}} ./... bench: desc: "Run benchmarks" diff --git a/internal/httputil/accesslog/access_log.go b/internal/httputil/accesslog/access_log.go new file mode 100644 index 00000000000..c4daaeffef4 --- /dev/null +++ b/internal/httputil/accesslog/access_log.go @@ -0,0 +1,89 @@ +package accesslog + +import ( + "net/http" + "net/url" + + "github.com/sirupsen/logrus" + + "github.com/TykTechnologies/tyk-pump/analytics" + "github.com/TykTechnologies/tyk/ctx" + "github.com/TykTechnologies/tyk/internal/crypto" + "github.com/TykTechnologies/tyk/request" +) + +// Record is a representation of a transaction log in the Gateway. +type Record struct { + fields logrus.Fields +} + +// NewRecord returns an Record object. +func NewRecord(apiID string, orgId string) *Record { + fields := logrus.Fields{ + "APIID": apiID, + "OrgID": orgId, + "prefix": "access-log", + } + return &Record{ + fields: fields, + } +} + +// WithLatency sets the latency of the Record. +func (a *Record) WithLatency(latency *analytics.Latency) *Record { + if latency != nil { + a.fields["TotalLatency"] = latency.Total + a.fields["UpstreamLatency"] = latency.Upstream + } + return a +} + +// WithAuthToken sets the access token from the request under APIKey. +// The access token is obfuscated, or hashed depending on passed arguments. +func (a *Record) WithAuthToken(req *http.Request, hashKeys bool, obfuscate func(string) string) *Record { + if req != nil { + token := ctx.GetAuthToken(req) + if !hashKeys { + a.fields["APIKey"] = obfuscate(token) + } else { + a.fields["APIKey"] = crypto.HashKey(token, hashKeys) + } + } + return a +} + +// WithRequest sets the request of the Record. +func (a *Record) WithRequest(req *http.Request) *Record { + if req != nil { + upstreamAddress := &url.URL{ + Scheme: req.URL.Scheme, + Host: req.URL.Host, + Path: req.URL.Path, + RawQuery: req.URL.RawQuery, + } + a.fields["ClientIP"] = request.RealIP(req) + a.fields["ClientRemoteAddr"] = req.RemoteAddr + a.fields["Host"] = req.Host + a.fields["Method"] = req.Method + a.fields["Proto"] = req.Proto + a.fields["RequestURI"] = req.RequestURI + a.fields["UpstreamAddress"] = upstreamAddress.String() + a.fields["UpstreamPath"] = req.URL.Path + a.fields["UpstreamURI"] = req.URL.RequestURI() + a.fields["UserAgent"] = req.UserAgent() + } + return a +} + +// WithResponse sets the request of the Record. +func (a *Record) WithResponse(resp *http.Response) *Record { + if resp != nil { + a.fields["StatusCode"] = resp.StatusCode + } + return a +} + +// Fields returns a logrus.Fields intended for logging. +func (a *Record) Fields() logrus.Fields { + return a.fields +} diff --git a/internal/httputil/accesslog/access_log_test.go b/internal/httputil/accesslog/access_log_test.go new file mode 100644 index 00000000000..45d261175f4 --- /dev/null +++ b/internal/httputil/accesslog/access_log_test.go @@ -0,0 +1,99 @@ +package accesslog + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/TykTechnologies/tyk-pump/analytics" + "github.com/TykTechnologies/tyk/request" +) + +func TestNewRecord(t *testing.T) { + apiID := "api_id" + orgID := "org_id" + + record := NewRecord(apiID, orgID).Fields() + + assert.Equal(t, apiID, record["APIID"]) + assert.Equal(t, orgID, record["OrgID"]) + assert.NotNil(t, record["APIID"]) + assert.NotNil(t, record["OrgID"]) +} + +func TestNewRecordWithLatency(t *testing.T) { + latency := &analytics.Latency{ + Total: 99, + Upstream: 101, + } + + record := NewRecord("api_id", "org_id").WithLatency(latency).Fields() + + assert.Equal(t, latency.Total, record["TotalLatency"]) + assert.Equal(t, latency.Upstream, record["UpstreamLatency"]) + assert.NotNil(t, record["TotalLatency"]) + assert.NotNil(t, record["UpstreamLatency"]) +} + +func TestNewRecordWithRequest(t *testing.T) { + req, _ := http.NewRequest(http.MethodGet, "/", nil) + + record := NewRecord("api_id", "org_id").WithRequest(req).Fields() + + assert.Equal(t, request.RealIP(req), record["ClientIP"]) + assert.Equal(t, req.RemoteAddr, record["ClientRemoteAddr"]) + assert.Equal(t, req.Host, record["Host"]) + assert.Equal(t, req.Method, record["Method"]) + assert.Equal(t, req.Proto, record["Proto"]) + assert.Equal(t, req.RequestURI, record["RequestURI"]) + assert.Equal(t, req.URL.Path, record["UpstreamAddress"]) + assert.Equal(t, req.URL.Path, record["UpstreamPath"]) + assert.Equal(t, req.URL.RequestURI(), record["UpstreamURI"]) + assert.Equal(t, req.UserAgent(), record["UserAgent"]) +} + +func TestNewRecordWithResponse(t *testing.T) { + resp := &http.Response{ + StatusCode: http.StatusOK, + } + + record := NewRecord("api_id", "org_id").WithResponse(resp).Fields() + + assert.Equal(t, resp.StatusCode, record["StatusCode"]) + assert.NotNil(t, record["StatusCode"]) +} + +func TestNewRecordField(t *testing.T) { + latency := &analytics.Latency{ + Total: 99, + Upstream: 101, + } + + req, _ := http.NewRequest(http.MethodGet, "http://example.com/path?userid=1", nil) + req.RemoteAddr = "0.0.0.0" + req.Header.Set("User-Agent", "user-agent") + + resp := &http.Response{ + StatusCode: http.StatusOK, + } + + record := NewRecord("api_id", "org_id").WithLatency(latency).WithRequest(req).WithResponse(resp).Fields() + + assert.Equal(t, "api_id", record["APIID"]) + assert.Equal(t, "org_id", record["OrgID"]) + assert.Equal(t, "access-log", record["prefix"]) + assert.Equal(t, int64(99), record["TotalLatency"]) + assert.Equal(t, int64(101), record["UpstreamLatency"]) + assert.Equal(t, request.RealIP(req), record["ClientIP"]) + assert.Equal(t, "0.0.0.0", record["ClientRemoteAddr"]) + assert.Equal(t, "example.com", record["Host"]) + assert.Equal(t, http.MethodGet, record["Method"]) + assert.Equal(t, "HTTP/1.1", record["Proto"]) + assert.Equal(t, "", record["RequestURI"]) + assert.Equal(t, "http://example.com/path?userid=1", record["UpstreamAddress"]) + assert.Equal(t, "/path", record["UpstreamPath"]) + assert.Equal(t, "/path?userid=1", record["UpstreamURI"]) + assert.Equal(t, "user-agent", record["UserAgent"]) + assert.Equal(t, http.StatusOK, record["StatusCode"]) +} diff --git a/storage/alias.go b/storage/alias.go new file mode 100644 index 00000000000..539df2c0a04 --- /dev/null +++ b/storage/alias.go @@ -0,0 +1,24 @@ +package storage + +import ( + "github.com/TykTechnologies/tyk/internal/crypto" +) + +const ( + HashSha256 = crypto.HashSha256 + HashMurmur32 = crypto.HashMurmur32 + HashMurmur64 = crypto.HashMurmur64 + HashMurmur128 = crypto.HashMurmur128 +) + +var ( + HashStr = crypto.HashStr + HashKey = crypto.HashKey +) + +var ( + GenerateToken = crypto.GenerateToken + TokenHashAlgo = crypto.TokenHashAlgo + TokenID = crypto.TokenID + TokenOrg = crypto.TokenOrg +) diff --git a/storage/storage.go b/storage/storage.go index 2c74e54e8a4..f636e8f7138 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -1,20 +1,9 @@ package storage import ( - "crypto/sha256" - "encoding/base64" - "encoding/hex" "errors" - "fmt" - "hash" - "strings" - "github.com/buger/jsonparser" - - "github.com/TykTechnologies/murmur3" logger "github.com/TykTechnologies/tyk/log" - - "github.com/TykTechnologies/tyk/internal/uuid" ) var log = logger.Get() @@ -24,8 +13,6 @@ var ErrKeyNotFound = errors.New("key not found") var ErrMDCBConnectionLost = errors.New("mdcb connection is lost") -const MongoBsonIdLength = 24 - // Handler is a standard interface to a storage backend, used by // AuthorisationManager to read and write key values to the backend type Handler interface { @@ -71,115 +58,3 @@ type AnalyticsHandler interface { SetExp(string, int64) error // Set key expiration GetExp(string) (int64, error) // Returns expiry of a key } - -const defaultHashAlgorithm = "murmur64" - -// If hashing algorithm is empty, use legacy key generation -func GenerateToken(orgID, keyID, hashAlgorithm string) (string, error) { - if keyID == "" { - keyID = uuid.NewHex() - } - - if hashAlgorithm != "" { - _, err := hashFunction(hashAlgorithm) - if err != nil { - hashAlgorithm = defaultHashAlgorithm - } - - jsonToken := fmt.Sprintf(`{"org":"%s","id":"%s","h":"%s"}`, orgID, keyID, hashAlgorithm) - return base64.StdEncoding.EncodeToString([]byte(jsonToken)), err - } - - // Legacy keys - return orgID + keyID, nil -} - -// `{"` in base64 -const B64JSONPrefix = "ey" - -func TokenHashAlgo(token string) string { - // Legacy tokens not b64 and not JSON records - if strings.HasPrefix(token, B64JSONPrefix) { - if jsonToken, err := base64.StdEncoding.DecodeString(token); err == nil { - hashAlgo, _ := jsonparser.GetString(jsonToken, "h") - return hashAlgo - } - } - - return "" -} - -// TODO: add checks -func TokenID(token string) (id string, err error) { - jsonToken, err := base64.StdEncoding.DecodeString(token) - if err != nil { - return "", err - } - - return jsonparser.GetString(jsonToken, "id") -} - -func TokenOrg(token string) string { - if strings.HasPrefix(token, B64JSONPrefix) { - if jsonToken, err := base64.StdEncoding.DecodeString(token); err == nil { - // Checking error in case if it is a legacy tooken which just by accided has the same b64JSON prefix - if org, err := jsonparser.GetString(jsonToken, "org"); err == nil { - return org - } - } - } - - // 24 is mongo bson id length - if len(token) > MongoBsonIdLength { - newToken := token[:MongoBsonIdLength] - _, err := hex.DecodeString(newToken) - if err == nil { - return newToken - } - } - - return "" -} - -var ( - HashSha256 = "sha256" - HashMurmur32 = "murmur32" - HashMurmur64 = "murmur64" - HashMurmur128 = "murmur128" -) - -func hashFunction(algorithm string) (hash.Hash, error) { - switch algorithm { - case HashSha256: - return sha256.New(), nil - case HashMurmur64: - return murmur3.New64(), nil - case HashMurmur128: - return murmur3.New128(), nil - case "", HashMurmur32: - return murmur3.New32(), nil - default: - return murmur3.New32(), fmt.Errorf("Unknown key hash function: %s. Falling back to murmur32.", algorithm) - } -} - -func HashStr(in string, withAlg ...string) string { - var algo string - if len(withAlg) > 0 && withAlg[0] != "" { - algo = withAlg[0] - } else { - algo = TokenHashAlgo(in) - } - - h, _ := hashFunction(algo) - h.Write([]byte(in)) - return hex.EncodeToString(h.Sum(nil)) -} - -func HashKey(in string, hashKey bool) string { - if !hashKey { - // Not hashing? Return the raw key - return in - } - return HashStr(in) -}