redis.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. // Unless explicitly stated otherwise all files in this repository are licensed
  2. // under the Apache License Version 2.0.
  3. // This product includes software developed at Datadog (https://www.datadoghq.com/).
  4. // Copyright 2016-present Datadog, Inc.
  5. package obfuscate
  6. import (
  7. "strings"
  8. )
  9. // redisTruncationMark is used as suffix by tracing libraries to indicate that a
  10. // command was truncated.
  11. const redisTruncationMark = "..."
  12. const maxRedisNbCommands = 3
  13. // Redis commands consisting in 2 words
  14. var redisCompoundCommandSet = map[string]bool{
  15. "CLIENT": true, "CLUSTER": true, "COMMAND": true, "CONFIG": true, "DEBUG": true, "SCRIPT": true}
  16. // QuantizeRedisString returns a quantized version of a Redis query.
  17. //
  18. // TODO(gbbr): Refactor this method to use the tokenizer and
  19. // remove "compactWhitespaces". This method is buggy when commands
  20. // contain quoted strings with newlines.
  21. func (*Obfuscator) QuantizeRedisString(query string) string {
  22. query = compactWhitespaces(query)
  23. var resource strings.Builder
  24. truncated := false
  25. nbCmds := 0
  26. for len(query) > 0 && nbCmds < maxRedisNbCommands {
  27. var rawLine string
  28. // Read the next command
  29. idx := strings.IndexByte(query, '\n')
  30. if idx == -1 {
  31. rawLine = query
  32. query = ""
  33. } else {
  34. rawLine = query[:idx]
  35. query = query[idx+1:]
  36. }
  37. line := strings.Trim(rawLine, " ")
  38. if len(line) == 0 {
  39. continue
  40. }
  41. // Parse arguments
  42. args := strings.SplitN(line, " ", 3)
  43. if strings.HasSuffix(args[0], redisTruncationMark) {
  44. truncated = true
  45. continue
  46. }
  47. command := strings.ToUpper(args[0])
  48. if redisCompoundCommandSet[command] && len(args) > 1 {
  49. if strings.HasSuffix(args[1], redisTruncationMark) {
  50. truncated = true
  51. continue
  52. }
  53. command += " " + strings.ToUpper(args[1])
  54. }
  55. // Write the command representation
  56. resource.WriteByte(' ')
  57. resource.WriteString(command)
  58. nbCmds++
  59. truncated = false
  60. }
  61. if nbCmds == maxRedisNbCommands || truncated {
  62. resource.WriteString(" ...")
  63. }
  64. return strings.Trim(resource.String(), " ")
  65. }
  66. // ObfuscateRedisString obfuscates the given Redis command.
  67. func (*Obfuscator) ObfuscateRedisString(rediscmd string) string {
  68. t := newRedisTokenizer([]byte(rediscmd))
  69. var (
  70. str strings.Builder
  71. cmd string
  72. args []string
  73. )
  74. for {
  75. tok, typ, done := t.scan()
  76. switch typ {
  77. case redisTokenCommand:
  78. // new command starting
  79. if cmd != "" {
  80. // a previous command was buffered, obfuscate it
  81. obfuscateRedisCmd(&str, cmd, args...)
  82. str.WriteByte('\n')
  83. }
  84. cmd = tok
  85. args = args[:0]
  86. case redisTokenArgument:
  87. args = append(args, tok)
  88. }
  89. if done {
  90. // last command
  91. obfuscateRedisCmd(&str, cmd, args...)
  92. break
  93. }
  94. }
  95. return str.String()
  96. }
  97. func obfuscateRedisCmd(out *strings.Builder, cmd string, args ...string) {
  98. out.WriteString(cmd)
  99. if len(args) == 0 {
  100. return
  101. }
  102. out.WriteByte(' ')
  103. switch strings.ToUpper(cmd) {
  104. case "AUTH":
  105. // Obfuscate everything after command
  106. // • AUTH password
  107. if len(args) > 0 {
  108. args[0] = "?"
  109. args = args[:1]
  110. }
  111. case "APPEND", "GETSET", "LPUSHX", "GEORADIUSBYMEMBER", "RPUSHX",
  112. "SET", "SETNX", "SISMEMBER", "ZRANK", "ZREVRANK", "ZSCORE":
  113. // Obfuscate 2nd argument:
  114. // • APPEND key value
  115. // • GETSET key value
  116. // • LPUSHX key value
  117. // • GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
  118. // • RPUSHX key value
  119. // • SET key value [expiration EX seconds|PX milliseconds] [NX|XX]
  120. // • SETNX key value
  121. // • SISMEMBER key member
  122. // • ZRANK key member
  123. // • ZREVRANK key member
  124. // • ZSCORE key member
  125. obfuscateRedisArgN(args, 1)
  126. case "HSET", "HSETNX", "LREM", "LSET", "SETBIT", "SETEX", "PSETEX",
  127. "SETRANGE", "ZINCRBY", "SMOVE", "RESTORE":
  128. // Obfuscate 3rd argument:
  129. // • HSET key field value
  130. // • HSETNX key field value
  131. // • LREM key count value
  132. // • LSET key index value
  133. // • SETBIT key offset value
  134. // • SETEX key seconds value
  135. // • PSETEX key milliseconds value
  136. // • SETRANGE key offset value
  137. // • ZINCRBY key increment member
  138. // • SMOVE source destination member
  139. // • RESTORE key ttl serialized-value [REPLACE]
  140. obfuscateRedisArgN(args, 2)
  141. case "LINSERT":
  142. // Obfuscate 4th argument:
  143. // • LINSERT key BEFORE|AFTER pivot value
  144. obfuscateRedisArgN(args, 3)
  145. case "GEOHASH", "GEOPOS", "GEODIST", "LPUSH", "RPUSH", "SREM",
  146. "ZREM", "SADD":
  147. // Obfuscate all arguments after the first one.
  148. // • GEOHASH key member [member ...]
  149. // • GEOPOS key member [member ...]
  150. // • GEODIST key member1 member2 [unit]
  151. // • LPUSH key value [value ...]
  152. // • RPUSH key value [value ...]
  153. // • SREM key member [member ...]
  154. // • ZREM key member [member ...]
  155. // • SADD key member [member ...]
  156. if len(args) > 1 {
  157. args[1] = "?"
  158. args = args[:2]
  159. }
  160. case "GEOADD":
  161. // Obfuscating every 3rd argument starting from first
  162. // • GEOADD key longitude latitude member [longitude latitude member ...]
  163. obfuscateRedisArgsStep(args, 1, 3)
  164. case "HMSET":
  165. // Every 2nd argument starting from first.
  166. // • HMSET key field value [field value ...]
  167. obfuscateRedisArgsStep(args, 1, 2)
  168. case "MSET", "MSETNX":
  169. // Every 2nd argument starting from command.
  170. // • MSET key value [key value ...]
  171. // • MSETNX key value [key value ...]
  172. obfuscateRedisArgsStep(args, 0, 2)
  173. case "CONFIG":
  174. // Obfuscate 2nd argument to SET sub-command.
  175. // • CONFIG SET parameter value
  176. if strings.ToUpper(args[0]) == "SET" {
  177. obfuscateRedisArgN(args, 2)
  178. }
  179. case "BITFIELD":
  180. // Obfuscate 3rd argument to SET sub-command:
  181. // • BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]
  182. var n int
  183. for i, arg := range args {
  184. if strings.ToUpper(arg) == "SET" {
  185. n = i
  186. }
  187. if n > 0 && i-n == 3 {
  188. args[i] = "?"
  189. break
  190. }
  191. }
  192. case "ZADD":
  193. // Obfuscate every 2nd argument after potential optional ones.
  194. // • ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
  195. var i int
  196. loop:
  197. for i = range args {
  198. if i == 0 {
  199. continue // key
  200. }
  201. switch args[i] {
  202. case "NX", "XX", "CH", "INCR":
  203. // continue
  204. default:
  205. break loop
  206. }
  207. }
  208. obfuscateRedisArgsStep(args, i, 2)
  209. default:
  210. // Obfuscate nothing.
  211. }
  212. out.WriteString(strings.Join(args, " "))
  213. }
  214. func obfuscateRedisArgN(args []string, n int) {
  215. if len(args) > n {
  216. args[n] = "?"
  217. }
  218. }
  219. func obfuscateRedisArgsStep(args []string, start, step int) {
  220. if start+step-1 >= len(args) {
  221. // can't reach target
  222. return
  223. }
  224. for i := start + step - 1; i < len(args); i += step {
  225. args[i] = "?"
  226. }
  227. }