diff options
Diffstat (limited to 'internal/api/security')
-rw-r--r-- | internal/api/security/extraheaders.go | 26 | ||||
-rw-r--r-- | internal/api/security/flocblock.go | 31 | ||||
-rw-r--r-- | internal/api/security/ratelimit.go | 70 | ||||
-rw-r--r-- | internal/api/security/robots.go | 57 | ||||
-rw-r--r-- | internal/api/security/security.go | 65 | ||||
-rw-r--r-- | internal/api/security/signaturecheck.go | 51 | ||||
-rw-r--r-- | internal/api/security/tokencheck.go | 120 | ||||
-rw-r--r-- | internal/api/security/useragentblock.go | 35 |
8 files changed, 0 insertions, 455 deletions
diff --git a/internal/api/security/extraheaders.go b/internal/api/security/extraheaders.go deleted file mode 100644 index f66ac43b4..000000000 --- a/internal/api/security/extraheaders.go +++ /dev/null @@ -1,26 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -package security - -import "github.com/gin-gonic/gin" - -// ExtraHeaders adds any additional required headers to the response -func (m *Module) ExtraHeaders(c *gin.Context) { - c.Header("Server", "gotosocial") -} diff --git a/internal/api/security/flocblock.go b/internal/api/security/flocblock.go deleted file mode 100644 index 0b61f4ef5..000000000 --- a/internal/api/security/flocblock.go +++ /dev/null @@ -1,31 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -package security - -import "github.com/gin-gonic/gin" - -// FlocBlock is a middleware that prevents google chrome cohort tracking by -// writing the Permissions-Policy header after all other parts of the request -// have been completed. Floc was replaced by Topics in 2022 and the spec says -// that interest-cohort will also block Topics (as of 2022-Nov). -// See: https://smartframe.io/blog/google-topics-api-everything-you-need-to-know -// See: https://github.com/patcg-individual-drafts/topics -func (m *Module) FlocBlock(c *gin.Context) { - c.Header("Permissions-Policy", "browsing-topics=()") -} diff --git a/internal/api/security/ratelimit.go b/internal/api/security/ratelimit.go deleted file mode 100644 index 3c0d078c7..000000000 --- a/internal/api/security/ratelimit.go +++ /dev/null @@ -1,70 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -package security - -import ( - "net" - "net/http" - "time" - - "github.com/gin-gonic/gin" - limiter "github.com/ulule/limiter/v3" - mgin "github.com/ulule/limiter/v3/drivers/middleware/gin" - memory "github.com/ulule/limiter/v3/drivers/store/memory" -) - -type RateLimitOptions struct { - Period time.Duration - Limit int64 -} - -func (m *Module) LimitReachedHandler(c *gin.Context) { - code := http.StatusTooManyRequests - c.AbortWithStatusJSON(code, gin.H{"error": "rate limit reached"}) -} - -// returns a gin middleware that will automatically rate limit caller (by IP address) -// and enrich the response header with the following headers: -// - `x-ratelimit-limit` maximum number of requests allowed per time period (fixed) -// - `x-ratelimit-remaining` number of remaining requests that can still be performed -// - `x-ratelimit-reset` unix timestamp when the rate limit will reset -// if `x-ratelimit-limit` is exceeded an HTTP 429 error is returned -func (m *Module) RateLimit(rateOptions RateLimitOptions) func(c *gin.Context) { - rate := limiter.Rate{ - Period: rateOptions.Period, - Limit: rateOptions.Limit, - } - - store := memory.NewStore() - - limiterInstance := limiter.New( - store, - rate, - // apply /64 mask to IPv6 addresses - limiter.WithIPv6Mask(net.CIDRMask(64, 128)), - ) - - middleware := mgin.NewMiddleware( - limiterInstance, - // use custom rate limit reached error - mgin.WithLimitReachedHandler(m.LimitReachedHandler), - ) - - return middleware -} diff --git a/internal/api/security/robots.go b/internal/api/security/robots.go deleted file mode 100644 index 5b8ba3c05..000000000 --- a/internal/api/security/robots.go +++ /dev/null @@ -1,57 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -package security - -import ( - "net/http" - - "github.com/gin-gonic/gin" -) - -const robotsString = `User-agent: * -Crawl-delay: 500 -# api stuff -Disallow: /api/ -# auth/login stuff -Disallow: /auth/ -Disallow: /oauth/ -Disallow: /check_your_email -Disallow: /wait_for_approval -Disallow: /account_disabled -# well known stuff -Disallow: /.well-known/ -# files -Disallow: /fileserver/ -# s2s AP stuff -Disallow: /users/ -Disallow: /emoji/ -# panels -Disallow: /admin -Disallow: /user -Disallow: /settings/ -` - -// RobotsGETHandler returns a decent robots.txt that prevents crawling -// the api, auth pages, settings pages, etc. -// -// More granular robots meta tags are then applied for web pages -// depending on user preferences (see internal/web). -func (m *Module) RobotsGETHandler(c *gin.Context) { - c.String(http.StatusOK, robotsString) -} diff --git a/internal/api/security/security.go b/internal/api/security/security.go deleted file mode 100644 index 1dce111d3..000000000 --- a/internal/api/security/security.go +++ /dev/null @@ -1,65 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -package security - -import ( - "net/http" - "time" - - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/config" - "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/internal/router" -) - -const robotsPath = "/robots.txt" - -// Module implements the ClientAPIModule interface for security middleware -type Module struct { - db db.DB - server oauth.Server -} - -// New returns a new security module -func New(db db.DB, server oauth.Server) api.ClientModule { - return &Module{ - db: db, - server: server, - } -} - -// Route attaches security middleware to the given router -func (m *Module) Route(s router.Router) error { - // only enable rate limit middleware if configured - // advanced-rate-limit-requests is greater than 0 - if rateLimitRequests := config.GetAdvancedRateLimitRequests(); rateLimitRequests > 0 { - s.AttachMiddleware(m.RateLimit(RateLimitOptions{ - Period: 5 * time.Minute, - Limit: int64(rateLimitRequests), - })) - } - s.AttachMiddleware(m.SignatureCheck) - s.AttachMiddleware(m.FlocBlock) - s.AttachMiddleware(m.ExtraHeaders) - s.AttachMiddleware(m.UserAgentBlock) - s.AttachMiddleware(m.TokenCheck) - s.AttachHandler(http.MethodGet, robotsPath, m.RobotsGETHandler) - return nil -} diff --git a/internal/api/security/signaturecheck.go b/internal/api/security/signaturecheck.go deleted file mode 100644 index 1c117cd1b..000000000 --- a/internal/api/security/signaturecheck.go +++ /dev/null @@ -1,51 +0,0 @@ -package security - -import ( - "net/http" - "net/url" - - "github.com/superseriousbusiness/gotosocial/internal/ap" - "github.com/superseriousbusiness/gotosocial/internal/log" - - "github.com/gin-gonic/gin" - "github.com/go-fed/httpsig" -) - -// SignatureCheck checks whether an incoming http request has been signed. If so, it will check if the domain -// that signed the request is permitted to access the server. If it is permitted, the handler will set the key -// verifier and the signature in the gin context for use down the line. -func (m *Module) SignatureCheck(c *gin.Context) { - // create the verifier from the request - // if the request is signed, it will have a signature header - verifier, err := httpsig.NewVerifier(c.Request) - if err == nil { - // the request was signed! - - // The key ID should be given in the signature so that we know where to fetch it from the remote server. - // This will be something like https://example.org/users/whatever_requesting_user#main-key - requestingPublicKeyID, err := url.Parse(verifier.KeyId()) - if err == nil && requestingPublicKeyID != nil { - // we managed to parse the url! - - // if the domain is blocked we want to bail as early as possible - blocked, err := m.db.IsURIBlocked(c.Request.Context(), requestingPublicKeyID) - if err != nil { - log.Errorf("could not tell if domain %s was blocked or not: %s", requestingPublicKeyID.Host, err) - c.AbortWithStatus(http.StatusInternalServerError) - return - } - if blocked { - log.Infof("domain %s is blocked", requestingPublicKeyID.Host) - c.AbortWithStatus(http.StatusForbidden) - return - } - - // set the verifier and signature on the context here to save some work further down the line - c.Set(string(ap.ContextRequestingPublicKeyVerifier), verifier) - signature := c.GetHeader("Signature") - if signature != "" { - c.Set(string(ap.ContextRequestingPublicKeySignature), signature) - } - } - } -} diff --git a/internal/api/security/tokencheck.go b/internal/api/security/tokencheck.go deleted file mode 100644 index 9f2b7f36e..000000000 --- a/internal/api/security/tokencheck.go +++ /dev/null @@ -1,120 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -package security - -import ( - "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/log" - "github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -// TokenCheck checks if the client has presented a valid oauth Bearer token. -// If so, it will check the User that the token belongs to, and set that in the context of -// the request. Then, it will look up the account for that user, and set that in the request too. -// If user or account can't be found, then the handler won't *fail*, in case the server wants to allow -// public requests that don't have a Bearer token set (eg., for public instance information and so on). -func (m *Module) TokenCheck(c *gin.Context) { - ctx := c.Request.Context() - defer c.Next() - - if c.Request.Header.Get("Authorization") == "" { - // no token set in the header, we can just bail - return - } - - ti, err := m.server.ValidationBearerToken(c.Copy().Request) - if err != nil { - log.Infof("token was passed in Authorization header but we could not validate it: %s", err) - return - } - c.Set(oauth.SessionAuthorizedToken, ti) - - // check for user-level token - if userID := ti.GetUserID(); userID != "" { - log.Tracef("authenticated user %s with bearer token, scope is %s", userID, ti.GetScope()) - - // fetch user for this token - user, err := m.db.GetUserByID(ctx, userID) - if err != nil { - if err != db.ErrNoEntries { - log.Errorf("database error looking for user with id %s: %s", userID, err) - return - } - log.Warnf("no user found for userID %s", userID) - return - } - - if user.ConfirmedAt.IsZero() { - log.Warnf("authenticated user %s has never confirmed thier email address", userID) - return - } - - if !*user.Approved { - log.Warnf("authenticated user %s's account was never approved by an admin", userID) - return - } - - if *user.Disabled { - log.Warnf("authenticated user %s's account was disabled'", userID) - return - } - - c.Set(oauth.SessionAuthorizedUser, user) - - // fetch account for this token - if user.Account == nil { - acct, err := m.db.GetAccountByID(ctx, user.AccountID) - if err != nil { - if err != db.ErrNoEntries { - log.Errorf("database error looking for account with id %s: %s", user.AccountID, err) - return - } - log.Warnf("no account found for userID %s", userID) - return - } - user.Account = acct - } - - if !user.Account.SuspendedAt.IsZero() { - log.Warnf("authenticated user %s's account (accountId=%s) has been suspended", userID, user.AccountID) - return - } - - c.Set(oauth.SessionAuthorizedAccount, user.Account) - } - - // check for application token - if clientID := ti.GetClientID(); clientID != "" { - log.Tracef("authenticated client %s with bearer token, scope is %s", clientID, ti.GetScope()) - - // fetch app for this token - app := >smodel.Application{} - if err := m.db.GetWhere(ctx, []db.Where{{Key: "client_id", Value: clientID}}, app); err != nil { - if err != db.ErrNoEntries { - log.Errorf("database error looking for application with clientID %s: %s", clientID, err) - return - } - log.Warnf("no app found for client %s", clientID) - return - } - c.Set(oauth.SessionAuthorizedApplication, app) - } -} diff --git a/internal/api/security/useragentblock.go b/internal/api/security/useragentblock.go deleted file mode 100644 index b117e8608..000000000 --- a/internal/api/security/useragentblock.go +++ /dev/null @@ -1,35 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -package security - -import ( - "errors" - "net/http" - - "github.com/gin-gonic/gin" -) - -// UserAgentBlock aborts requests with empty user agent strings. -func (m *Module) UserAgentBlock(c *gin.Context) { - if ua := c.Request.UserAgent(); ua == "" { - code := http.StatusTeapot - err := errors.New(http.StatusText(code) + ": no user-agent sent with request") - c.AbortWithStatusJSON(code, gin.H{"error": err.Error()}) - } -} |