gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-taldir] branch master updated: fix rate limiting logic


From: gnunet
Subject: [taler-taldir] branch master updated: fix rate limiting logic
Date: Mon, 11 Jul 2022 18:29:47 +0200

This is an automated email from the git hooks/post-receive script.

martin-schanzenbach pushed a commit to branch master
in repository taldir.

The following commit(s) were added to refs/heads/master by this push:
     new a1a6414  fix rate limiting logic
a1a6414 is described below

commit a1a641455a0ded785a7337c183e3ef28dc3da76d
Author: Martin Schanzenbach <schanzen@gnunet.org>
AuthorDate: Mon Jul 11 18:29:44 2022 +0200

    fix rate limiting logic
---
 cmd/taldir-server/main_test.go |  34 ++++++++++++
 pkg/taldir/taldir.go           | 116 +++++++++++++++++++++++++----------------
 taldir.conf                    |   4 +-
 3 files changed, 108 insertions(+), 46 deletions(-)

diff --git a/cmd/taldir-server/main_test.go b/cmd/taldir-server/main_test.go
index 77c18c9..f5b6675 100644
--- a/cmd/taldir-server/main_test.go
+++ b/cmd/taldir-server/main_test.go
@@ -181,6 +181,40 @@ func TestReRegisterRequestTooMany(s *testing.T) {
 
 }
 
+func TestSolutionRequestTooMany(s *testing.T) {
+  t.ClearDatabase()
+
+  req, _ := http.NewRequest("POST", "/register/test", 
bytes.NewBuffer(validRegisterRequest))
+  response := executeRequest(req)
+
+  if http.StatusAccepted != response.Code {
+    s.Errorf("Expected response code %d. Got %d\n", http.StatusAccepted, 
response.Code)
+  }
+  h_addr := getHAddress("abc@test")
+  solution := 
util.GenerateSolution("000G006XE97PTWV3B7AJNCRQZA6BF26HPV3XZ07293FMY7KD4181946A90",
 "wrongSolution")
+  solutionJSON := "{\"solution\": \"" + solution + "\"}"
+  req, _ = http.NewRequest("POST", "/" + h_addr, 
bytes.NewBuffer([]byte(solutionJSON)))
+  response = executeRequest(req)
+  if http.StatusForbidden != response.Code {
+    s.Errorf("Expected response code %d. Got %d\n", http.StatusForbidden, 
response.Code)
+  }
+  req, _ = http.NewRequest("POST", "/" + h_addr, 
bytes.NewBuffer([]byte(solutionJSON)))
+  response = executeRequest(req)
+  if http.StatusForbidden != response.Code {
+    s.Errorf("Expected response code %d. Got %d\n", http.StatusForbidden, 
response.Code)
+  }
+  req, _ = http.NewRequest("POST", "/" + h_addr, 
bytes.NewBuffer([]byte(solutionJSON)))
+  response = executeRequest(req)
+  if http.StatusForbidden != response.Code {
+    s.Errorf("Expected response code %d. Got %d\n", http.StatusForbidden, 
response.Code)
+  }
+  req, _ = http.NewRequest("POST", "/" + h_addr, 
bytes.NewBuffer([]byte(solutionJSON)))
+  response = executeRequest(req)
+  if http.StatusTooManyRequests != response.Code {
+    s.Errorf("Expected response code %d. Got %d\n", 
http.StatusTooManyRequests, response.Code)
+  }
+
+}
 
 func TestRegisterRequestWrongPubkey(s *testing.T) {
   t.ClearDatabase()
diff --git a/pkg/taldir/taldir.go b/pkg/taldir/taldir.go
index b5dd9d8..76c5a6a 100644
--- a/pkg/taldir/taldir.go
+++ b/pkg/taldir/taldir.go
@@ -78,8 +78,15 @@ type Taldir struct {
   // Code TTL
   CodeTtl time.Duration
 
-  // Code retries max
-  CodeRetryMax int
+  // How often may a challenge be requested
+  ValidationInitiationMax int
+
+  // How often may a solution be attempted (in the given timeframe)
+  SolutionAttemptsMax int
+
+  // The timeframe for the above solution attempts
+  SolutionTimeframe time.Duration
+
 
   // Code length in bytes before encoding
   CodeBytes int
@@ -161,11 +168,11 @@ type Entry struct {
   // Public key of the user to register in base32
   PublicKey string `json:"public_key"`
 
-  // Time of (re)registration. In Unix epoch microseconds)
+  // Time of (re)registration.
   RegisteredAt int64 `json:"-"`
 
   // How long the registration lasts in microseconds
-  Duration int64 `json:"-"`
+  Duration time.Duration `json:"-"`
 }
 
 // A validation is created when a registration for an entry is initiated.
@@ -196,7 +203,13 @@ type Validation struct {
   TimeframeStart time.Time
 
   // How often was this validation re-initiated
-  RetryCount int
+  InitiationCount int
+
+  // How often was a solution for this validation tried
+  SolutionAttemptCount int
+
+  // The beginning of the last solution timeframe
+  LastSolutionTimeframeStart time.Time
 }
 
 type ErrorDetail struct {
@@ -241,13 +254,6 @@ type ValidationConfirmation struct {
   Solution string `json:"solution"`
 }
 
-// matcher is a language.Matcher configured for all supported languages.
-var langMatcher = language.NewMatcher([]language.Tag{
-  language.BritishEnglish,
-  //language.Norwegian,
-  language.German,
-})
-
 // Primary lookup function.
 // Allows the caller to query a wallet key using the hash(!) of the
 // identity, e.g. SHA512(<email address>)
@@ -300,21 +306,40 @@ func (t *Taldir) validationRequest(w http.ResponseWriter, 
r *http.Request){
     w.WriteHeader(http.StatusNotFound)
     return
   }
+  validation.SolutionAttemptCount++
+  if 
validation.LastSolutionTimeframeStart.Add(t.SolutionTimeframe).After(time.Now())
 {
+    if validation.SolutionAttemptCount > t.SolutionAttemptsMax {
+      w.WriteHeader(429)
+      rlResponse := RateLimitedResponse{
+        Code: gana.TALDIR_REGISTER_RATE_LIMITED,
+        RequestFrequency: t.RequestFrequency,
+        Hint: "Solution attempt rate limit reached",
+      }
+      jsonResp, _ := json.Marshal(rlResponse)
+      w.Write(jsonResp)
+      return
+    }
+  } else {
+    log.Println("New solution timeframe set.")
+    validation.LastSolutionTimeframeStart = time.Now()
+    validation.SolutionAttemptCount = 1
+  }
+  t.Db.Save(&validation)
   expectedSolution := util.GenerateSolution(validation.PublicKey, 
validation.Code)
   if confirm.Solution != expectedSolution {
-    // FIXME how TF do we rate limit here??
     w.WriteHeader(http.StatusForbidden)
     return
   }
   // FIXME: Expire validations somewhere?
   err = t.Db.Delete(&validation).Error
   if err != nil {
+    log.Fatalf("Error deleting validation")
     w.WriteHeader(http.StatusInternalServerError)
     return
   }
   entry.HsAddress = saltHAddress(validation.HAddress, t.Salt)
   entry.Inbox = validation.Inbox
-  entry.Duration = validation.Duration
+  entry.Duration = time.Duration(validation.Duration)
   entry.RegisteredAt = time.Now().UnixMicro()
   entry.PublicKey = validation.PublicKey
   err = t.Db.First(&entry, "hs_address = ?", entry.HsAddress).Error
@@ -350,6 +375,7 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r 
*http.Request){
     w.Write(resp)
     return
   }
+  // Check if this validation method is supported or not.
   if !t.Validators[vars["method"]] {
     errDetail.Code = gana.TALDIR_METHOD_NOT_SUPPORTED
     errDetail.Hint = "Unsupported method"
@@ -359,47 +385,39 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r 
*http.Request){
     w.Write(resp)
     return
   }
+
+  // Setup validation object. Retrieve object from DB if it already
+  // exists.
   h := sha512.New()
   h.Write([]byte(req.Address))
-  validation.HAddress = util.EncodeBinaryToString(h.Sum(nil))
-  // We first try if there is already an entry for this address which
-  // is still valid and the duration is not extended.
+  h_address := util.EncodeBinaryToString(h.Sum(nil))
+  validation.HAddress = h_address
   hs_address := saltHAddress(validation.HAddress, t.Salt)
   err = t.Db.First(&entry, "hs_address = ?", hs_address).Error
   if err == nil {
     log.Println("Entry for this address already exists..")
-    lastRegValidity := entry.RegisteredAt + entry.Duration
-    requestedValidity := time.Now().UnixMicro() + req.Duration
-    earliestReRegistration := entry.RegisteredAt + t.RequestFrequency
-    // Rate limit re-registrations.
-    if time.Now().UnixMicro() < earliestReRegistration {
-      w.WriteHeader(429)
-      rlResponse := RateLimitedResponse{
-        Code: gana.TALDIR_REGISTER_RATE_LIMITED,
-        RequestFrequency: t.RequestFrequency,
-        Hint: "Registration rate limit reached",
-      }
-      jsonResp, _ := json.Marshal(rlResponse)
-      w.Write(jsonResp)
-      return
-    }
-    // Do not allow re-registrations with shorter duration.
-    if requestedValidity <= lastRegValidity {
-      w.WriteHeader(200)
-      // FIXME how to return how long it is already paid for??
+    regAt := time.UnixMicro(entry.RegisteredAt)
+    entryValidity := regAt.Add(entry.Duration)
+    log.Printf("Entry valid until: %s , requested until: %s\n", entryValidity, 
time.Now().Add(time.Duration(req.Duration)))
+    if time.Now().Add(time.Duration(req.Duration)).Before(entryValidity) {
+      w.WriteHeader(http.StatusOK)
+      w.Header().Set("Content-Type", "application/json")
+      w.Write([]byte("{\"valid_until\": " + entryValidity.String() + "}"))
       return
     }
   }
-  err = t.Db.First(&validation, "h_address = ?", validation.HAddress).Error
+  err = t.Db.First(&validation, "h_address = ?", h_address).Error
   validation.Code = util.GenerateCode(t.CodeBytes)
   validation.Inbox = req.Inbox
   validation.Duration = req.Duration
   validation.PublicKey = req.PublicKey
+  validation.SolutionAttemptCount = 0
+  validation.LastSolutionTimeframeStart = time.Now()
   if err == nil {
-    // FIXME: Validation already pending for this address
-    // How should we proceed here? Expire old validations?
+    // Limit re-initiation attempts
+    validation.InitiationCount++
     if time.Now().Before(validation.TimeframeStart.Add(t.CodeTtl)) {
-      if validation.RetryCount >= t.CodeRetryMax {
+      if validation.InitiationCount > t.ValidationInitiationMax {
         w.WriteHeader(429)
         rlResponse := RateLimitedResponse{
           Code: gana.TALDIR_REGISTER_RATE_LIMITED,
@@ -411,14 +429,14 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r 
*http.Request){
         t.Db.Delete(&validation)
         return
       }
-      validation.RetryCount++
     } else {
       log.Println("Validation stale, resetting retry counter")
       validation.TimeframeStart = time.Now()
-      validation.RetryCount = 0
+      validation.InitiationCount = 1
     }
     err = t.Db.Save(&validation).Error
   } else  {
+    validation.InitiationCount = 1
     validation.TimeframeStart = time.Now()
     err = t.Db.Create(&validation).Error
   }
@@ -426,7 +444,7 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r 
*http.Request){
     w.WriteHeader(http.StatusInternalServerError)
     return
   }
-  fmt.Println("Address registration request created:", validation)
+  log.Println("Address registration request created:", validation)
   if !t.Cfg.Section("taldir-" + vars["method"]).HasKey("command") {
     log.Fatal(err)
     t.Db.Delete(&validation)
@@ -449,7 +467,7 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r 
*http.Request){
     return
   }
   w.WriteHeader(202)
-  fmt.Printf("Output from method script %s is %s\n", path, out)
+  log.Printf("Output from method script %s is %s\n", path, out)
 }
 
 func notImplemented(w http.ResponseWriter, r *http.Request) {
@@ -655,13 +673,21 @@ func (t *Taldir) Initialize(cfgfile string) {
     t.Validators[a] = true
   }
   t.CodeBytes = 
t.Cfg.Section("taldir").Key("activation_code_bytes").MustInt(16)
-  t.CodeRetryMax = 
t.Cfg.Section("taldir").Key("activation_retry_max").MustInt(2)
+  t.ValidationInitiationMax = 
t.Cfg.Section("taldir").Key("validation_initiation_max").MustInt(3)
+  t.SolutionAttemptsMax = 
t.Cfg.Section("taldir").Key("solution_attempt_max").MustInt(3)
+
   validationTtlStr := 
t.Cfg.Section("taldir").Key("activation_code_ttl").MustString("5m")
   t.CodeTtl, err = time.ParseDuration(validationTtlStr)
   if err != nil {
     log.Fatal(err)
   }
 
+  retryTimeframeStr := 
t.Cfg.Section("taldir").Key("code_attempt_timeframe").MustString("1h")
+  t.SolutionTimeframe, err = time.ParseDuration(retryTimeframeStr)
+  if err != nil {
+    log.Fatal(err)
+  }
+
   psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s 
sslmode=disable",
   t.Cfg.Section("taldir-pq").Key("host").MustString("localhost"),
   t.Cfg.Section("taldir-pq").Key("port").MustInt64(5432),
diff --git a/taldir.conf b/taldir.conf
index a34bd21..1c9a899 100644
--- a/taldir.conf
+++ b/taldir.conf
@@ -11,8 +11,10 @@ default_doc_lang = en-US
 default_tos_path = terms/
 default_pp_path = privacy/
 activation_code_bytes = 16
-activation_retry_max = 2
+validation_initiation_max = 3
+solution_attempt_max = 3
 activation_code_ttl = 10m
+solution_attempt_timeframe = 1h
 
 [taldir-email]
 sender = "taldir@taler.net"

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]