ban.ml 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  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. (** Prepare a ready-to-use cdb.
  31. *)
  32. let prepare_cdb (db : Mapcdb.cdb) : Mapcdb.cdb =
  33. (* don't log in case banned *)
  34. let Cdb db' = db in
  35. let _ = db' |> File.restore_static in
  36. db
  37. let chunk_s = 600.
  38. (* if expiry sooner than 2 chunks in the future: None *)
  39. let check (db : Mapcdb.cdb) (tnow : Ptime.t) (k : string) : Ptime.t option =
  40. (* Logr.debug (fun m -> m "%s.%s %s" "Ban" "check" k); *)
  41. Option.bind
  42. (Mapcdb.find_string_opt k db)
  43. (fun t ->
  44. let noban v = Logr.debug (fun m -> m "%s.%s %s not banned (%s)" "Ban" "check" k v);
  45. None in
  46. (* Logr.debug (fun m -> m "%s.%s check %s" "Ban" "check" t); *)
  47. match t |> Ptime.of_rfc3339 with
  48. | Ok (t, _, _) ->
  49. let dt = 2. *. chunk_s |> Ptime.Span.of_float_s |> Option.get in
  50. let than = Ptime.sub_span t dt |> Option.get in
  51. if Ptime.is_earlier tnow ~than
  52. then (
  53. Logr.info (fun m -> m "%s.%s %s banned until %a" "Ban" "check" k Ptime.pp than);
  54. Some than)
  55. else noban "expired"
  56. | _ -> noban "time fail" (* is this too generous? *)
  57. )
  58. (** Check for a ban for the request.
  59. *
  60. * db ban db
  61. * tnow time
  62. * req http request
  63. *)
  64. let check_req (db : Mapcdb.cdb) (tnow : Ptime.t) (req : Cgi.Request.t) =
  65. match check db tnow req.remote_addr with
  66. | None -> Ok req
  67. | Some t -> Http.s429_t t
  68. (** add another chunk to the expiry in the ban db *)
  69. let escalate db tnow addr : unit =
  70. let base = match Mapcdb.find_string_opt addr db with
  71. | None -> tnow
  72. | Some v ->
  73. match v |> Ptime.of_rfc3339 with
  74. | Ok (t, _, _) -> max tnow t
  75. | Error _ -> tnow
  76. in
  77. let expiry = chunk_s
  78. |> Ptime.Span.of_float_s |> Option.get
  79. |> Ptime.add_span base |> Option.get
  80. |> Ptime.to_rfc3339 in
  81. Logr.info (fun m -> m "%s.%s addr: %s expiry: %s" "Ban" "escalate" addr expiry);
  82. let _ = Mapcdb.update_string addr expiry db in
  83. Logr.warn (fun m -> m "%s.%s TODO use a predicate to remove expired entries." "Ban" "escalate")
  84. let escalate_req db tnow (r : Cgi.Request.t)=
  85. Ok (escalate db tnow r.remote_addr)
  86. (*
  87. *
  88. * # Brute force protect authentication.
  89. *
  90. * ## Requirements
  91. *
  92. * 1) persistence on disc,
  93. * 2) fast lookup if a given address (ip4 or ip6 string) is blacklisted and not
  94. * expired,
  95. * 3) add penalty and refresh expiry,
  96. * 4) housekeeping (unaccessed expiry)
  97. *
  98. * ## Caveats
  99. *
  100. * 1) mitigate DOS (be savy with CPU, files, space)
  101. * 2) fast negative answer (not banned)
  102. * 3) slow penalty, do the housekeeping here
  103. * 4) slow ban lift/expiry, too
  104. *
  105. * ## Possible storage
  106. *
  107. * - separate files named after address, timestamp expiry (evtl. with offset),
  108. * content severity
  109. * or
  110. * - one binary file mmapped as a Bigarray
  111. * or
  112. * - one fixed-line-length text file mmapped as a Bigstring
  113. * or
  114. * - one Csexp file
  115. *)