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 falsetags/v1.11.0-rc1
| @@ -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 | ||||
| @@ -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"> | ||||