ban.ml 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. (*
  2. * _ _ ____ _
  3. * _| || |_/ ___| ___ _ __ _ __ ___ | |
  4. * |_ .. _\___ \ / _ \ '_ \| '_ \ / _ \| |
  5. * |_ _|___) | __/ |_) | |_) | (_) |_|
  6. * |_||_| |____/ \___| .__/| .__/ \___/(_)
  7. * |_| |_|
  8. *
  9. * Personal Social Web.
  10. *
  11. * Copyright (C) The #Seppo contributors. All rights reserved.
  12. *
  13. * This program is free software: you can redistribute it and/or modify
  14. * it under the terms of the GNU General Public License as published by
  15. * the Free Software Foundation, either version 3 of the License, or
  16. * (at your option) any later version.
  17. *
  18. * This program is distributed in the hope that it will be useful,
  19. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. * GNU General Public License for more details.
  22. *
  23. * You should have received a copy of the GNU General Public License
  24. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  25. *)
  26. (** Brute force mitigation HTTP 429,
  27. * https://www.rfc-editor.org/rfc/rfc6585#section-4 *)
  28. let fn = "app/var/run/ipban.cdb"
  29. let cdb = Mapcdb.Cdb fn
  30. let prepare_cdb (db : Mapcdb.cdb) : Mapcdb.cdb =
  31. (* don't log in case banned *)
  32. let Cdb db' = db in
  33. let _ = db' |> File.restore_static in
  34. db
  35. let chunk_s = 600.
  36. (* if expiry sooner than 2 chunks in the future: None *)
  37. let check (db : Mapcdb.cdb) (tnow : Ptime.t) (k : string) : Ptime.t option =
  38. (* Logr.debug (fun m -> m "%s.%s %s" "Ban" "check" k); *)
  39. Option.bind
  40. (Mapcdb.find_string_opt k db)
  41. (fun t ->
  42. let noban v = Logr.debug (fun m -> m "%s.%s %s not banned (%s)" "Ban" "check" k v);
  43. None in
  44. (* Logr.debug (fun m -> m "%s.%s check %s" "Ban" "check" t); *)
  45. match t |> Ptime.of_rfc3339 with
  46. | Ok (t, _, _) ->
  47. let dt = 2. *. chunk_s |> Ptime.Span.of_float_s |> Option.get in
  48. let than = Ptime.sub_span t dt |> Option.get in
  49. if Ptime.is_earlier tnow ~than
  50. then (
  51. Logr.info (fun m -> m "%s.%s %s banned until %a" "Ban" "check" k Ptime.pp than);
  52. Some than)
  53. else noban "expired"
  54. | _ -> noban "time fail" (* is this too generous? *)
  55. )
  56. let check_req (db : Mapcdb.cdb) (tnow : Ptime.t) (req : Cgi.Request.t) =
  57. match check db tnow req.remote_addr with
  58. | None -> Ok req
  59. | Some t -> Http.s429_t t
  60. (* add another chunk to the expiry in the ban db *)
  61. let escalate db tnow addr : unit =
  62. let base = match Mapcdb.find_string_opt addr db with
  63. | None -> tnow
  64. | Some v ->
  65. match v |> Ptime.of_rfc3339 with
  66. | Ok (t, _, _) -> max tnow t
  67. | Error _ -> tnow
  68. in
  69. let expiry = chunk_s
  70. |> Ptime.Span.of_float_s |> Option.get
  71. |> Ptime.add_span base |> Option.get
  72. |> Ptime.to_rfc3339 in
  73. Logr.info (fun m -> m "%s.%s addr: %s expiry: %s" "Ban" "escalate" addr expiry);
  74. let _ = Mapcdb.update_string addr expiry db in
  75. Logr.warn (fun m -> m "%s.%s TODO use a predicate to remove expired entries." "Ban" "escalate")
  76. let escalate_req db tnow (r : Cgi.Request.t)=
  77. Ok (escalate db tnow r.remote_addr)
  78. (*
  79. *
  80. * # Brute force protect authentication.
  81. *
  82. * ## Requirements
  83. *
  84. * 1) persistence on disc,
  85. * 2) fast lookup if a given address (ip4 or ip6 string) is blacklisted and not
  86. * expired,
  87. * 3) add penalty and refresh expiry,
  88. * 4) housekeeping (unaccessed expiry)
  89. *
  90. * ## Caveats
  91. *
  92. * 1) mitigate DOS (be savy with CPU, files, space)
  93. * 2) fast negative answer (not banned)
  94. * 3) slow penalty, do the housekeeping here
  95. * 4) slow ban lift/expiry, too
  96. *
  97. * ## Possible storage
  98. *
  99. * - separate files named after address, timestamp expiry (evtl. with offset),
  100. * content severity
  101. * or
  102. * - one binary file mmapped as a Bigarray
  103. * or
  104. * - one fixed-line-length text file mmapped as a Bigstring
  105. * or
  106. * - one Csexp file
  107. *)