Allow Protected Branches to Whitelist Deploy Keys (#8483)
Add an option to protected branches to add writing deploy keys to the whitelist for pushing. Please note this is technically a breaking change: previously if the owner of a repository was on the whitelist then any writing deploy key was effectively on the whitelist. This option will now need to be set if that is desired. Closes #8472 Details: * Allow Protected Branches to Whitelist Deploy Keys * Add migration * Ensure that IsDeployKey is set to false on the http pushes * add not null default false
This commit is contained in:
		
							parent
							
								
									b1c1e1549b
								
							
						
					
					
						commit
						0bfe5eb10b
					
				
					 13 changed files with 48 additions and 2 deletions
				
			
		|  | @ -66,6 +66,7 @@ func runHookPreReceive(c *cli.Context) error { | ||||||
| 	reponame := os.Getenv(models.EnvRepoName) | 	reponame := os.Getenv(models.EnvRepoName) | ||||||
| 	userID, _ := strconv.ParseInt(os.Getenv(models.EnvPusherID), 10, 64) | 	userID, _ := strconv.ParseInt(os.Getenv(models.EnvPusherID), 10, 64) | ||||||
| 	prID, _ := strconv.ParseInt(os.Getenv(models.ProtectedBranchPRID), 10, 64) | 	prID, _ := strconv.ParseInt(os.Getenv(models.ProtectedBranchPRID), 10, 64) | ||||||
|  | 	isDeployKey, _ := strconv.ParseBool(os.Getenv(models.EnvIsDeployKey)) | ||||||
| 
 | 
 | ||||||
| 	buf := bytes.NewBuffer(nil) | 	buf := bytes.NewBuffer(nil) | ||||||
| 	scanner := bufio.NewScanner(os.Stdin) | 	scanner := bufio.NewScanner(os.Stdin) | ||||||
|  | @ -98,6 +99,7 @@ func runHookPreReceive(c *cli.Context) error { | ||||||
| 				GitObjectDirectory:              os.Getenv(private.GitObjectDirectory), | 				GitObjectDirectory:              os.Getenv(private.GitObjectDirectory), | ||||||
| 				GitQuarantinePath:               os.Getenv(private.GitQuarantinePath), | 				GitQuarantinePath:               os.Getenv(private.GitQuarantinePath), | ||||||
| 				ProtectedBranchID:               prID, | 				ProtectedBranchID:               prID, | ||||||
|  | 				IsDeployKey:                     isDeployKey, | ||||||
| 			}) | 			}) | ||||||
| 			switch statusCode { | 			switch statusCode { | ||||||
| 			case http.StatusInternalServerError: | 			case http.StatusInternalServerError: | ||||||
|  |  | ||||||
|  | @ -191,6 +191,8 @@ func runServ(c *cli.Context) error { | ||||||
| 	os.Setenv(models.EnvPusherID, strconv.FormatInt(results.UserID, 10)) | 	os.Setenv(models.EnvPusherID, strconv.FormatInt(results.UserID, 10)) | ||||||
| 	os.Setenv(models.ProtectedBranchRepoID, strconv.FormatInt(results.RepoID, 10)) | 	os.Setenv(models.ProtectedBranchRepoID, strconv.FormatInt(results.RepoID, 10)) | ||||||
| 	os.Setenv(models.ProtectedBranchPRID, fmt.Sprintf("%d", 0)) | 	os.Setenv(models.ProtectedBranchPRID, fmt.Sprintf("%d", 0)) | ||||||
|  | 	os.Setenv(models.EnvIsDeployKey, fmt.Sprintf("%t", results.IsDeployKey)) | ||||||
|  | 	os.Setenv(models.EnvKeyID, fmt.Sprintf("%d", results.KeyID)) | ||||||
| 
 | 
 | ||||||
| 	//LFS token authentication
 | 	//LFS token authentication
 | ||||||
| 	if verb == lfsAuthenticateVerb { | 	if verb == lfsAuthenticateVerb { | ||||||
|  |  | ||||||
|  | @ -34,6 +34,7 @@ type ProtectedBranch struct { | ||||||
| 	WhitelistUserIDs          []int64            `xorm:"JSON TEXT"` | 	WhitelistUserIDs          []int64            `xorm:"JSON TEXT"` | ||||||
| 	WhitelistTeamIDs          []int64            `xorm:"JSON TEXT"` | 	WhitelistTeamIDs          []int64            `xorm:"JSON TEXT"` | ||||||
| 	EnableMergeWhitelist      bool               `xorm:"NOT NULL DEFAULT false"` | 	EnableMergeWhitelist      bool               `xorm:"NOT NULL DEFAULT false"` | ||||||
|  | 	WhitelistDeployKeys       bool               `xorm:"NOT NULL DEFAULT false"` | ||||||
| 	MergeWhitelistUserIDs     []int64            `xorm:"JSON TEXT"` | 	MergeWhitelistUserIDs     []int64            `xorm:"JSON TEXT"` | ||||||
| 	MergeWhitelistTeamIDs     []int64            `xorm:"JSON TEXT"` | 	MergeWhitelistTeamIDs     []int64            `xorm:"JSON TEXT"` | ||||||
| 	EnableStatusCheck         bool               `xorm:"NOT NULL DEFAULT false"` | 	EnableStatusCheck         bool               `xorm:"NOT NULL DEFAULT false"` | ||||||
|  |  | ||||||
|  | @ -260,6 +260,8 @@ var migrations = []Migration{ | ||||||
| 	NewMigration("change length of some external login users columns", changeSomeColumnsLengthOfExternalLoginUser), | 	NewMigration("change length of some external login users columns", changeSomeColumnsLengthOfExternalLoginUser), | ||||||
| 	// v102 -> v103
 | 	// v102 -> v103
 | ||||||
| 	NewMigration("update migration repositories' service type", dropColumnHeadUserNameOnPullRequest), | 	NewMigration("update migration repositories' service type", dropColumnHeadUserNameOnPullRequest), | ||||||
|  | 	// v103 -> v104
 | ||||||
|  | 	NewMigration("Add WhitelistDeployKeys to protected branch", addWhitelistDeployKeysToBranches), | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Migrate database to current version
 | // Migrate database to current version
 | ||||||
|  |  | ||||||
							
								
								
									
										18
									
								
								models/migrations/v103.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								models/migrations/v103.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | ||||||
|  | // Copyright 2019 The Gitea Authors. All rights reserved.
 | ||||||
|  | // Use of this source code is governed by a MIT-style
 | ||||||
|  | // license that can be found in the LICENSE file.
 | ||||||
|  | 
 | ||||||
|  | package migrations | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"xorm.io/xorm" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func addWhitelistDeployKeysToBranches(x *xorm.Engine) error { | ||||||
|  | 	type ProtectedBranch struct { | ||||||
|  | 		ID                  int64 | ||||||
|  | 		WhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false"` | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return x.Sync2(new(ProtectedBranch)) | ||||||
|  | } | ||||||
|  | @ -22,6 +22,8 @@ const ( | ||||||
| 	EnvPusherName   = "GITEA_PUSHER_NAME" | 	EnvPusherName   = "GITEA_PUSHER_NAME" | ||||||
| 	EnvPusherEmail  = "GITEA_PUSHER_EMAIL" | 	EnvPusherEmail  = "GITEA_PUSHER_EMAIL" | ||||||
| 	EnvPusherID     = "GITEA_PUSHER_ID" | 	EnvPusherID     = "GITEA_PUSHER_ID" | ||||||
|  | 	EnvKeyID        = "GITEA_KEY_ID" | ||||||
|  | 	EnvIsDeployKey  = "GITEA_IS_DEPLOY_KEY" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // CommitToPushCommit transforms a git.Commit to PushCommit type.
 | // CommitToPushCommit transforms a git.Commit to PushCommit type.
 | ||||||
|  |  | ||||||
|  | @ -152,6 +152,7 @@ type ProtectBranchForm struct { | ||||||
| 	EnableWhitelist         bool | 	EnableWhitelist         bool | ||||||
| 	WhitelistUsers          string | 	WhitelistUsers          string | ||||||
| 	WhitelistTeams          string | 	WhitelistTeams          string | ||||||
|  | 	WhitelistDeployKeys     bool | ||||||
| 	EnableMergeWhitelist    bool | 	EnableMergeWhitelist    bool | ||||||
| 	MergeWhitelistUsers     string | 	MergeWhitelistUsers     string | ||||||
| 	MergeWhitelistTeams     string | 	MergeWhitelistTeams     string | ||||||
|  |  | ||||||
|  | @ -31,11 +31,12 @@ type HookOptions struct { | ||||||
| 	GitAlternativeObjectDirectories string | 	GitAlternativeObjectDirectories string | ||||||
| 	GitQuarantinePath               string | 	GitQuarantinePath               string | ||||||
| 	ProtectedBranchID               int64 | 	ProtectedBranchID               int64 | ||||||
|  | 	IsDeployKey                     bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // HookPreReceive check whether the provided commits are allowed
 | // HookPreReceive check whether the provided commits are allowed
 | ||||||
| func HookPreReceive(ownerName, repoName string, opts HookOptions) (int, string) { | func HookPreReceive(ownerName, repoName string, opts HookOptions) (int, string) { | ||||||
| 	reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/pre-receive/%s/%s?old=%s&new=%s&ref=%s&userID=%d&gitObjectDirectory=%s&gitAlternativeObjectDirectories=%s&gitQuarantinePath=%s&prID=%d", | 	reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/pre-receive/%s/%s?old=%s&new=%s&ref=%s&userID=%d&gitObjectDirectory=%s&gitAlternativeObjectDirectories=%s&gitQuarantinePath=%s&prID=%d&isDeployKey=%t", | ||||||
| 		url.PathEscape(ownerName), | 		url.PathEscape(ownerName), | ||||||
| 		url.PathEscape(repoName), | 		url.PathEscape(repoName), | ||||||
| 		url.QueryEscape(opts.OldCommitID), | 		url.QueryEscape(opts.OldCommitID), | ||||||
|  | @ -46,6 +47,7 @@ func HookPreReceive(ownerName, repoName string, opts HookOptions) (int, string) | ||||||
| 		url.QueryEscape(opts.GitAlternativeObjectDirectories), | 		url.QueryEscape(opts.GitAlternativeObjectDirectories), | ||||||
| 		url.QueryEscape(opts.GitQuarantinePath), | 		url.QueryEscape(opts.GitQuarantinePath), | ||||||
| 		opts.ProtectedBranchID, | 		opts.ProtectedBranchID, | ||||||
|  | 		opts.IsDeployKey, | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
| 	resp, err := newInternalRequest(reqURL, "GET").Response() | 	resp, err := newInternalRequest(reqURL, "GET").Response() | ||||||
|  |  | ||||||
|  | @ -1334,6 +1334,7 @@ settings.protect_this_branch = Enable Branch Protection | ||||||
| settings.protect_this_branch_desc = Prevent deletion and disable any Git pushing to the branch. | settings.protect_this_branch_desc = Prevent deletion and disable any Git pushing to the branch. | ||||||
| settings.protect_whitelist_committers = Enable Push Whitelist | settings.protect_whitelist_committers = Enable Push Whitelist | ||||||
| settings.protect_whitelist_committers_desc = Allow whitelisted users or teams to push to this branch (but not force push). | settings.protect_whitelist_committers_desc = Allow whitelisted users or teams to push to this branch (but not force push). | ||||||
|  | settings.protect_whitelist_deploy_keys = Whitelist deploy keys with write access to push | ||||||
| settings.protect_whitelist_users = Whitelisted users for pushing: | settings.protect_whitelist_users = Whitelisted users for pushing: | ||||||
| settings.protect_whitelist_search_users = Search users… | settings.protect_whitelist_search_users = Search users… | ||||||
| settings.protect_whitelist_teams = Whitelisted teams for pushing: | settings.protect_whitelist_teams = Whitelisted teams for pushing: | ||||||
|  |  | ||||||
|  | @ -33,6 +33,7 @@ func HookPreReceive(ctx *macaron.Context) { | ||||||
| 	gitAlternativeObjectDirectories := ctx.QueryTrim("gitAlternativeObjectDirectories") | 	gitAlternativeObjectDirectories := ctx.QueryTrim("gitAlternativeObjectDirectories") | ||||||
| 	gitQuarantinePath := ctx.QueryTrim("gitQuarantinePath") | 	gitQuarantinePath := ctx.QueryTrim("gitQuarantinePath") | ||||||
| 	prID := ctx.QueryInt64("prID") | 	prID := ctx.QueryInt64("prID") | ||||||
|  | 	isDeployKey := ctx.QueryBool("isDeployKey") | ||||||
| 
 | 
 | ||||||
| 	branchName := strings.TrimPrefix(refFullName, git.BranchPrefix) | 	branchName := strings.TrimPrefix(refFullName, git.BranchPrefix) | ||||||
| 	repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName) | 	repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName) | ||||||
|  | @ -95,7 +96,12 @@ func HookPreReceive(ctx *macaron.Context) { | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		canPush := protectBranch.CanUserPush(userID) | 		canPush := false | ||||||
|  | 		if isDeployKey { | ||||||
|  | 			canPush = protectBranch.WhitelistDeployKeys | ||||||
|  | 		} else { | ||||||
|  | 			canPush = protectBranch.CanUserPush(userID) | ||||||
|  | 		} | ||||||
| 		if !canPush && prID > 0 { | 		if !canPush && prID > 0 { | ||||||
| 			pr, err := models.GetPullRequestByID(prID) | 			pr, err := models.GetPullRequestByID(prID) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
|  |  | ||||||
|  | @ -263,6 +263,7 @@ func HTTP(ctx *context.Context) { | ||||||
| 			models.EnvPusherName + "=" + authUser.Name, | 			models.EnvPusherName + "=" + authUser.Name, | ||||||
| 			models.EnvPusherID + fmt.Sprintf("=%d", authUser.ID), | 			models.EnvPusherID + fmt.Sprintf("=%d", authUser.ID), | ||||||
| 			models.ProtectedBranchRepoID + fmt.Sprintf("=%d", repo.ID), | 			models.ProtectedBranchRepoID + fmt.Sprintf("=%d", repo.ID), | ||||||
|  | 			models.EnvIsDeployKey + "=false", | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if !authUser.KeepEmailPrivate { | 		if !authUser.KeepEmailPrivate { | ||||||
|  |  | ||||||
|  | @ -213,6 +213,7 @@ func SettingsProtectedBranchPost(ctx *context.Context, f auth.ProtectBranchForm) | ||||||
| 
 | 
 | ||||||
| 		protectBranch.EnableStatusCheck = f.EnableStatusCheck | 		protectBranch.EnableStatusCheck = f.EnableStatusCheck | ||||||
| 		protectBranch.StatusCheckContexts = f.StatusCheckContexts | 		protectBranch.StatusCheckContexts = f.StatusCheckContexts | ||||||
|  | 		protectBranch.WhitelistDeployKeys = f.WhitelistDeployKeys | ||||||
| 
 | 
 | ||||||
| 		protectBranch.RequiredApprovals = f.RequiredApprovals | 		protectBranch.RequiredApprovals = f.RequiredApprovals | ||||||
| 		if strings.TrimSpace(f.ApprovalsWhitelistUsers) != "" { | 		if strings.TrimSpace(f.ApprovalsWhitelistUsers) != "" { | ||||||
|  |  | ||||||
|  | @ -59,6 +59,13 @@ | ||||||
| 								</div> | 								</div> | ||||||
| 							</div> | 							</div> | ||||||
| 						{{end}} | 						{{end}} | ||||||
|  | 						<br> | ||||||
|  | 						<div class="whitelist field"> | ||||||
|  | 							<div class="ui checkbox"> | ||||||
|  | 								<input type="checkbox" name="whitelist_deploy_keys" {{if .Branch.WhitelistDeployKeys}}checked{{end}}> | ||||||
|  | 								<label for="whitelist_deploy_keys">{{.i18n.Tr "repo.settings.protect_whitelist_deploy_keys"}}</label> | ||||||
|  | 							</div> | ||||||
|  | 						</div> | ||||||
| 					</div> | 					</div> | ||||||
| 
 | 
 | ||||||
| 					<div class="field"> | 					<div class="field"> | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue