Skip to content
This repository was archived by the owner on Jul 12, 2023. It is now read-only.

Commit 0db80f9

Browse files
authored
Attempt to fix realm quota alert (#743)
- Display the currently configured limit and remaining tokens on the realm settings page. - Only create the metrics for realms that have abuse prevention enabled.
1 parent 5748bae commit 0db80f9

File tree

6 files changed

+80
-49
lines changed

6 files changed

+80
-49
lines changed

cmd/server/assets/realmadmin/_form_abuse_prevention.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
{{define "realmadmin/_form_abuse_prevention"}}
22

33
{{$realm := .realm}}
4+
{{$quotaRemaining := .quotaRemaining}}
5+
{{$quotaLimit := .quotaLimit}}
46

57
<form method="POST" action="/realm/settings#abuse-prevention" class="floating-form">
68
{{ .csrfField }}
@@ -27,6 +29,17 @@
2729
</div>
2830

2931
<div id="abuse-prevention-configuration" class="collapse{{if $realm.AbusePreventionEnabled}} show{{end}}">
32+
<div class="alert alert-primary" role="alert">
33+
Your current remaining daily quota is:
34+
<small class="text-monospace">{{$quotaRemaining}}/{{$quotaLimit}}</small>. This value resets at
35+
midnight UTC.
36+
37+
{{if gt $quotaRemaining $quotaLimit}}
38+
If your remaining quota exceeds the maximum quota, it means a realm
39+
administrator added a temporary burst.
40+
{{end}}
41+
</div>
42+
3043
<div class="form-label-group">
3144
<input type="text" name="abuse_prevention_limit" id="abuse-prevention-limit" class="form-control" placeholder="Computed limit" value="{{$realm.AbusePreventionLimit}}" readonly />
3245
<label for="abuse-prevention-limit">Computed limit</label>

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ require (
4747
github.com/rakutentech/jwk-go v1.0.1
4848
github.com/russross/blackfriday/v2 v2.0.1
4949
github.com/sethvargo/go-envconfig v0.3.2
50-
github.com/sethvargo/go-limiter v0.5.2
50+
github.com/sethvargo/go-limiter v0.6.0
5151
github.com/sethvargo/go-password v0.2.0
52-
github.com/sethvargo/go-redisstore v0.2.1-opencensus
52+
github.com/sethvargo/go-redisstore v0.3.0-opencensus
5353
github.com/sethvargo/go-retry v0.1.0
5454
github.com/sethvargo/go-signalcontext v0.1.0
5555
github.com/sirupsen/logrus v1.7.0 // indirect

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,12 +1153,12 @@ github.com/sethvargo/go-envconfig v0.3.2 h1:277Lb2iTpUZjUZu1qLoLa/aetwvtZbKh8wNW
11531153
github.com/sethvargo/go-envconfig v0.3.2/go.mod h1:XZ2JRR7vhlBEO5zMmOpLgUhgYltqYqq4d4tKagtPUv0=
11541154
github.com/sethvargo/go-gcpkms v0.1.0 h1:pyjDLqLwpk9pMjDSTilPpaUjgP1AfSjX9WGzitZwGUY=
11551155
github.com/sethvargo/go-gcpkms v0.1.0/go.mod h1:33BuvqUjsYk0bpMgn+WCclCYtMLOyaqtn5j0fCo4vvk=
1156-
github.com/sethvargo/go-limiter v0.5.2 h1:NIFp7xy3NyE2+mEHbengdLQF0C0STOpwF5Qw5JtayIs=
1157-
github.com/sethvargo/go-limiter v0.5.2/go.mod h1:C0kbSFbiriE5k2FFOe18M1YZbAR2Fiwf72uGu0CXCcU=
1156+
github.com/sethvargo/go-limiter v0.6.0 h1:186jmCdl1ItQUXbHFdTBrFSZztN6/bL9855C5jfMlKU=
1157+
github.com/sethvargo/go-limiter v0.6.0/go.mod h1:C0kbSFbiriE5k2FFOe18M1YZbAR2Fiwf72uGu0CXCcU=
11581158
github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI=
11591159
github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE=
1160-
github.com/sethvargo/go-redisstore v0.2.1-opencensus h1:EAwZAuZr5DJdLmEruJTj2zeBvmZsIXI7wqgMueuaxks=
1161-
github.com/sethvargo/go-redisstore v0.2.1-opencensus/go.mod h1:TMFAy7azG5hDd/Hb5ng2CDsawcxg1+oEuGhuVp7eycI=
1160+
github.com/sethvargo/go-redisstore v0.3.0-opencensus h1:H9W15fuJHwHmttV+G6oY94J/YjxhRz0E/1S5y7elxlg=
1161+
github.com/sethvargo/go-redisstore v0.3.0-opencensus/go.mod h1:byILvIz3sOqWiKLQmL7KUK0CzD3MWHajkksZH7V43yk=
11621162
github.com/sethvargo/go-retry v0.1.0 h1:8sPqlWannzcReEcYjHSNw9becsiYudcwTD7CasGjQaI=
11631163
github.com/sethvargo/go-retry v0.1.0/go.mod h1:JzIOdZqQDNpPkQDmcqgtteAcxFLtYpNF/zJCM1ysDg8=
11641164
github.com/sethvargo/go-signalcontext v0.1.0 h1:3IU7HOlmRXF0PSDf85C4nJ/zjYDjF+DS+LufcKfLvyk=

pkg/controller/issueapi/issue.go

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -192,31 +192,35 @@ func (c *Controller) HandleIssue() http.Handler {
192192
}
193193
}
194194

195-
// If we got this far, we're about to issue a code.
196-
dig, err := digest.HMACUint(realm.ID, c.config.GetRateLimitConfig().HMACKey)
197-
if err != nil {
198-
controller.InternalError(w, r, c.h, err)
199-
return
200-
}
201-
key := fmt.Sprintf("realm:quota:%s", dig)
202-
limit, remaining, reset, ok, err := c.limiter.Take(ctx, key)
203-
if err != nil {
204-
logger.Errorw("failed to take from limiter", "error", err)
205-
stats.Record(ctx, c.metrics.QuotaErrors.M(1))
206-
c.h.RenderJSON(w, http.StatusInternalServerError, api.Errorf("failed to verify realm stats, please try again"))
207-
return
208-
}
209-
if !ok {
210-
logger.Warnw("realm has exceeded daily quota",
211-
"realm", realm.ID,
212-
"limit", limit,
213-
"reset", reset)
214-
stats.Record(ctx, c.metrics.QuotaExceeded.M(1))
215-
216-
if c.config.GetEnforceRealmQuotas() {
217-
c.h.RenderJSON(w, http.StatusTooManyRequests, api.Errorf("exceeded realm quota"))
195+
// If we got this far, we're about to issue a code - take from the limiter
196+
// to ensure this is permitted.
197+
if realm.AbusePreventionEnabled {
198+
dig, err := digest.HMACUint(realm.ID, c.config.GetRateLimitConfig().HMACKey)
199+
if err != nil {
200+
controller.InternalError(w, r, c.h, err)
218201
return
219202
}
203+
key := fmt.Sprintf("realm:quota:%s", dig)
204+
limit, remaining, reset, ok, err := c.limiter.Take(ctx, key)
205+
c.recordCapacity(ctx, limit, remaining)
206+
if err != nil {
207+
logger.Errorw("failed to take from limiter", "error", err)
208+
stats.Record(ctx, c.metrics.QuotaErrors.M(1))
209+
c.h.RenderJSON(w, http.StatusInternalServerError, api.Errorf("failed to verify realm stats, please try again"))
210+
return
211+
}
212+
if !ok {
213+
logger.Warnw("realm has exceeded daily quota",
214+
"realm", realm.ID,
215+
"limit", limit,
216+
"reset", reset)
217+
stats.Record(ctx, c.metrics.QuotaExceeded.M(1))
218+
219+
if c.config.GetEnforceRealmQuotas() {
220+
c.h.RenderJSON(w, http.StatusTooManyRequests, api.Errorf("exceeded realm quota"))
221+
return
222+
}
223+
}
220224
}
221225

222226
now := time.Now().UTC()
@@ -251,8 +255,6 @@ func (c *Controller) HandleIssue() http.Handler {
251255
return
252256
}
253257

254-
c.recordCapacity(ctx, realm, remaining)
255-
256258
if request.Phone != "" && smsProvider != nil {
257259
message := realm.BuildSMSText(code, longCode, c.config.GetENXRedirectDomain())
258260
if err := smsProvider.SendSMS(ctx, request.Phone, message); err != nil {
@@ -296,13 +298,9 @@ func (c *Controller) getAuthorizationFromContext(r *http.Request) (*database.Aut
296298
return authorizedApp, currentUser, nil
297299
}
298300

299-
func (c *Controller) recordCapacity(ctx context.Context, realm *database.Realm, remaining uint64) {
300-
if !realm.AbusePreventionEnabled {
301-
return
302-
}
301+
func (c *Controller) recordCapacity(ctx context.Context, limit, remaining uint64) {
303302
stats.Record(ctx, c.metrics.RealmTokenRemaining.M(int64(remaining)))
304303

305-
limit := realm.AbusePreventionEffectiveLimit()
306304
issued := uint64(limit) - remaining
307305
stats.Record(ctx, c.metrics.RealmTokenIssued.M(int64(issued)))
308306

pkg/controller/realmadmin/express.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func (c *Controller) HandleDisableExpress() http.Handler {
4646

4747
if !realm.EnableENExpress {
4848
flash.Error("Realm is not currently enrolled in EN Express.")
49-
c.renderSettings(ctx, w, r, realm, nil)
49+
c.renderSettings(ctx, w, r, realm, nil, 0, 0)
5050
return
5151
}
5252

@@ -56,7 +56,7 @@ func (c *Controller) HandleDisableExpress() http.Handler {
5656
if err := c.db.SaveRealm(realm, currentUser); err != nil {
5757
flash.Error("Failed to disable EN Express: %v", err)
5858

59-
c.renderSettings(ctx, w, r, realm, nil)
59+
c.renderSettings(ctx, w, r, realm, nil, 0, 0)
6060
return
6161
}
6262

@@ -90,7 +90,7 @@ func (c *Controller) HandleEnableExpress() http.Handler {
9090

9191
if realm.EnableENExpress {
9292
flash.Error("Realm already has EN Express Enabled.")
93-
c.renderSettings(ctx, w, r, realm, nil)
93+
c.renderSettings(ctx, w, r, realm, nil, 0, 0)
9494
return
9595
}
9696

@@ -110,7 +110,7 @@ func (c *Controller) HandleEnableExpress() http.Handler {
110110
// This will allow the user to correct other validation errors and then click "uprade" again.
111111
realm.EnableENExpress = false
112112
realm.SMSTextTemplate = enxSettings.SMSTextTemplate
113-
c.renderSettings(ctx, w, r, realm, nil)
113+
c.renderSettings(ctx, w, r, realm, nil, 0, 0)
114114
return
115115
}
116116

pkg/controller/realmadmin/settings.go

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,31 @@ func (c *Controller) HandleSettings() http.Handler {
106106
return
107107
}
108108

109+
var quotaLimit, quotaRemaining uint64
110+
if realm.AbusePreventionEnabled {
111+
dig, err := digest.HMACUint(realm.ID, c.config.RateLimit.HMACKey)
112+
if err != nil {
113+
controller.InternalError(w, r, c.h, err)
114+
return
115+
}
116+
key := fmt.Sprintf("realm:quota:%s", dig)
117+
118+
quotaLimit, quotaRemaining, err = c.limiter.Get(ctx, key)
119+
if err != nil {
120+
controller.InternalError(w, r, c.h, err)
121+
return
122+
}
123+
}
124+
109125
if r.Method == http.MethodGet {
110-
c.renderSettings(ctx, w, r, realm, nil)
126+
c.renderSettings(ctx, w, r, realm, nil, quotaLimit, quotaRemaining)
111127
return
112128
}
113129

114130
var form FormData
115131
if err := controller.BindForm(w, r, &form); err != nil {
116132
flash.Error("Failed to process form: %v", err)
117-
c.renderSettings(ctx, w, r, realm, nil)
133+
c.renderSettings(ctx, w, r, realm, nil, quotaLimit, quotaRemaining)
118134
return
119135
}
120136

@@ -158,7 +174,7 @@ func (c *Controller) HandleSettings() http.Handler {
158174
if err != nil {
159175
realm.AddError("allowedCIDRsAdminAPI", err.Error())
160176
flash.Error("Failed to update realm")
161-
c.renderSettings(ctx, w, r, realm, nil)
177+
c.renderSettings(ctx, w, r, realm, nil, quotaLimit, quotaRemaining)
162178
return
163179
}
164180
realm.AllowedCIDRsAdminAPI = allowedCIDRsAdminADPI
@@ -167,7 +183,7 @@ func (c *Controller) HandleSettings() http.Handler {
167183
if err != nil {
168184
realm.AddError("allowedCIDRsAPIServer", err.Error())
169185
flash.Error("Failed to update realm")
170-
c.renderSettings(ctx, w, r, realm, nil)
186+
c.renderSettings(ctx, w, r, realm, nil, quotaLimit, quotaRemaining)
171187
return
172188
}
173189
realm.AllowedCIDRsAPIServer = allowedCIDRsAPIServer
@@ -176,7 +192,7 @@ func (c *Controller) HandleSettings() http.Handler {
176192
if err != nil {
177193
realm.AddError("allowedCIDRsServer", err.Error())
178194
flash.Error("Failed to update realm")
179-
c.renderSettings(ctx, w, r, realm, nil)
195+
c.renderSettings(ctx, w, r, realm, nil, quotaLimit, quotaRemaining)
180196
return
181197
}
182198
realm.AllowedCIDRsServer = allowedCIDRsServer
@@ -191,7 +207,7 @@ func (c *Controller) HandleSettings() http.Handler {
191207
// Save realm
192208
if err := c.db.SaveRealm(realm, currentUser); err != nil {
193209
flash.Error("Failed to update realm: %v", err)
194-
c.renderSettings(ctx, w, r, realm, nil)
210+
c.renderSettings(ctx, w, r, realm, nil, quotaLimit, quotaRemaining)
195211
return
196212
}
197213

@@ -213,7 +229,7 @@ func (c *Controller) HandleSettings() http.Handler {
213229

214230
if err := c.db.SaveSMSConfig(smsConfig); err != nil {
215231
flash.Error("Failed to update realm: %v", err)
216-
c.renderSettings(ctx, w, r, realm, smsConfig)
232+
c.renderSettings(ctx, w, r, realm, smsConfig, quotaLimit, quotaRemaining)
217233
return
218234
}
219235
} else {
@@ -229,7 +245,7 @@ func (c *Controller) HandleSettings() http.Handler {
229245

230246
if err := c.db.SaveSMSConfig(smsConfig); err != nil {
231247
flash.Error("Failed to update realm: %v", err)
232-
c.renderSettings(ctx, w, r, realm, smsConfig)
248+
c.renderSettings(ctx, w, r, realm, smsConfig, quotaLimit, quotaRemaining)
233249
return
234250
}
235251
}
@@ -256,7 +272,7 @@ func (c *Controller) HandleSettings() http.Handler {
256272
})
257273
}
258274

259-
func (c *Controller) renderSettings(ctx context.Context, w http.ResponseWriter, r *http.Request, realm *database.Realm, smsConfig *database.SMSConfig) {
275+
func (c *Controller) renderSettings(ctx context.Context, w http.ResponseWriter, r *http.Request, realm *database.Realm, smsConfig *database.SMSConfig, quotaLimit, quotaRemaining uint64) {
260276
if smsConfig == nil {
261277
var err error
262278
smsConfig, err = realm.SMSConfig(c.db)
@@ -309,5 +325,9 @@ func (c *Controller) renderSettings(ctx context.Context, w http.ResponseWriter,
309325
m["longCodeLengths"] = longCodeLengths
310326
m["longCodeHours"] = longCodeHours
311327
m["enxRedirectDomain"] = c.config.GetENXRedirectDomain()
328+
329+
m["quotaLimit"] = quotaLimit
330+
m["quotaRemaining"] = quotaRemaining
331+
312332
c.h.RenderHTML(w, "realmadmin/edit", m)
313333
}

0 commit comments

Comments
 (0)