appsec.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. package appsec
  2. import (
  3. "fmt"
  4. "os"
  5. "regexp"
  6. "github.com/antonmedv/expr"
  7. "github.com/antonmedv/expr/vm"
  8. "github.com/crowdsecurity/crowdsec/pkg/cwhub"
  9. "github.com/crowdsecurity/crowdsec/pkg/exprhelpers"
  10. "github.com/crowdsecurity/crowdsec/pkg/types"
  11. log "github.com/sirupsen/logrus"
  12. "gopkg.in/yaml.v2"
  13. )
  14. type Hook struct {
  15. Filter string `yaml:"filter"`
  16. FilterExpr *vm.Program `yaml:"-"`
  17. OnSuccess string `yaml:"on_success"`
  18. Apply []string `yaml:"apply"`
  19. ApplyExpr []*vm.Program `yaml:"-"`
  20. }
  21. const (
  22. hookOnLoad = iota
  23. hookPreEval
  24. hookPostEval
  25. hookOnMatch
  26. )
  27. // @tko : todo - debug mode
  28. func (h *Hook) Build(hookStage int) error {
  29. ctx := map[string]interface{}{}
  30. switch hookStage {
  31. case hookOnLoad:
  32. ctx = GetOnLoadEnv(&AppsecRuntimeConfig{})
  33. case hookPreEval:
  34. ctx = GetPreEvalEnv(&AppsecRuntimeConfig{}, &ParsedRequest{})
  35. case hookPostEval:
  36. ctx = GetPostEvalEnv(&AppsecRuntimeConfig{}, &ParsedRequest{})
  37. case hookOnMatch:
  38. ctx = GetOnMatchEnv(&AppsecRuntimeConfig{}, &ParsedRequest{}, types.Event{})
  39. }
  40. opts := exprhelpers.GetExprOptions(ctx)
  41. if h.Filter != "" {
  42. program, err := expr.Compile(h.Filter, opts...) //FIXME: opts
  43. if err != nil {
  44. return fmt.Errorf("unable to compile filter %s : %w", h.Filter, err)
  45. }
  46. h.FilterExpr = program
  47. }
  48. for _, apply := range h.Apply {
  49. program, err := expr.Compile(apply, opts...)
  50. if err != nil {
  51. return fmt.Errorf("unable to compile apply %s : %w", apply, err)
  52. }
  53. h.ApplyExpr = append(h.ApplyExpr, program)
  54. }
  55. return nil
  56. }
  57. type AppsecTempResponse struct {
  58. InBandInterrupt bool
  59. OutOfBandInterrupt bool
  60. Action string //allow, deny, captcha, log
  61. HTTPResponseCode int
  62. SendEvent bool //do we send an internal event on rule match
  63. SendAlert bool //do we send an alert on rule match
  64. }
  65. type AppsecSubEngineOpts struct {
  66. DisableBodyInspection bool `yaml:"disable_body_inspection"`
  67. RequestBodyInMemoryLimit *int `yaml:"request_body_in_memory_limit"`
  68. }
  69. // runtime version of AppsecConfig
  70. type AppsecRuntimeConfig struct {
  71. Name string
  72. OutOfBandRules []AppsecCollection
  73. InBandRules []AppsecCollection
  74. DefaultRemediation string
  75. RemediationByTag map[string]string //Also used for ByName, as the name (for modsec rules) is a tag crowdsec-NAME
  76. RemediationById map[int]string
  77. CompiledOnLoad []Hook
  78. CompiledPreEval []Hook
  79. CompiledPostEval []Hook
  80. CompiledOnMatch []Hook
  81. CompiledVariablesTracking []*regexp.Regexp
  82. Config *AppsecConfig
  83. //CorazaLogger debuglog.Logger
  84. //those are ephemeral, created/destroyed with every req
  85. OutOfBandTx ExtendedTransaction //is it a good idea ?
  86. InBandTx ExtendedTransaction //is it a good idea ?
  87. Response AppsecTempResponse
  88. //should we store matched rules here ?
  89. Logger *log.Entry
  90. //Set by on_load to ignore some rules on loading
  91. DisabledInBandRuleIds []int
  92. DisabledInBandRulesTags []string //Also used for ByName, as the name (for modsec rules) is a tag crowdsec-NAME
  93. DisabledOutOfBandRuleIds []int
  94. DisabledOutOfBandRulesTags []string //Also used for ByName, as the name (for modsec rules) is a tag crowdsec-NAME
  95. }
  96. type AppsecConfig struct {
  97. Name string `yaml:"name"`
  98. OutOfBandRules []string `yaml:"outofband_rules"`
  99. InBandRules []string `yaml:"inband_rules"`
  100. DefaultRemediation string `yaml:"default_remediation"`
  101. DefaultPassAction string `yaml:"default_pass_action"`
  102. BlockedHTTPCode int `yaml:"blocked_http_code"`
  103. PassedHTTPCode int `yaml:"passed_http_code"`
  104. OnLoad []Hook `yaml:"on_load"`
  105. PreEval []Hook `yaml:"pre_eval"`
  106. PostEval []Hook `yaml:"post_eval"`
  107. OnMatch []Hook `yaml:"on_match"`
  108. VariablesTracking []string `yaml:"variables_tracking"`
  109. InbandOptions AppsecSubEngineOpts `yaml:"inband_options"`
  110. OutOfBandOptions AppsecSubEngineOpts `yaml:"outofband_options"`
  111. LogLevel *log.Level `yaml:"log_level"`
  112. Logger *log.Entry `yaml:"-"`
  113. }
  114. func (w *AppsecRuntimeConfig) ClearResponse() {
  115. log.Debugf("#-> %p", w)
  116. w.Response = AppsecTempResponse{}
  117. log.Debugf("-> %p", w.Config)
  118. w.Response.Action = w.Config.DefaultPassAction
  119. w.Response.HTTPResponseCode = w.Config.PassedHTTPCode
  120. w.Response.SendEvent = true
  121. w.Response.SendAlert = true
  122. }
  123. func (wc *AppsecConfig) LoadByPath(file string) error {
  124. wc.Logger.Debugf("loading config %s", file)
  125. yamlFile, err := os.ReadFile(file)
  126. if err != nil {
  127. return fmt.Errorf("unable to read file %s : %s", file, err)
  128. }
  129. err = yaml.UnmarshalStrict(yamlFile, wc)
  130. if err != nil {
  131. return fmt.Errorf("unable to parse yaml file %s : %s", file, err)
  132. }
  133. if wc.Name == "" {
  134. return fmt.Errorf("name cannot be empty")
  135. }
  136. if wc.LogLevel == nil {
  137. lvl := wc.Logger.Logger.GetLevel()
  138. wc.LogLevel = &lvl
  139. }
  140. wc.Logger = wc.Logger.Dup().WithField("name", wc.Name)
  141. wc.Logger.Logger.SetLevel(*wc.LogLevel)
  142. if wc.DefaultRemediation == "" {
  143. return fmt.Errorf("default_remediation cannot be empty")
  144. }
  145. switch wc.DefaultRemediation {
  146. case "ban", "captcha", "log":
  147. //those are the officially supported remediation(s)
  148. default:
  149. wc.Logger.Warningf("default '%s' remediation of %s is none of [ban,captcha,log] ensure bouncer compatbility!", wc.DefaultRemediation, file)
  150. }
  151. if wc.BlockedHTTPCode == 0 {
  152. wc.BlockedHTTPCode = 403
  153. }
  154. if wc.PassedHTTPCode == 0 {
  155. wc.PassedHTTPCode = 200
  156. }
  157. if wc.DefaultPassAction == "" {
  158. wc.DefaultPassAction = "allow"
  159. }
  160. return nil
  161. }
  162. func (wc *AppsecConfig) Load(configName string) error {
  163. appsecConfigs := hub.GetItemMap(cwhub.APPSEC_CONFIGS)
  164. for _, hubAppsecConfigItem := range appsecConfigs {
  165. if !hubAppsecConfigItem.State.Installed {
  166. continue
  167. }
  168. if hubAppsecConfigItem.Name != configName {
  169. continue
  170. }
  171. wc.Logger.Infof("loading %s", hubAppsecConfigItem.State.LocalPath)
  172. err := wc.LoadByPath(hubAppsecConfigItem.State.LocalPath)
  173. if err != nil {
  174. return fmt.Errorf("unable to load appsec-config %s : %s", hubAppsecConfigItem.State.LocalPath, err)
  175. }
  176. return nil
  177. }
  178. return fmt.Errorf("no appsec-config found for %s", configName)
  179. }
  180. func (wc *AppsecConfig) GetDataDir() string {
  181. return hub.GetDataDir()
  182. }
  183. func (wc *AppsecConfig) Build() (*AppsecRuntimeConfig, error) {
  184. ret := &AppsecRuntimeConfig{Logger: wc.Logger.WithField("component", "appsec_runtime_config")}
  185. ret.Name = wc.Name
  186. ret.Config = wc
  187. ret.DefaultRemediation = wc.DefaultRemediation
  188. wc.Logger.Tracef("Loading config %+v", wc)
  189. //load rules
  190. for _, rule := range wc.OutOfBandRules {
  191. wc.Logger.Infof("loading outofband rule %s", rule)
  192. collections, err := LoadCollection(rule, wc.Logger.WithField("component", "appsec_collection_loader"))
  193. if err != nil {
  194. return nil, fmt.Errorf("unable to load outofband rule %s : %s", rule, err)
  195. }
  196. ret.OutOfBandRules = append(ret.OutOfBandRules, collections...)
  197. }
  198. wc.Logger.Infof("Loaded %d outofband rules", len(ret.OutOfBandRules))
  199. for _, rule := range wc.InBandRules {
  200. wc.Logger.Infof("loading inband rule %s", rule)
  201. collections, err := LoadCollection(rule, wc.Logger.WithField("component", "appsec_collection_loader"))
  202. if err != nil {
  203. return nil, fmt.Errorf("unable to load inband rule %s : %s", rule, err)
  204. }
  205. ret.InBandRules = append(ret.InBandRules, collections...)
  206. }
  207. wc.Logger.Infof("Loaded %d inband rules", len(ret.InBandRules))
  208. //load hooks
  209. for _, hook := range wc.OnLoad {
  210. err := hook.Build(hookOnLoad)
  211. if err != nil {
  212. return nil, fmt.Errorf("unable to build on_load hook : %s", err)
  213. }
  214. ret.CompiledOnLoad = append(ret.CompiledOnLoad, hook)
  215. }
  216. for _, hook := range wc.PreEval {
  217. err := hook.Build(hookPreEval)
  218. if err != nil {
  219. return nil, fmt.Errorf("unable to build pre_eval hook : %s", err)
  220. }
  221. ret.CompiledPreEval = append(ret.CompiledPreEval, hook)
  222. }
  223. for _, hook := range wc.PostEval {
  224. err := hook.Build(hookPostEval)
  225. if err != nil {
  226. return nil, fmt.Errorf("unable to build post_eval hook : %s", err)
  227. }
  228. ret.CompiledPostEval = append(ret.CompiledPostEval, hook)
  229. }
  230. for _, hook := range wc.OnMatch {
  231. err := hook.Build(hookOnMatch)
  232. if err != nil {
  233. return nil, fmt.Errorf("unable to build on_match hook : %s", err)
  234. }
  235. ret.CompiledOnMatch = append(ret.CompiledOnMatch, hook)
  236. }
  237. //variable tracking
  238. for _, variable := range wc.VariablesTracking {
  239. compiledVariableRule, err := regexp.Compile(variable)
  240. if err != nil {
  241. return nil, fmt.Errorf("cannot compile variable regexp %s: %w", variable, err)
  242. }
  243. ret.CompiledVariablesTracking = append(ret.CompiledVariablesTracking, compiledVariableRule)
  244. }
  245. return ret, nil
  246. }
  247. func (w *AppsecRuntimeConfig) ProcessOnLoadRules() error {
  248. for _, rule := range w.CompiledOnLoad {
  249. if rule.FilterExpr != nil {
  250. output, err := exprhelpers.Run(rule.FilterExpr, GetOnLoadEnv(w), w.Logger, w.Logger.Level >= log.DebugLevel)
  251. if err != nil {
  252. return fmt.Errorf("unable to run appsec on_load filter %s : %w", rule.Filter, err)
  253. }
  254. switch t := output.(type) {
  255. case bool:
  256. if !t {
  257. log.Debugf("filter didnt match")
  258. continue
  259. }
  260. default:
  261. log.Errorf("Filter must return a boolean, can't filter")
  262. continue
  263. }
  264. }
  265. for _, applyExpr := range rule.ApplyExpr {
  266. _, err := exprhelpers.Run(applyExpr, GetOnLoadEnv(w), w.Logger, w.Logger.Level >= log.DebugLevel)
  267. if err != nil {
  268. log.Errorf("unable to apply appsec on_load expr: %s", err)
  269. continue
  270. }
  271. }
  272. }
  273. return nil
  274. }
  275. func (w *AppsecRuntimeConfig) ProcessOnMatchRules(request *ParsedRequest, evt types.Event) error {
  276. for _, rule := range w.CompiledOnMatch {
  277. if rule.FilterExpr != nil {
  278. output, err := exprhelpers.Run(rule.FilterExpr, GetOnMatchEnv(w, request, evt), w.Logger, w.Logger.Level >= log.DebugLevel)
  279. if err != nil {
  280. return fmt.Errorf("unable to run appsec on_match filter %s : %w", rule.Filter, err)
  281. }
  282. switch t := output.(type) {
  283. case bool:
  284. if !t {
  285. log.Debugf("filter didnt match")
  286. continue
  287. }
  288. default:
  289. log.Errorf("Filter must return a boolean, can't filter")
  290. continue
  291. }
  292. }
  293. for _, applyExpr := range rule.ApplyExpr {
  294. _, err := exprhelpers.Run(applyExpr, GetOnMatchEnv(w, request, evt), w.Logger, w.Logger.Level >= log.DebugLevel)
  295. if err != nil {
  296. log.Errorf("unable to apply appsec on_match expr: %s", err)
  297. continue
  298. }
  299. }
  300. }
  301. return nil
  302. }
  303. func (w *AppsecRuntimeConfig) ProcessPreEvalRules(request *ParsedRequest) error {
  304. for _, rule := range w.CompiledPreEval {
  305. if rule.FilterExpr != nil {
  306. output, err := exprhelpers.Run(rule.FilterExpr, GetPreEvalEnv(w, request), w.Logger, w.Logger.Level >= log.DebugLevel)
  307. if err != nil {
  308. return fmt.Errorf("unable to run appsec pre_eval filter %s : %w", rule.Filter, err)
  309. }
  310. switch t := output.(type) {
  311. case bool:
  312. if !t {
  313. log.Debugf("filter didnt match")
  314. continue
  315. }
  316. default:
  317. log.Errorf("Filter must return a boolean, can't filter")
  318. continue
  319. }
  320. }
  321. // here means there is no filter or the filter matched
  322. for _, applyExpr := range rule.ApplyExpr {
  323. _, err := exprhelpers.Run(applyExpr, GetPreEvalEnv(w, request), w.Logger, w.Logger.Level >= log.DebugLevel)
  324. if err != nil {
  325. log.Errorf("unable to apply appsec pre_eval expr: %s", err)
  326. continue
  327. }
  328. }
  329. }
  330. return nil
  331. }
  332. func (w *AppsecRuntimeConfig) ProcessPostEvalRules(request *ParsedRequest) error {
  333. for _, rule := range w.CompiledPostEval {
  334. if rule.FilterExpr != nil {
  335. output, err := exprhelpers.Run(rule.FilterExpr, GetPostEvalEnv(w, request), w.Logger, w.Logger.Level >= log.DebugLevel)
  336. if err != nil {
  337. return fmt.Errorf("unable to run appsec post_eval filter %s : %w", rule.Filter, err)
  338. }
  339. switch t := output.(type) {
  340. case bool:
  341. if !t {
  342. log.Debugf("filter didnt match")
  343. continue
  344. }
  345. default:
  346. log.Errorf("Filter must return a boolean, can't filter")
  347. continue
  348. }
  349. }
  350. // here means there is no filter or the filter matched
  351. for _, applyExpr := range rule.ApplyExpr {
  352. _, err := exprhelpers.Run(applyExpr, GetPostEvalEnv(w, request), w.Logger, w.Logger.Level >= log.DebugLevel)
  353. if err != nil {
  354. log.Errorf("unable to apply appsec post_eval expr: %s", err)
  355. continue
  356. }
  357. }
  358. }
  359. return nil
  360. }
  361. func (w *AppsecRuntimeConfig) RemoveInbandRuleByID(id int) error {
  362. w.Logger.Debugf("removing inband rule %d", id)
  363. return w.InBandTx.RemoveRuleByIDWithError(id)
  364. }
  365. func (w *AppsecRuntimeConfig) RemoveOutbandRuleByID(id int) error {
  366. w.Logger.Debugf("removing outband rule %d", id)
  367. return w.OutOfBandTx.RemoveRuleByIDWithError(id)
  368. }
  369. func (w *AppsecRuntimeConfig) RemoveInbandRuleByTag(tag string) error {
  370. w.Logger.Debugf("removing inband rule with tag %s", tag)
  371. return w.InBandTx.RemoveRuleByTagWithError(tag)
  372. }
  373. func (w *AppsecRuntimeConfig) RemoveOutbandRuleByTag(tag string) error {
  374. w.Logger.Debugf("removing outband rule with tag %s", tag)
  375. return w.OutOfBandTx.RemoveRuleByTagWithError(tag)
  376. }
  377. func (w *AppsecRuntimeConfig) RemoveInbandRuleByName(name string) error {
  378. tag := fmt.Sprintf("crowdsec-%s", name)
  379. w.Logger.Debugf("removing inband rule %s", tag)
  380. return w.InBandTx.RemoveRuleByTagWithError(tag)
  381. }
  382. func (w *AppsecRuntimeConfig) RemoveOutbandRuleByName(name string) error {
  383. tag := fmt.Sprintf("crowdsec-%s", name)
  384. w.Logger.Debugf("removing outband rule %s", tag)
  385. return w.OutOfBandTx.RemoveRuleByTagWithError(tag)
  386. }
  387. func (w *AppsecRuntimeConfig) CancelEvent() error {
  388. w.Logger.Debugf("canceling event")
  389. w.Response.SendEvent = false
  390. return nil
  391. }
  392. // Disable a rule at load time, meaning it will not run for any request
  393. func (w *AppsecRuntimeConfig) DisableInBandRuleByID(id int) error {
  394. w.DisabledInBandRuleIds = append(w.DisabledInBandRuleIds, id)
  395. return nil
  396. }
  397. // Disable a rule at load time, meaning it will not run for any request
  398. func (w *AppsecRuntimeConfig) DisableInBandRuleByName(name string) error {
  399. tagValue := fmt.Sprintf("crowdsec-%s", name)
  400. w.DisabledInBandRulesTags = append(w.DisabledInBandRulesTags, tagValue)
  401. return nil
  402. }
  403. // Disable a rule at load time, meaning it will not run for any request
  404. func (w *AppsecRuntimeConfig) DisableInBandRuleByTag(tag string) error {
  405. w.DisabledInBandRulesTags = append(w.DisabledInBandRulesTags, tag)
  406. return nil
  407. }
  408. // Disable a rule at load time, meaning it will not run for any request
  409. func (w *AppsecRuntimeConfig) DisableOutBandRuleByID(id int) error {
  410. w.DisabledOutOfBandRuleIds = append(w.DisabledOutOfBandRuleIds, id)
  411. return nil
  412. }
  413. // Disable a rule at load time, meaning it will not run for any request
  414. func (w *AppsecRuntimeConfig) DisableOutBandRuleByName(name string) error {
  415. tagValue := fmt.Sprintf("crowdsec-%s", name)
  416. w.DisabledOutOfBandRulesTags = append(w.DisabledOutOfBandRulesTags, tagValue)
  417. return nil
  418. }
  419. // Disable a rule at load time, meaning it will not run for any request
  420. func (w *AppsecRuntimeConfig) DisableOutBandRuleByTag(tag string) error {
  421. w.DisabledOutOfBandRulesTags = append(w.DisabledOutOfBandRulesTags, tag)
  422. return nil
  423. }
  424. func (w *AppsecRuntimeConfig) SendEvent() error {
  425. w.Logger.Debugf("sending event")
  426. w.Response.SendEvent = true
  427. return nil
  428. }
  429. func (w *AppsecRuntimeConfig) SendAlert() error {
  430. w.Logger.Debugf("sending alert")
  431. w.Response.SendAlert = true
  432. return nil
  433. }
  434. func (w *AppsecRuntimeConfig) CancelAlert() error {
  435. w.Logger.Debugf("canceling alert")
  436. w.Response.SendAlert = false
  437. return nil
  438. }
  439. func (w *AppsecRuntimeConfig) SetActionByTag(tag string, action string) error {
  440. if w.RemediationByTag == nil {
  441. w.RemediationByTag = make(map[string]string)
  442. }
  443. w.Logger.Debugf("setting action of %s to %s", tag, action)
  444. w.RemediationByTag[tag] = action
  445. return nil
  446. }
  447. func (w *AppsecRuntimeConfig) SetActionByID(id int, action string) error {
  448. if w.RemediationById == nil {
  449. w.RemediationById = make(map[int]string)
  450. }
  451. w.Logger.Debugf("setting action of %d to %s", id, action)
  452. w.RemediationById[id] = action
  453. return nil
  454. }
  455. func (w *AppsecRuntimeConfig) SetActionByName(name string, action string) error {
  456. if w.RemediationByTag == nil {
  457. w.RemediationByTag = make(map[string]string)
  458. }
  459. tag := fmt.Sprintf("crowdsec-%s", name)
  460. w.Logger.Debugf("setting action of %s to %s", tag, action)
  461. w.RemediationByTag[tag] = action
  462. return nil
  463. }
  464. func (w *AppsecRuntimeConfig) SetAction(action string) error {
  465. //log.Infof("setting to %s", action)
  466. w.Logger.Debugf("setting action to %s", action)
  467. switch action {
  468. case "allow":
  469. w.Response.Action = action
  470. w.Response.HTTPResponseCode = w.Config.PassedHTTPCode
  471. //@tko how should we handle this ? it seems bouncer only understand bans, but it might be misleading ?
  472. case "deny", "ban", "block":
  473. w.Response.Action = "ban"
  474. case "log":
  475. w.Response.Action = action
  476. w.Response.HTTPResponseCode = w.Config.PassedHTTPCode
  477. case "captcha":
  478. w.Response.Action = action
  479. default:
  480. return fmt.Errorf("unknown action %s", action)
  481. }
  482. return nil
  483. }
  484. func (w *AppsecRuntimeConfig) SetHTTPCode(code int) error {
  485. w.Logger.Debugf("setting http code to %d", code)
  486. w.Response.HTTPResponseCode = code
  487. return nil
  488. }
  489. type BodyResponse struct {
  490. Action string `json:"action"`
  491. HTTPStatus int `json:"http_status"`
  492. }
  493. func (w *AppsecRuntimeConfig) GenerateResponse(response AppsecTempResponse, logger *log.Entry) BodyResponse {
  494. resp := BodyResponse{}
  495. //if there is no interrupt, we should allow with default code
  496. if !response.InBandInterrupt {
  497. resp.Action = w.Config.DefaultPassAction
  498. resp.HTTPStatus = w.Config.PassedHTTPCode
  499. return resp
  500. }
  501. resp.Action = response.Action
  502. if resp.Action == "" {
  503. resp.Action = w.Config.DefaultRemediation
  504. }
  505. logger.Debugf("action is %s", resp.Action)
  506. resp.HTTPStatus = response.HTTPResponseCode
  507. if resp.HTTPStatus == 0 {
  508. resp.HTTPStatus = w.Config.BlockedHTTPCode
  509. }
  510. logger.Debugf("http status is %d", resp.HTTPStatus)
  511. return resp
  512. }