diff --git a/server/service/async/collect_test.go b/server/service/async/collect_test.go index 47f0d29f5c..bcfebfef8c 100644 --- a/server/service/async/collect_test.go +++ b/server/service/async/collect_test.go @@ -53,7 +53,7 @@ func TestCollector(t *testing.T) { // running at each 10ms ±10% for 100ms, min 9, max 11 but stay on the // safe side, especially for min. - require.GreaterOrEqual(t, countHandler, 5) + require.GreaterOrEqual(t, countHandler, 4) require.LessOrEqual(t, countHandler, 12) stats := coll.ReadStats() diff --git a/server/service/handler.go b/server/service/handler.go index 2efce19341..9e3aff6100 100644 --- a/server/service/handler.go +++ b/server/service/handler.go @@ -219,6 +219,11 @@ func addMetrics(r *mux.Router) { r.Walk(walkFn) } +// desktopRateLimitMaxBurst is the max burst used for device request rate limiting. +// +// Defined as const to be used in tests. +const desktopRateLimitMaxBurst = 100 + func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetConfig, logger kitlog.Logger, limitStore throttled.GCRAStore, opts []kithttp.ServerOption, extra extraHandlerOpts, @@ -401,7 +406,7 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC de := newDeviceAuthenticatedEndpointer(svc, logger, opts, r, apiVersions...) // We allow a quota of 720 because in the onboarding of a Fleet Desktop takes a few tries until it authenticates // properly - desktopQuota := throttled.RateQuota{MaxRate: throttled.PerHour(720), MaxBurst: 100} + desktopQuota := throttled.RateQuota{MaxRate: throttled.PerHour(720), MaxBurst: desktopRateLimitMaxBurst} de.WithCustomMiddleware( errorLimiter.Limit("get_device_host", desktopQuota), ).GET("/api/_version_/fleet/device/{token}", getDeviceHostEndpoint, getDeviceHostRequest{}) diff --git a/server/service/integration_core_test.go b/server/service/integration_core_test.go index 8f3e841e23..23596fbe90 100644 --- a/server/service/integration_core_test.go +++ b/server/service/integration_core_test.go @@ -4610,202 +4610,6 @@ func (s *integrationTestSuite) TestPasswordReset() { res.Body.Close() } -func (s *integrationTestSuite) TestDeviceAuthenticatedEndpoints() { - t := s.T() - - hosts := s.createHosts(t) - ac, err := s.ds.AppConfig(context.Background()) - require.NoError(t, err) - ac.OrgInfo.OrgLogoURL = "http://example.com/logo" - err = s.ds.SaveAppConfig(context.Background(), ac) - require.NoError(t, err) - - // create some mappings and MDM/Munki data - s.ds.ReplaceHostDeviceMapping(context.Background(), hosts[0].ID, []*fleet.HostDeviceMapping{ - {HostID: hosts[0].ID, Email: "a@b.c", Source: "google_chrome_profiles"}, - {HostID: hosts[0].ID, Email: "b@b.c", Source: "google_chrome_profiles"}, - }) - require.NoError(t, s.ds.SetOrUpdateMDMData(context.Background(), hosts[0].ID, true, "url", false)) - require.NoError(t, s.ds.SetOrUpdateMunkiVersion(context.Background(), hosts[0].ID, "1.3.0")) - // create a battery for hosts[0] - require.NoError(t, s.ds.ReplaceHostBatteries(context.Background(), hosts[0].ID, []*fleet.HostBattery{ - {HostID: hosts[0].ID, SerialNumber: "a", CycleCount: 1, Health: "Good"}, - })) - - // create an auth token for hosts[0] - token := "much_valid" - mysql.ExecAdhocSQL(t, s.ds, func(db sqlx.ExtContext) error { - _, err := db.ExecContext(context.Background(), `INSERT INTO host_device_auth (host_id, token) VALUES (?, ?)`, hosts[0].ID, token) - return err - }) - - // get host without token - res := s.DoRawNoAuth("GET", "/api/latest/fleet/device/", nil, http.StatusNotFound) - res.Body.Close() - - // get host with invalid token - res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/no_such_token", nil, http.StatusUnauthorized) - res.Body.Close() - - // get host with valid token - var getHostResp getDeviceHostResponse - res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token, nil, http.StatusOK) - json.NewDecoder(res.Body).Decode(&getHostResp) - res.Body.Close() - require.Equal(t, hosts[0].ID, getHostResp.Host.ID) - require.False(t, getHostResp.Host.RefetchRequested) - require.Equal(t, "http://example.com/logo", getHostResp.OrgLogoURL) - require.Nil(t, getHostResp.Host.Policies) - require.NotNil(t, getHostResp.Host.Batteries) - require.Equal(t, &fleet.HostBattery{CycleCount: 1, Health: "Good"}, (*getHostResp.Host.Batteries)[0]) - hostDevResp := getHostResp.Host - - // make request for same host on the host details API endpoint, responses should match, except for policies - getHostResp = getDeviceHostResponse{} - s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d", hosts[0].ID), nil, http.StatusOK, &getHostResp) - getHostResp.Host.Policies = nil - require.Equal(t, hostDevResp, getHostResp.Host) - - // request a refetch for that valid host - res = s.DoRawNoAuth("POST", "/api/latest/fleet/device/"+token+"/refetch", nil, http.StatusOK) - res.Body.Close() - - // host should have that flag turned to true - getHostResp = getDeviceHostResponse{} - res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token, nil, http.StatusOK) - json.NewDecoder(res.Body).Decode(&getHostResp) - res.Body.Close() - require.True(t, getHostResp.Host.RefetchRequested) - - // request a refetch for an invalid token - res = s.DoRawNoAuth("POST", "/api/latest/fleet/device/no_such_token/refetch", nil, http.StatusUnauthorized) - res.Body.Close() - - // list device mappings for valid token - var listDMResp listHostDeviceMappingResponse - res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/device_mapping", nil, http.StatusOK) - json.NewDecoder(res.Body).Decode(&listDMResp) - res.Body.Close() - require.Equal(t, hosts[0].ID, listDMResp.HostID) - require.Len(t, listDMResp.DeviceMapping, 2) - devDMs := listDMResp.DeviceMapping - - // compare response with standard list device mapping API for that same host - listDMResp = listHostDeviceMappingResponse{} - s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/device_mapping", hosts[0].ID), nil, http.StatusOK, &listDMResp) - require.Equal(t, hosts[0].ID, listDMResp.HostID) - require.Equal(t, devDMs, listDMResp.DeviceMapping) - - // list device mappings for invalid token - res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/no_such_token/device_mapping", nil, http.StatusUnauthorized) - res.Body.Close() - - // get macadmins for valid token - var getMacadm getMacadminsDataResponse - res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/macadmins", nil, http.StatusOK) - json.NewDecoder(res.Body).Decode(&getMacadm) - res.Body.Close() - require.Equal(t, "1.3.0", getMacadm.Macadmins.Munki.Version) - devMacadm := getMacadm.Macadmins - - // compare response with standard macadmins API for that same host - getMacadm = getMacadminsDataResponse{} - s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/macadmins", hosts[0].ID), nil, http.StatusOK, &getMacadm) - require.Equal(t, devMacadm, getMacadm.Macadmins) - - // get macadmins for invalid token - res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/no_such_token/macadmins", nil, http.StatusUnauthorized) - res.Body.Close() - - // response includes license info - getHostResp = getDeviceHostResponse{} - res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token, nil, http.StatusOK) - json.NewDecoder(res.Body).Decode(&getHostResp) - res.Body.Close() - require.NotNil(t, getHostResp.License) - require.Equal(t, getHostResp.License.Tier, "free") - - // device policies are not accessible for free endpoints - listPoliciesResp := listDevicePoliciesResponse{} - res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/policies", nil, http.StatusPaymentRequired) - json.NewDecoder(res.Body).Decode(&getHostResp) - res.Body.Close() - require.Nil(t, listPoliciesResp.Policies) - - // get list of api features - apiFeaturesResp := deviceAPIFeaturesResponse{} - res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/api_features", nil, http.StatusOK) - json.NewDecoder(res.Body).Decode(&apiFeaturesResp) - res.Body.Close() - require.Nil(t, apiFeaturesResp.Err) - require.NotNil(t, apiFeaturesResp.Features) -} - -// TestDefaultTransparencyURL tests that Fleet Free licensees are restricted to the default transparency url. -func (s *integrationTestSuite) TestDefaultTransparencyURL() { - t := s.T() - - host, err := s.ds.NewHost(context.Background(), &fleet.Host{ - DetailUpdatedAt: time.Now(), - LabelUpdatedAt: time.Now(), - PolicyUpdatedAt: time.Now(), - SeenTime: time.Now().Add(-1 * time.Minute), - OsqueryHostID: t.Name(), - NodeKey: t.Name(), - UUID: uuid.New().String(), - Hostname: fmt.Sprintf("%sfoo.local", t.Name()), - Platform: "darwin", - }) - require.NoError(t, err) - - // create device token for host - token := "token_test_default_transparency_url" - mysql.ExecAdhocSQL(t, s.ds, func(db sqlx.ExtContext) error { - _, err := db.ExecContext(context.Background(), `INSERT INTO host_device_auth (host_id, token) VALUES (?, ?)`, host.ID, token) - return err - }) - - // confirm initial default url - acResp := appConfigResponse{} - s.DoJSON("GET", "/api/latest/fleet/config", nil, http.StatusOK, &acResp) - require.NotNil(t, acResp) - require.Equal(t, fleet.DefaultTransparencyURL, acResp.FleetDesktop.TransparencyURL) - - // confirm device endpoint returns initial default url - deviceResp := &transparencyURLResponse{} - rawResp := s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/transparency", nil, http.StatusTemporaryRedirect) - json.NewDecoder(rawResp.Body).Decode(deviceResp) - rawResp.Body.Close() - require.NoError(t, deviceResp.Err) - require.Equal(t, fleet.DefaultTransparencyURL, rawResp.Header.Get("Location")) - - // empty string applies default url - acResp = appConfigResponse{} - s.DoJSON("PATCH", "/api/latest/fleet/config", fleet.AppConfig{FleetDesktop: fleet.FleetDesktopSettings{TransparencyURL: ""}}, http.StatusOK, &acResp) - require.NotNil(t, acResp) - require.Equal(t, fleet.DefaultTransparencyURL, acResp.FleetDesktop.TransparencyURL) - - // device endpoint returns default url - deviceResp = &transparencyURLResponse{} - rawResp = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/transparency", nil, http.StatusTemporaryRedirect) - json.NewDecoder(rawResp.Body).Decode(deviceResp) - rawResp.Body.Close() - require.NoError(t, deviceResp.Err) - require.Equal(t, fleet.DefaultTransparencyURL, rawResp.Header.Get("Location")) - - // modify transparency url with custom url fails - acResp = appConfigResponse{} - s.DoJSON("PATCH", "/api/latest/fleet/config", fleet.AppConfig{FleetDesktop: fleet.FleetDesktopSettings{TransparencyURL: "customURL"}}, http.StatusUnprocessableEntity, &acResp) - - // device endpoint still returns default url - deviceResp = &transparencyURLResponse{} - rawResp = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/transparency", nil, http.StatusTemporaryRedirect) - json.NewDecoder(rawResp.Body).Decode(deviceResp) - rawResp.Body.Close() - require.NoError(t, deviceResp.Err) - require.Equal(t, fleet.DefaultTransparencyURL, rawResp.Header.Get("Location")) -} - func (s *integrationTestSuite) TestModifyUser() { t := s.T() diff --git a/server/service/integration_desktop_test.go b/server/service/integration_desktop_test.go new file mode 100644 index 0000000000..70fc692524 --- /dev/null +++ b/server/service/integration_desktop_test.go @@ -0,0 +1,221 @@ +package service + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/fleetdm/fleet/v4/server/datastore/mysql" + "github.com/fleetdm/fleet/v4/server/fleet" + "github.com/google/uuid" + "github.com/jmoiron/sqlx" + "github.com/stretchr/testify/require" +) + +func (s *integrationTestSuite) TestDeviceAuthenticatedEndpoints() { + t := s.T() + + hosts := s.createHosts(t) + ac, err := s.ds.AppConfig(context.Background()) + require.NoError(t, err) + ac.OrgInfo.OrgLogoURL = "http://example.com/logo" + err = s.ds.SaveAppConfig(context.Background(), ac) + require.NoError(t, err) + + // create some mappings and MDM/Munki data + s.ds.ReplaceHostDeviceMapping(context.Background(), hosts[0].ID, []*fleet.HostDeviceMapping{ + {HostID: hosts[0].ID, Email: "a@b.c", Source: "google_chrome_profiles"}, + {HostID: hosts[0].ID, Email: "b@b.c", Source: "google_chrome_profiles"}, + }) + require.NoError(t, s.ds.SetOrUpdateMDMData(context.Background(), hosts[0].ID, true, "url", false)) + require.NoError(t, s.ds.SetOrUpdateMunkiVersion(context.Background(), hosts[0].ID, "1.3.0")) + // create a battery for hosts[0] + require.NoError(t, s.ds.ReplaceHostBatteries(context.Background(), hosts[0].ID, []*fleet.HostBattery{ + {HostID: hosts[0].ID, SerialNumber: "a", CycleCount: 1, Health: "Good"}, + })) + + // create an auth token for hosts[0] + token := "much_valid" + mysql.ExecAdhocSQL(t, s.ds, func(db sqlx.ExtContext) error { + _, err := db.ExecContext(context.Background(), `INSERT INTO host_device_auth (host_id, token) VALUES (?, ?)`, hosts[0].ID, token) + return err + }) + + // get host without token + res := s.DoRawNoAuth("GET", "/api/latest/fleet/device/", nil, http.StatusNotFound) + res.Body.Close() + + // get host with invalid token + res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/no_such_token", nil, http.StatusUnauthorized) + res.Body.Close() + + // get host with valid token + var getHostResp getDeviceHostResponse + res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token, nil, http.StatusOK) + json.NewDecoder(res.Body).Decode(&getHostResp) + res.Body.Close() + require.Equal(t, hosts[0].ID, getHostResp.Host.ID) + require.False(t, getHostResp.Host.RefetchRequested) + require.Equal(t, "http://example.com/logo", getHostResp.OrgLogoURL) + require.Nil(t, getHostResp.Host.Policies) + require.NotNil(t, getHostResp.Host.Batteries) + require.Equal(t, &fleet.HostBattery{CycleCount: 1, Health: "Good"}, (*getHostResp.Host.Batteries)[0]) + hostDevResp := getHostResp.Host + + // make request for same host on the host details API endpoint, responses should match, except for policies + getHostResp = getDeviceHostResponse{} + s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d", hosts[0].ID), nil, http.StatusOK, &getHostResp) + getHostResp.Host.Policies = nil + require.Equal(t, hostDevResp, getHostResp.Host) + + // request a refetch for that valid host + res = s.DoRawNoAuth("POST", "/api/latest/fleet/device/"+token+"/refetch", nil, http.StatusOK) + res.Body.Close() + + // host should have that flag turned to true + getHostResp = getDeviceHostResponse{} + res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token, nil, http.StatusOK) + json.NewDecoder(res.Body).Decode(&getHostResp) + res.Body.Close() + require.True(t, getHostResp.Host.RefetchRequested) + + // request a refetch for an invalid token + res = s.DoRawNoAuth("POST", "/api/latest/fleet/device/no_such_token/refetch", nil, http.StatusUnauthorized) + res.Body.Close() + + // list device mappings for valid token + var listDMResp listHostDeviceMappingResponse + res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/device_mapping", nil, http.StatusOK) + json.NewDecoder(res.Body).Decode(&listDMResp) + res.Body.Close() + require.Equal(t, hosts[0].ID, listDMResp.HostID) + require.Len(t, listDMResp.DeviceMapping, 2) + devDMs := listDMResp.DeviceMapping + + // compare response with standard list device mapping API for that same host + listDMResp = listHostDeviceMappingResponse{} + s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/device_mapping", hosts[0].ID), nil, http.StatusOK, &listDMResp) + require.Equal(t, hosts[0].ID, listDMResp.HostID) + require.Equal(t, devDMs, listDMResp.DeviceMapping) + + // list device mappings for invalid token + res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/no_such_token/device_mapping", nil, http.StatusUnauthorized) + res.Body.Close() + + // get macadmins for valid token + var getMacadm getMacadminsDataResponse + res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/macadmins", nil, http.StatusOK) + json.NewDecoder(res.Body).Decode(&getMacadm) + res.Body.Close() + require.Equal(t, "1.3.0", getMacadm.Macadmins.Munki.Version) + devMacadm := getMacadm.Macadmins + + // compare response with standard macadmins API for that same host + getMacadm = getMacadminsDataResponse{} + s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/macadmins", hosts[0].ID), nil, http.StatusOK, &getMacadm) + require.Equal(t, devMacadm, getMacadm.Macadmins) + + // get macadmins for invalid token + res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/no_such_token/macadmins", nil, http.StatusUnauthorized) + res.Body.Close() + + // response includes license info + getHostResp = getDeviceHostResponse{} + res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token, nil, http.StatusOK) + json.NewDecoder(res.Body).Decode(&getHostResp) + res.Body.Close() + require.NotNil(t, getHostResp.License) + require.Equal(t, getHostResp.License.Tier, "free") + + // device policies are not accessible for free endpoints + listPoliciesResp := listDevicePoliciesResponse{} + res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/policies", nil, http.StatusPaymentRequired) + json.NewDecoder(res.Body).Decode(&getHostResp) + res.Body.Close() + require.Nil(t, listPoliciesResp.Policies) + + // get list of api features + apiFeaturesResp := deviceAPIFeaturesResponse{} + res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/api_features", nil, http.StatusOK) + json.NewDecoder(res.Body).Decode(&apiFeaturesResp) + res.Body.Close() + require.Nil(t, apiFeaturesResp.Err) + require.NotNil(t, apiFeaturesResp.Features) +} + +// TestDefaultTransparencyURL tests that Fleet Free licensees are restricted to the default transparency url. +func (s *integrationTestSuite) TestDefaultTransparencyURL() { + t := s.T() + + host, err := s.ds.NewHost(context.Background(), &fleet.Host{ + DetailUpdatedAt: time.Now(), + LabelUpdatedAt: time.Now(), + PolicyUpdatedAt: time.Now(), + SeenTime: time.Now().Add(-1 * time.Minute), + OsqueryHostID: t.Name(), + NodeKey: t.Name(), + UUID: uuid.New().String(), + Hostname: fmt.Sprintf("%sfoo.local", t.Name()), + Platform: "darwin", + }) + require.NoError(t, err) + + // create device token for host + token := "token_test_default_transparency_url" + mysql.ExecAdhocSQL(t, s.ds, func(db sqlx.ExtContext) error { + _, err := db.ExecContext(context.Background(), `INSERT INTO host_device_auth (host_id, token) VALUES (?, ?)`, host.ID, token) + return err + }) + + // confirm initial default url + acResp := appConfigResponse{} + s.DoJSON("GET", "/api/latest/fleet/config", nil, http.StatusOK, &acResp) + require.NotNil(t, acResp) + require.Equal(t, fleet.DefaultTransparencyURL, acResp.FleetDesktop.TransparencyURL) + + // confirm device endpoint returns initial default url + deviceResp := &transparencyURLResponse{} + rawResp := s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/transparency", nil, http.StatusTemporaryRedirect) + json.NewDecoder(rawResp.Body).Decode(deviceResp) + rawResp.Body.Close() + require.NoError(t, deviceResp.Err) + require.Equal(t, fleet.DefaultTransparencyURL, rawResp.Header.Get("Location")) + + // empty string applies default url + acResp = appConfigResponse{} + s.DoJSON("PATCH", "/api/latest/fleet/config", fleet.AppConfig{FleetDesktop: fleet.FleetDesktopSettings{TransparencyURL: ""}}, http.StatusOK, &acResp) + require.NotNil(t, acResp) + require.Equal(t, fleet.DefaultTransparencyURL, acResp.FleetDesktop.TransparencyURL) + + // device endpoint returns default url + deviceResp = &transparencyURLResponse{} + rawResp = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/transparency", nil, http.StatusTemporaryRedirect) + json.NewDecoder(rawResp.Body).Decode(deviceResp) + rawResp.Body.Close() + require.NoError(t, deviceResp.Err) + require.Equal(t, fleet.DefaultTransparencyURL, rawResp.Header.Get("Location")) + + // modify transparency url with custom url fails + acResp = appConfigResponse{} + s.DoJSON("PATCH", "/api/latest/fleet/config", fleet.AppConfig{FleetDesktop: fleet.FleetDesktopSettings{TransparencyURL: "customURL"}}, http.StatusUnprocessableEntity, &acResp) + + // device endpoint still returns default url + deviceResp = &transparencyURLResponse{} + rawResp = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/transparency", nil, http.StatusTemporaryRedirect) + json.NewDecoder(rawResp.Body).Decode(deviceResp) + rawResp.Body.Close() + require.NoError(t, deviceResp.Err) + require.Equal(t, fleet.DefaultTransparencyURL, rawResp.Header.Get("Location")) +} + +func (s *integrationTestSuite) TestDesktopRateLimit() { + headers := map[string]string{ + "X-Forwarded-For": "1.2.3.4", + } + for i := 0; i < desktopRateLimitMaxBurst+1; i++ { // rate limiting off-by-one + s.DoRawWithHeaders("GET", "/api/latest/fleet/device/"+uuid.NewString(), nil, http.StatusUnauthorized, headers).Body.Close() + } + s.DoRawWithHeaders("GET", "/api/latest/fleet/device/"+uuid.NewString(), nil, http.StatusTooManyRequests, headers).Body.Close() +} diff --git a/server/service/middleware/ratelimit/ratelimit.go b/server/service/middleware/ratelimit/ratelimit.go index 4c50a8db5e..dec3fc5a60 100644 --- a/server/service/middleware/ratelimit/ratelimit.go +++ b/server/service/middleware/ratelimit/ratelimit.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" + authz_ctx "github.com/fleetdm/fleet/v4/server/contexts/authz" "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" "github.com/go-kit/kit/endpoint" kithttp "github.com/go-kit/kit/transport/http" @@ -80,6 +81,10 @@ func (m *ErrorMiddleware) Limit(keyName string, quota throttled.RateQuota) endpo return nil, ctxerr.Wrap(ctx, err, "check rate limit") } if result.Remaining == 0 { + // We need to set authentication as checked, otherwise we end up returning HTTP 500 errors. + if az, ok := authz_ctx.FromContext(ctx); ok { + az.SetChecked() + } return nil, ctxerr.Wrap(ctx, &ratelimitError{result: result}) }