Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorToon Claes <toon@gitlab.com>2021-05-03 09:16:29 +0300
committerToon Claes <toon@gitlab.com>2021-05-03 09:16:29 +0300
commit08430e67d1673cbac3aa3e7d103e8e047e5b23c0 (patch)
tree55e2c900d7443ceffe4e908eef84325ea6dded59
parent516e2a4a71a41e48465de590d9eaea2f8085e088 (diff)
parentfacfad44b0aaf675e425c4c0dbf73d80462c7e19 (diff)
Merge branch 'pks-reject-pushes-into-internal-namespaces' into 'master'
git: Reject pushes into internal ref namespaces See merge request gitlab-org/gitaly!3426
-rw-r--r--internal/git/command_description.go39
-rw-r--r--internal/git/reference.go9
-rw-r--r--internal/gitaly/service/cleanup/internalrefs/cleaner.go10
-rw-r--r--internal/gitaly/service/smarthttp/receive_pack_test.go50
4 files changed, 99 insertions, 9 deletions
diff --git a/internal/git/command_description.go b/internal/git/command_description.go
index 5e26d5d7d..43904eff6 100644
--- a/internal/git/command_description.go
+++ b/internal/git/command_description.go
@@ -2,6 +2,7 @@ package git
import (
"fmt"
+ "log"
"strings"
)
@@ -149,6 +150,16 @@ var commandDescriptions = map[string]commandDescription{
// Make git-receive-pack(1) advertise the push options
// capability to clients.
ConfigPair{Key: "receive.advertisePushOptions", Value: "true"},
+
+ // Hide several reference spaces from being displayed on pushes. This has
+ // two outcomes: first, we reduce the initial ref advertisement and should
+ // speed up pushes for repos which have loads of merge requests, pipelines
+ // and environments. Second, this also prohibits clients to update or delete
+ // these refs.
+ ConfigPair{Key: "receive.hideRefs", Value: "refs/environments/"},
+ ConfigPair{Key: "receive.hideRefs", Value: "refs/keep-around/"},
+ ConfigPair{Key: "receive.hideRefs", Value: "refs/merge-requests/"},
+ ConfigPair{Key: "receive.hideRefs", Value: "refs/pipelines/"},
},
},
"remote": {
@@ -221,6 +232,34 @@ var commandDescriptions = map[string]commandDescription{
},
}
+func init() {
+ // This is the poor-mans static assert that all internal ref prefixes are properly hidden
+ // from git-receive-pack(1) such that they cannot be written to when the user pushes.
+ receivePackDesc, ok := commandDescriptions["receive-pack"]
+ if !ok {
+ log.Fatal("could not find command description of git-receive-pack(1)")
+ }
+
+ hiddenRefs := map[string]bool{}
+ for _, opt := range receivePackDesc.opts {
+ configPair, ok := opt.(ConfigPair)
+ if !ok {
+ continue
+ }
+ if configPair.Key != "receive.hideRefs" {
+ continue
+ }
+
+ hiddenRefs[configPair.Value] = true
+ }
+
+ for _, internalRef := range InternalRefPrefixes {
+ if !hiddenRefs[internalRef] {
+ log.Fatalf("command description of receive-pack is missing hidden ref %q", internalRef)
+ }
+ }
+}
+
// mayUpdateRef indicates if a command is known to update references.
// This is useful to determine if a command requires reference hook
// configuration. A non-exhaustive list of commands is consulted to determine if
diff --git a/internal/git/reference.go b/internal/git/reference.go
index 494d20c77..96a32190d 100644
--- a/internal/git/reference.go
+++ b/internal/git/reference.go
@@ -6,6 +6,15 @@ import (
"strings"
)
+// InternalRefPrefixes is an array of all reference prefixes which are used internally by GitLab.
+// These need special treatment in some cases, e.g. to restrict writing to them.
+var InternalRefPrefixes = [...]string{
+ "refs/environments/",
+ "refs/keep-around/",
+ "refs/merge-requests/",
+ "refs/pipelines/",
+}
+
// Revision represents anything that resolves to either a commit, multiple
// commits or to an object different than a commit. This could be e.g.
// "master", "master^{commit}", an object hash or similar. See gitrevisions(1)
diff --git a/internal/gitaly/service/cleanup/internalrefs/cleaner.go b/internal/gitaly/service/cleanup/internalrefs/cleaner.go
index 69369299e..5d5a75d19 100644
--- a/internal/gitaly/service/cleanup/internalrefs/cleaner.go
+++ b/internal/gitaly/service/cleanup/internalrefs/cleaner.go
@@ -15,14 +15,6 @@ import (
"gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
)
-// Only references in these namespaces are cleaned up
-var internalRefs = []string{
- "refs/environments/",
- "refs/keep-around/",
- "refs/merge-requests/",
- "refs/pipelines/",
-}
-
// A ForEachFunc can be called for every entry in the filter-repo or BFG object
// map file that the cleaner is processing. Returning an error will stop the
// cleaner before it has processed the entry in question
@@ -137,7 +129,7 @@ func buildLookupTable(ctx context.Context, gitCmdFactory git.CommandFactory, rep
cmd, err := gitCmdFactory.New(ctx, repo, git.SubCmd{
Name: "for-each-ref",
Flags: []git.Option{git.ValueFlag{Name: "--format", Value: "%(objectname) %(refname)"}},
- Args: internalRefs,
+ Args: git.InternalRefPrefixes[:],
})
if err != nil {
return nil, err
diff --git a/internal/gitaly/service/smarthttp/receive_pack_test.go b/internal/gitaly/service/smarthttp/receive_pack_test.go
index 555fb28c9..d4ba2c428 100644
--- a/internal/gitaly/service/smarthttp/receive_pack_test.go
+++ b/internal/gitaly/service/smarthttp/receive_pack_test.go
@@ -17,6 +17,7 @@ import (
"gitlab.com/gitlab-org/gitaly/internal/git"
"gitlab.com/gitlab-org/gitaly/internal/git/gittest"
"gitlab.com/gitlab-org/gitaly/internal/git/hooks"
+ "gitlab.com/gitlab-org/gitaly/internal/git/localrepo"
"gitlab.com/gitlab-org/gitaly/internal/gitaly/config"
"gitlab.com/gitlab-org/gitaly/internal/gitaly/service"
"gitlab.com/gitlab-org/gitaly/internal/gitaly/service/hook"
@@ -103,6 +104,55 @@ func TestSuccessfulReceivePackRequest(t *testing.T) {
}, payload)
}
+func TestReceivePackHiddenRefs(t *testing.T) {
+ cfg, repoProto, repoPath := testcfg.BuildWithRepo(t)
+ repoProto.GlProjectPath = "project/path"
+
+ testhelper.ConfigureGitalyHooksBin(t, cfg)
+
+ ctx, cancel := testhelper.Context()
+ defer cancel()
+
+ repo := localrepo.New(git.NewExecCommandFactory(cfg), repoProto, cfg)
+ oldHead, err := repo.ResolveRevision(ctx, "HEAD~")
+ require.NoError(t, err)
+ newHead, err := repo.ResolveRevision(ctx, "HEAD")
+ require.NoError(t, err)
+
+ serverSocketPath := runSmartHTTPServer(t, cfg)
+
+ client, conn := newSmartHTTPClient(t, serverSocketPath, cfg.Auth.Token)
+ defer conn.Close()
+
+ for _, ref := range []string{
+ "refs/environments/1",
+ "refs/merge-requests/1/head",
+ "refs/merge-requests/1/merge",
+ "refs/pipelines/1",
+ } {
+ t.Run(ref, func(t *testing.T) {
+ request := &bytes.Buffer{}
+ gittest.WritePktlineString(t, request, fmt.Sprintf("%s %s %s\x00 %s",
+ oldHead, newHead, ref, uploadPackCapabilities))
+ gittest.WritePktlineFlush(t, request)
+
+ // The options passed are the same ones used when doing an actual push.
+ revisions := strings.NewReader(fmt.Sprintf("^%s\n%s\n", oldHead, newHead))
+ pack := testhelper.MustRunCommand(t, revisions, "git", "-C", repoPath, "pack-objects", "--stdout", "--revs", "--thin", "--delta-base-offset", "-q")
+ request.Write(pack)
+
+ stream, err := client.PostReceivePack(ctx)
+ require.NoError(t, err)
+
+ response := doPush(t, stream, &gitalypb.PostReceivePackRequest{
+ Repository: repoProto, GlUsername: "user", GlId: "123", GlRepository: "project-456",
+ }, request)
+
+ require.Contains(t, string(response), fmt.Sprintf("%s deny updating a hidden ref", ref))
+ })
+ }
+}
+
func TestSuccessfulReceivePackRequestWithGitProtocol(t *testing.T) {
cfg, repo, repoPath := testcfg.BuildWithRepo(t)