Deprecate query string auth tokens (#28390) (#28430)

Backport #28390 by @jackHay22

## Changes
- Add deprecation warning to `Token` and `AccessToken` authentication
methods in swagger.
- Add deprecation warning header to API response. Example: 
  ```
  HTTP/1.1 200 OK
  ...
  Warning: token and access_token API authentication is deprecated
  ...
  ```
- Add setting `DISABLE_QUERY_AUTH_TOKEN` to reject query string auth
tokens entirely. Default is `false`

## Next steps
- `DISABLE_QUERY_AUTH_TOKEN` should be true in a subsequent release and
the methods should be removed in swagger
- `DISABLE_QUERY_AUTH_TOKEN` should be removed and the implementation of
the auth methods in question should be removed

## Open questions
- Should there be further changes to the swagger documentation?
Deprecation is not yet supported for security definitions (coming in
[OpenAPI Spec version
3.2.0](https://github.com/OAI/OpenAPI-Specification/issues/2506))
- Should the API router logger sanitize urls that use `token` or
`access_token`? (This is obviously an insufficient solution on its own)

Co-authored-by: Jack Hay <jack@allspice.io>
Co-authored-by: delvh <dev.lh@web.de>
This commit is contained in:
Giteabot 2023-12-12 13:45:00 +08:00 committed by GitHub
parent 6f4d5c0b8c
commit f144521aea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 39 additions and 7 deletions

View File

@ -491,6 +491,11 @@ INTERNAL_TOKEN=
;; Cache successful token hashes. API tokens are stored in the DB as pbkdf2 hashes however, this means that there is a potentially significant hashing load when there are multiple API operations. ;; Cache successful token hashes. API tokens are stored in the DB as pbkdf2 hashes however, this means that there is a potentially significant hashing load when there are multiple API operations.
;; This cache will store the successfully hashed tokens in a LRU cache as a balance between performance and security. ;; This cache will store the successfully hashed tokens in a LRU cache as a balance between performance and security.
;SUCCESSFUL_TOKENS_CACHE_SIZE = 20 ;SUCCESSFUL_TOKENS_CACHE_SIZE = 20
;;
;; Reject API tokens sent in URL query string (Accept Header-based API tokens only). This avoids security vulnerabilities
;; stemming from cached/logged plain-text API tokens.
;; In future releases, this will become the default behavior
;DISABLE_QUERY_AUTH_TOKEN = false
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@ -35,6 +35,7 @@ var (
PasswordHashAlgo string PasswordHashAlgo string
PasswordCheckPwn bool PasswordCheckPwn bool
SuccessfulTokensCacheSize int SuccessfulTokensCacheSize int
DisableQueryAuthToken bool
CSRFCookieName = "_csrf" CSRFCookieName = "_csrf"
CSRFCookieHTTPOnly = true CSRFCookieHTTPOnly = true
) )
@ -159,4 +160,11 @@ func loadSecurityFrom(rootCfg ConfigProvider) {
PasswordComplexity = append(PasswordComplexity, name) PasswordComplexity = append(PasswordComplexity, name)
} }
} }
// TODO: default value should be true in future releases
DisableQueryAuthToken = sec.Key("DISABLE_QUERY_AUTH_TOKEN").MustBool(false)
if !DisableQueryAuthToken {
log.Warn("Enabling Query API Auth tokens is not recommended. DISABLE_QUERY_AUTH_TOKEN will default to true in gitea 1.23 and will be removed in gitea 1.24.")
}
} }

View File

@ -35,10 +35,12 @@
// type: apiKey // type: apiKey
// name: token // name: token
// in: query // in: query
// description: This authentication option is deprecated for removal in Gitea 1.23. Please use AuthorizationHeaderToken instead.
// AccessToken: // AccessToken:
// type: apiKey // type: apiKey
// name: access_token // name: access_token
// in: query // in: query
// description: This authentication option is deprecated for removal in Gitea 1.23. Please use AuthorizationHeaderToken instead.
// AuthorizationHeaderToken: // AuthorizationHeaderToken:
// type: apiKey // type: apiKey
// name: Authorization // name: Authorization
@ -787,6 +789,13 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.APIC
} }
} }
// check for and warn against deprecated authentication options
func checkDeprecatedAuthMethods(ctx *context.APIContext) {
if ctx.FormString("token") != "" || ctx.FormString("access_token") != "" {
ctx.Resp.Header().Set("Warning", "token and access_token API authentication is deprecated and will be removed in gitea 1.23. Please use AuthorizationHeaderToken instead. Existing queries will continue to work but without authorization.")
}
}
// Routes registers all v1 APIs routes to web application. // Routes registers all v1 APIs routes to web application.
func Routes() *web.Route { func Routes() *web.Route {
m := web.NewRoute() m := web.NewRoute()
@ -805,6 +814,8 @@ func Routes() *web.Route {
} }
m.Use(context.APIContexter()) m.Use(context.APIContexter())
m.Use(checkDeprecatedAuthMethods)
// Get user from session if logged in. // Get user from session if logged in.
m.Use(apiAuth(buildAuthGroup())) m.Use(apiAuth(buildAuthGroup()))

View File

@ -14,6 +14,7 @@ import (
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/services/auth/source/oauth2" "code.gitea.io/gitea/services/auth/source/oauth2"
@ -62,14 +63,19 @@ func (o *OAuth2) Name() string {
// representing whether the token exists or not // representing whether the token exists or not
func parseToken(req *http.Request) (string, bool) { func parseToken(req *http.Request) (string, bool) {
_ = req.ParseForm() _ = req.ParseForm()
// Check token. if !setting.DisableQueryAuthToken {
if token := req.Form.Get("token"); token != "" { // Check token.
return token, true if token := req.Form.Get("token"); token != "" {
} return token, true
// Check access token. }
if token := req.Form.Get("access_token"); token != "" { // Check access token.
return token, true if token := req.Form.Get("access_token"); token != "" {
return token, true
}
} else if req.Form.Get("token") != "" || req.Form.Get("access_token") != "" {
log.Warn("API token sent in query string but DISABLE_QUERY_AUTH_TOKEN=true")
} }
// check header token // check header token
if auHead := req.Header.Get("Authorization"); auHead != "" { if auHead := req.Header.Get("Authorization"); auHead != "" {
auths := strings.Fields(auHead) auths := strings.Fields(auHead)

View File

@ -23955,6 +23955,7 @@
}, },
"securityDefinitions": { "securityDefinitions": {
"AccessToken": { "AccessToken": {
"description": "This authentication option is deprecated for removal in Gitea 1.23. Please use AuthorizationHeaderToken instead.",
"type": "apiKey", "type": "apiKey",
"name": "access_token", "name": "access_token",
"in": "query" "in": "query"
@ -23987,6 +23988,7 @@
"in": "header" "in": "header"
}, },
"Token": { "Token": {
"description": "This authentication option is deprecated for removal in Gitea 1.23. Please use AuthorizationHeaderToken instead.",
"type": "apiKey", "type": "apiKey",
"name": "token", "name": "token",
"in": "query" "in": "query"