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:
zeripath 2019-10-21 09:21:45 +01:00 committed by GitHub
parent b1c1e1549b
commit 0bfe5eb10b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 48 additions and 2 deletions

View file

@ -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:

View file

@ -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 {

View file

@ -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"`

View file

@ -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
View 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))
}

View file

@ -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.

View file

@ -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

View file

@ -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()

View file

@ -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:

View file

@ -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 {

View file

@ -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 {

View file

@ -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) != "" {

View file

@ -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">