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:
authorSavely Krasovsky <krasovskiisi@sovcombank.ru>2022-08-23 17:41:55 +0300
committerWill Chandler <wchandler@gitlab.com>2022-08-23 17:41:55 +0300
commit77c9fca2d05f8e6e93d15e457ff37b86ae852bc4 (patch)
tree69a0089b1c36814bcf776733bb5d90934cbb8339
parent46d8149fe7233a1d0d7518f64ace97bccba3ad1b (diff)
feat(gitaly-git2go): sign commits with OpenPGP key
-rw-r--r--Makefile2
-rw-r--r--NOTICE90
-rw-r--r--cmd/gitaly-git2go/apply.go27
-rw-r--r--cmd/gitaly-git2go/cherry_pick.go15
-rw-r--r--cmd/gitaly-git2go/commit.go6
-rw-r--r--cmd/gitaly-git2go/commit/commit.go29
-rw-r--r--cmd/gitaly-git2go/conflicts.go2
-rw-r--r--cmd/gitaly-git2go/git2goutil/commit.go47
-rw-r--r--cmd/gitaly-git2go/git2goutil/sign.go43
-rw-r--r--cmd/gitaly-git2go/git2goutil/test_sign.go49
-rw-r--r--cmd/gitaly-git2go/main.go2
-rw-r--r--cmd/gitaly-git2go/merge.go25
-rw-r--r--cmd/gitaly-git2go/rebase.go1
-rw-r--r--cmd/gitaly-git2go/resolve_conflicts.go19
-rw-r--r--cmd/gitaly-git2go/revert.go15
-rw-r--r--cmd/gitaly-git2go/submodule.go18
-rw-r--r--go.mod2
-rw-r--r--go.sum5
-rw-r--r--internal/backup/storage_service_sink.go6
-rw-r--r--internal/git/command_factory.go2
-rw-r--r--internal/git2go/apply.go7
-rw-r--r--internal/git2go/apply_test.go14
-rw-r--r--internal/git2go/cherry_pick.go10
-rw-r--r--internal/git2go/commit.go36
-rw-r--r--internal/git2go/commit_test.go69
-rw-r--r--internal/git2go/executor.go2
-rw-r--r--internal/git2go/merge.go3
-rw-r--r--internal/git2go/rebase.go4
-rw-r--r--internal/git2go/resolve_conflicts.go2
-rw-r--r--internal/git2go/revert.go4
-rw-r--r--internal/git2go/submodule.go7
-rw-r--r--internal/git2go/testdata/publicKey.gpgbin0 -> 260 bytes
-rw-r--r--internal/git2go/testdata/signingKey.gpgbin0 -> 297 bytes
-rw-r--r--internal/gitaly/config/config.go3
-rw-r--r--internal/gitaly/service/operations/apply_patch_test.go6
-rw-r--r--internal/gitaly/service/operations/commit_files.go2
-rw-r--r--internal/gitaly/service/remote/update_remote_mirror_test.go2
-rw-r--r--internal/testhelper/testhelper.go2
38 files changed, 460 insertions, 118 deletions
diff --git a/Makefile b/Makefile
index c61517e61..157721c1b 100644
--- a/Makefile
+++ b/Makefile
@@ -264,7 +264,7 @@ find_go_sources = $(shell find ${SOURCE_DIR} -type d \( -name ruby
# TEST_PACKAGES: packages which shall be tested
run_go_tests = PATH='${SOURCE_DIR}/internal/testhelper/testdata/home/bin:${PATH}' \
TEST_TMP_DIR='${TEST_TMP_DIR}' \
- ${GOTESTSUM} --format ${TEST_FORMAT} --junitfile ${TEST_REPORT} --jsonfile ${TEST_FULL_OUTPUT} -- -ldflags '${GO_LDFLAGS}' -tags '${SERVER_BUILD_TAGS},${GIT2GO_BUILD_TAGS}' ${TEST_OPTIONS} ${TEST_PACKAGES}
+ ${GOTESTSUM} --format ${TEST_FORMAT} --junitfile ${TEST_REPORT} --jsonfile ${TEST_FULL_OUTPUT} -- -ldflags '${GO_LDFLAGS}' -tags '${SERVER_BUILD_TAGS},${GIT2GO_BUILD_TAGS},gitaly_test_signing' ${TEST_OPTIONS} ${TEST_PACKAGES}
## Test options passed to `dlv test`.
DEBUG_OPTIONS ?= $(patsubst -%,-test.%,${TEST_OPTIONS})
diff --git a/NOTICE b/NOTICE
index 3ab61f6ad..867970232 100644
--- a/NOTICE
+++ b/NOTICE
@@ -2711,6 +2711,36 @@ LICENSE - github.com/Azure/go-autorest/tracing
limitations under the License.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+LICENSE - github.com/ProtonMail/go-crypto
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
LICENSE.txt - github.com/aws/aws-sdk-go
Apache License
@@ -6755,6 +6785,66 @@ SOFTWARE.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+LICENSE - github.com/cloudflare/circl
+Copyright (c) 2019 Cloudflare. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Cloudflare nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+========================================================================
+
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
LICENSE - github.com/cloudflare/tableflip
Copyright (c) 2017-2018, Cloudflare. All rights reserved.
diff --git a/cmd/gitaly-git2go/apply.go b/cmd/gitaly-git2go/apply.go
index 3a7f65b79..e5cc1339a 100644
--- a/cmd/gitaly-git2go/apply.go
+++ b/cmd/gitaly-git2go/apply.go
@@ -42,12 +42,14 @@ func (iter *patchIterator) Value() git2go.Patch { return iter.value }
func (iter *patchIterator) Err() error { return iter.error }
type applySubcommand struct {
- gitBinaryPath string
+ gitBinaryPath string
+ signingKeyPath string
}
func (cmd *applySubcommand) Flags() *flag.FlagSet {
fs := flag.NewFlagSet("apply", flag.ExitOnError)
fs.StringVar(&cmd.gitBinaryPath, "git-binary-path", "", "Path to the Git binary.")
+ fs.StringVar(&cmd.signingKeyPath, "signing-key", "", "Path to the OpenPGP signing key.")
return fs
}
@@ -126,30 +128,35 @@ func (cmd *applySubcommand) applyPatch(
}
}
- patchedTree, err := patchedIndex.WriteTreeTo(repo)
+ patchedTreeOID, err := patchedIndex.WriteTreeTo(repo)
if err != nil {
return nil, fmt.Errorf("write patched tree: %w", err)
}
+ patchedTree, err := repo.LookupTree(patchedTreeOID)
+ if err != nil {
+ return nil, fmt.Errorf("lookup tree: %w", err)
+ }
author := git.Signature(patch.Author)
- patchedCommitOID, err := repo.CreateCommitFromIds("", &author, committer, patch.Message, patchedTree, parentCommitOID)
+ patchedCommitID, err := git2goutil.NewCommitSubmitter(repo, cmd.signingKeyPath).
+ Commit(&author, committer, git.MessageEncodingUTF8, patch.Message, patchedTree, parentCommit)
if err != nil {
return nil, fmt.Errorf("create commit: %w", err)
}
- return patchedCommitOID, nil
+ return patchedCommitID, nil
}
// threeWayMerge attempts a three-way merge as a fallback if applying the patch fails.
// Fallback three-way merge is only possible if the patch records the pre-image blobs
// and the repository contains them. It works as follows:
//
-// 1. An index that contains only the pre-image blobs of the patch is built. This is done
-// by calling `git apply --build-fake-ancestor`. The tree of the index is the fake
-// ancestor tree.
-// 2. The fake ancestor tree is patched to produce the post-image tree of the patch.
-// 3. Three-way merge is performed with fake ancestor tree as the common ancestor, the
-// base commit's tree as our tree and the patched fake ancestor tree as their tree.
+// 1. An index that contains only the pre-image blobs of the patch is built. This is done
+// by calling `git apply --build-fake-ancestor`. The tree of the index is the fake
+// ancestor tree.
+// 2. The fake ancestor tree is patched to produce the post-image tree of the patch.
+// 3. Three-way merge is performed with fake ancestor tree as the common ancestor, the
+// base commit's tree as our tree and the patched fake ancestor tree as their tree.
func (cmd *applySubcommand) threeWayMerge(
ctx context.Context,
repo *git.Repository,
diff --git a/cmd/gitaly-git2go/cherry_pick.go b/cmd/gitaly-git2go/cherry_pick.go
index ebf29759f..1a185a7e2 100644
--- a/cmd/gitaly-git2go/cherry_pick.go
+++ b/cmd/gitaly-git2go/cherry_pick.go
@@ -102,21 +102,26 @@ func (cmd *cherryPickSubcommand) cherryPick(ctx context.Context, r *git2go.Cherr
}
}
- tree, err := index.WriteTreeTo(repo)
+ treeOID, err := index.WriteTreeTo(repo)
if err != nil {
return "", fmt.Errorf("could not write tree: %w", err)
}
+ tree, err := repo.LookupTree(treeOID)
+ if err != nil {
+ return "", fmt.Errorf("lookup tree: %w", err)
+ }
- if tree.Equal(ours.TreeId()) {
+ if treeOID.Equal(ours.TreeId()) {
return "", git2go.EmptyError{}
}
committer := git.Signature(git2go.NewSignature(r.CommitterName, r.CommitterMail, r.CommitterDate))
- commit, err := repo.CreateCommitFromIds("", pick.Author(), &committer, r.Message, tree, ours.Id())
+ commitID, err := git2goutil.NewCommitSubmitter(repo, r.SigningKey).
+ Commit(pick.Author(), &committer, git.MessageEncodingUTF8, r.Message, tree, ours)
if err != nil {
- return "", fmt.Errorf("could not create cherry-pick commit: %w", err)
+ return "", fmt.Errorf("create not create cherry-pick commit: %w", err)
}
- return commit.String(), nil
+ return commitID.String(), nil
}
diff --git a/cmd/gitaly-git2go/commit.go b/cmd/gitaly-git2go/commit.go
index cd0a1a31d..0dbf90d24 100644
--- a/cmd/gitaly-git2go/commit.go
+++ b/cmd/gitaly-git2go/commit.go
@@ -12,8 +12,10 @@ import (
type commitSubcommand struct{}
-func (commitSubcommand) Flags() *flag.FlagSet { return flag.NewFlagSet("commit", flag.ExitOnError) }
+func (cmd *commitSubcommand) Flags() *flag.FlagSet {
+ return flag.NewFlagSet("commit", flag.ExitOnError)
+}
-func (commitSubcommand) Run(ctx context.Context, decoder *gob.Decoder, encoder *gob.Encoder) error {
+func (cmd *commitSubcommand) Run(ctx context.Context, decoder *gob.Decoder, encoder *gob.Encoder) error {
return commit.Run(ctx, decoder, encoder)
}
diff --git a/cmd/gitaly-git2go/commit/commit.go b/cmd/gitaly-git2go/commit/commit.go
index 57ca1f586..e26acd559 100644
--- a/cmd/gitaly-git2go/commit/commit.go
+++ b/cmd/gitaly-git2go/commit/commit.go
@@ -15,7 +15,7 @@ import (
// Run runs the commit subcommand.
func Run(ctx context.Context, decoder *gob.Decoder, encoder *gob.Encoder) error {
- var params git2go.CommitParams
+ var params git2go.CommitCommand
if err := decoder.Decode(&params); err != nil {
return err
}
@@ -27,8 +27,8 @@ func Run(ctx context.Context, decoder *gob.Decoder, encoder *gob.Encoder) error
})
}
-func commit(ctx context.Context, params git2go.CommitParams) (string, error) {
- repo, err := git2goutil.OpenRepository(params.Repository)
+func commit(ctx context.Context, request git2go.CommitCommand) (string, error) {
+ repo, err := git2goutil.OpenRepository(request.Repository)
if err != nil {
return "", fmt.Errorf("open repository: %w", err)
}
@@ -38,20 +38,20 @@ func commit(ctx context.Context, params git2go.CommitParams) (string, error) {
return "", fmt.Errorf("new index: %w", err)
}
- var parents []*git.Oid
- if params.Parent != "" {
- parentOID, err := git.NewOid(params.Parent)
+ var parents []*git.Commit
+ if request.Parent != "" {
+ parentOID, err := git.NewOid(request.Parent)
if err != nil {
return "", fmt.Errorf("parse base commit oid: %w", err)
}
- parents = []*git.Oid{parentOID}
-
baseCommit, err := repo.LookupCommit(parentOID)
if err != nil {
return "", fmt.Errorf("lookup commit: %w", err)
}
+ parents = []*git.Commit{baseCommit}
+
baseTree, err := baseCommit.Tree()
if err != nil {
return "", fmt.Errorf("lookup tree: %w", err)
@@ -62,7 +62,7 @@ func commit(ctx context.Context, params git2go.CommitParams) (string, error) {
}
}
- for _, action := range params.Actions {
+ for _, action := range request.Actions {
if err := apply(action, repo, index); err != nil {
if git.IsErrorClass(err, git.ErrorClassIndex) {
err = git2go.IndexError(err.Error())
@@ -76,10 +76,15 @@ func commit(ctx context.Context, params git2go.CommitParams) (string, error) {
if err != nil {
return "", fmt.Errorf("write tree: %w", err)
}
+ tree, err := repo.LookupTree(treeOID)
+ if err != nil {
+ return "", fmt.Errorf("lookup tree: %w", err)
+ }
- author := git.Signature(params.Author)
- committer := git.Signature(params.Committer)
- commitID, err := repo.CreateCommitFromIds("", &author, &committer, params.Message, treeOID, parents...)
+ author := git.Signature(request.Author)
+ committer := git.Signature(request.Committer)
+ commitID, err := git2goutil.NewCommitSubmitter(repo, request.SigningKey).
+ Commit(&author, &committer, git.MessageEncodingUTF8, request.Message, tree, parents...)
if err != nil {
if git.IsErrorClass(err, git.ErrorClassInvalid) {
return "", git2go.InvalidArgumentError(err.Error())
diff --git a/cmd/gitaly-git2go/conflicts.go b/cmd/gitaly-git2go/conflicts.go
index 1e2fbb4e0..954483cad 100644
--- a/cmd/gitaly-git2go/conflicts.go
+++ b/cmd/gitaly-git2go/conflicts.go
@@ -32,7 +32,7 @@ func (cmd *conflictsSubcommand) Run(_ context.Context, decoder *gob.Decoder, enc
return encoder.Encode(res)
}
-func (conflictsSubcommand) conflicts(request git2go.ConflictsCommand) git2go.ConflictsResult {
+func (*conflictsSubcommand) conflicts(request git2go.ConflictsCommand) git2go.ConflictsResult {
repo, err := git2goutil.OpenRepository(request.Repository)
if err != nil {
return conflictError(codes.Internal, fmt.Errorf("could not open repository: %w", err).Error())
diff --git a/cmd/gitaly-git2go/git2goutil/commit.go b/cmd/gitaly-git2go/git2goutil/commit.go
new file mode 100644
index 000000000..7f45640ad
--- /dev/null
+++ b/cmd/gitaly-git2go/git2goutil/commit.go
@@ -0,0 +1,47 @@
+package git2goutil
+
+import (
+ "fmt"
+
+ git "github.com/libgit2/git2go/v33"
+)
+
+// CommitSubmitter is the helper struct to make signed Commits conveniently.
+type CommitSubmitter struct {
+ Repo *git.Repository
+ SigningKeyPath string
+}
+
+// NewCommitSubmitter creates a new CommitSubmitter.
+func NewCommitSubmitter(repo *git.Repository, signingKeyPath string) *CommitSubmitter {
+ return &CommitSubmitter{
+ Repo: repo,
+ SigningKeyPath: signingKeyPath,
+ }
+}
+
+// Commit commits a commit with or without OpenPGP signature depends on SigningKeyPath value.
+func (cs *CommitSubmitter) Commit(
+ author, committer *git.Signature,
+ messageEncoding git.MessageEncoding,
+ message string,
+ tree *git.Tree,
+ parents ...*git.Commit,
+) (*git.Oid, error) {
+ commitBytes, err := cs.Repo.CreateCommitBuffer(author, committer, messageEncoding, message, tree, parents...)
+ if err != nil {
+ return nil, err
+ }
+
+ signature, err := CreateCommitSignature(cs.SigningKeyPath, string(commitBytes))
+ if err != nil {
+ return nil, fmt.Errorf("create commit signature: %w", err)
+ }
+
+ commitID, err := cs.Repo.CreateCommitWithSignature(string(commitBytes), signature, "")
+ if err != nil {
+ return nil, err
+ }
+
+ return commitID, nil
+}
diff --git a/cmd/gitaly-git2go/git2goutil/sign.go b/cmd/gitaly-git2go/git2goutil/sign.go
new file mode 100644
index 000000000..b8437d9ad
--- /dev/null
+++ b/cmd/gitaly-git2go/git2goutil/sign.go
@@ -0,0 +1,43 @@
+//go:build !gitaly_test_signing
+
+package git2goutil
+
+import (
+ "bytes"
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/ProtonMail/go-crypto/openpgp"
+ "github.com/ProtonMail/go-crypto/openpgp/packet"
+)
+
+// CreateCommitSignature reads the given signing key and produces PKCS#7 detached signature.
+// When the path to the signing key is not present, an empty signature is returned.
+func CreateCommitSignature(signingKeyPath, contentToSign string) (string, error) {
+ if signingKeyPath == "" {
+ return "", nil
+ }
+
+ file, err := os.Open(signingKeyPath)
+ if err != nil {
+ return "", fmt.Errorf("open file: %w", err)
+ }
+
+ entity, err := openpgp.ReadEntity(packet.NewReader(file))
+ if err != nil {
+ return "", fmt.Errorf("read entity: %w", err)
+ }
+
+ sigBuf := new(bytes.Buffer)
+ if err := openpgp.ArmoredDetachSignText(
+ sigBuf,
+ entity,
+ strings.NewReader(contentToSign),
+ &packet.Config{},
+ ); err != nil {
+ return "", fmt.Errorf("sign commit: %w", err)
+ }
+
+ return sigBuf.String(), nil
+}
diff --git a/cmd/gitaly-git2go/git2goutil/test_sign.go b/cmd/gitaly-git2go/git2goutil/test_sign.go
new file mode 100644
index 000000000..990344910
--- /dev/null
+++ b/cmd/gitaly-git2go/git2goutil/test_sign.go
@@ -0,0 +1,49 @@
+//go:build gitaly_test_signing
+
+package git2goutil
+
+import (
+ "bytes"
+ "fmt"
+ "os"
+ "strings"
+ "time"
+
+ "github.com/ProtonMail/go-crypto/openpgp"
+ "github.com/ProtonMail/go-crypto/openpgp/packet"
+)
+
+// CreateCommitSignature reads the given signing key and produces PKCS#7 detached signature.
+// When the path to the signing key is not present, an empty signature is returned.
+// Test version creates deterministic signature which is the same with the same data every run.
+func CreateCommitSignature(signingKeyPath, contentToSign string) (string, error) {
+ if signingKeyPath == "" {
+ return "", nil
+ }
+
+ file, err := os.Open(signingKeyPath)
+ if err != nil {
+ return "", fmt.Errorf("open file: %w", err)
+ }
+
+ entity, err := openpgp.ReadEntity(packet.NewReader(file))
+ if err != nil {
+ return "", fmt.Errorf("read entity: %w", err)
+ }
+
+ sigBuf := new(bytes.Buffer)
+ if err := openpgp.ArmoredDetachSignText(
+ sigBuf,
+ entity,
+ strings.NewReader(contentToSign),
+ &packet.Config{
+ Time: func() time.Time {
+ return time.Date(2022, 8, 20, 11, 22, 33, 0, time.UTC)
+ },
+ },
+ ); err != nil {
+ return "", fmt.Errorf("sign commit: %w", err)
+ }
+
+ return sigBuf.String(), nil
+}
diff --git a/cmd/gitaly-git2go/main.go b/cmd/gitaly-git2go/main.go
index e8a2c2c7b..8ee0e7fd0 100644
--- a/cmd/gitaly-git2go/main.go
+++ b/cmd/gitaly-git2go/main.go
@@ -27,7 +27,7 @@ type subcmd interface {
var subcommands = map[string]subcmd{
"apply": &applySubcommand{},
"cherry-pick": &cherryPickSubcommand{},
- "commit": commitSubcommand{},
+ "commit": &commitSubcommand{},
"conflicts": &conflictsSubcommand{},
"merge": &mergeSubcommand{},
"rebase": &rebaseSubcommand{},
diff --git a/cmd/gitaly-git2go/merge.go b/cmd/gitaly-git2go/merge.go
index 4ab3ef9fd..21922b24a 100644
--- a/cmd/gitaly-git2go/merge.go
+++ b/cmd/gitaly-git2go/merge.go
@@ -18,8 +18,7 @@ import (
type mergeSubcommand struct{}
func (cmd *mergeSubcommand) Flags() *flag.FlagSet {
- flags := flag.NewFlagSet("merge", flag.ExitOnError)
- return flags
+ return flag.NewFlagSet("merge", flag.ExitOnError)
}
func (cmd *mergeSubcommand) Run(_ context.Context, decoder *gob.Decoder, encoder *gob.Encoder) error {
@@ -32,7 +31,7 @@ func (cmd *mergeSubcommand) Run(_ context.Context, decoder *gob.Decoder, encoder
request.AuthorDate = time.Now()
}
- commitID, err := merge(request)
+ commitID, err := cmd.merge(request)
return encoder.Encode(git2go.Result{
CommitID: commitID,
@@ -40,7 +39,7 @@ func (cmd *mergeSubcommand) Run(_ context.Context, decoder *gob.Decoder, encoder
})
}
-func merge(request git2go.MergeCommand) (string, error) {
+func (cmd *mergeSubcommand) merge(request git2go.MergeCommand) (string, error) {
repo, err := git2goutil.OpenRepository(request.Repository)
if err != nil {
return "", fmt.Errorf("could not open repository: %w", err)
@@ -86,10 +85,14 @@ func merge(request git2go.MergeCommand) (string, error) {
}
}
- tree, err := index.WriteTreeTo(repo)
+ treeOID, err := index.WriteTreeTo(repo)
if err != nil {
return "", fmt.Errorf("could not write tree: %w", err)
}
+ tree, err := repo.LookupTree(treeOID)
+ if err != nil {
+ return "", fmt.Errorf("lookup tree: %w", err)
+ }
author := git.Signature(git2go.NewSignature(request.AuthorName, request.AuthorMail, request.AuthorDate))
committer := author
@@ -97,18 +100,20 @@ func merge(request git2go.MergeCommand) (string, error) {
committer = git.Signature(git2go.NewSignature(request.CommitterName, request.CommitterMail, request.CommitterDate))
}
- var parents []*git.Oid
+ var parents []*git.Commit
if request.Squash {
- parents = []*git.Oid{ours.Id()}
+ parents = []*git.Commit{ours}
} else {
- parents = []*git.Oid{ours.Id(), theirs.Id()}
+ parents = []*git.Commit{ours, theirs}
}
- commit, err := repo.CreateCommitFromIds("", &author, &committer, request.Message, tree, parents...)
+
+ commitID, err := git2goutil.NewCommitSubmitter(repo, request.SigningKey).
+ Commit(&author, &committer, git.MessageEncodingUTF8, request.Message, tree, parents...)
if err != nil {
return "", fmt.Errorf("could not create merge commit: %w", err)
}
- return commit.String(), nil
+ return commitID.String(), nil
}
func resolveConflicts(repo *git.Repository, index *git.Index) error {
diff --git a/cmd/gitaly-git2go/rebase.go b/cmd/gitaly-git2go/rebase.go
index b0638c72e..1fa23984e 100644
--- a/cmd/gitaly-git2go/rebase.go
+++ b/cmd/gitaly-git2go/rebase.go
@@ -73,6 +73,7 @@ func (cmd *rebaseSubcommand) rebase(ctx context.Context, request *git2go.RebaseC
return "", fmt.Errorf("get rebase options: %w", err)
}
opts.InMemory = 1
+ opts.CommitCreateCallback = git2goutil.NewCommitSubmitter(repo, request.SigningKey).Commit
var commit *git.AnnotatedCommit
if request.BranchName != "" {
diff --git a/cmd/gitaly-git2go/resolve_conflicts.go b/cmd/gitaly-git2go/resolve_conflicts.go
index 73062eb17..6d05f59c7 100644
--- a/cmd/gitaly-git2go/resolve_conflicts.go
+++ b/cmd/gitaly-git2go/resolve_conflicts.go
@@ -180,26 +180,31 @@ func (cmd resolveSubcommand) Run(_ context.Context, decoder *gob.Decoder, encode
return fmt.Errorf("Missing resolutions for the following files: %s", strings.Join(conflictPaths, ", ")) //nolint
}
- tree, err := index.WriteTreeTo(repo)
+ treeOID, err := index.WriteTreeTo(repo)
if err != nil {
return fmt.Errorf("write tree to repo: %w", err)
}
+ tree, err := repo.LookupTree(treeOID)
+ if err != nil {
+ return fmt.Errorf("lookup tree: %w", err)
+ }
- signature := git2go.NewSignature(request.AuthorName, request.AuthorMail, request.AuthorDate)
+ sign := git2go.NewSignature(request.AuthorName, request.AuthorMail, request.AuthorDate)
committer := &git.Signature{
- Name: signature.Name,
- Email: signature.Email,
+ Name: sign.Name,
+ Email: sign.Email,
When: request.AuthorDate,
}
- commit, err := repo.CreateCommitFromIds("", committer, committer, request.Message, tree, ours.Id(), theirs.Id())
+ commitID, err := git2goutil.NewCommitSubmitter(repo, request.SigningKey).
+ Commit(committer, committer, git.MessageEncodingUTF8, request.Message, tree, ours, theirs)
if err != nil {
- return fmt.Errorf("could not create resolve conflict commit: %w", err)
+ return fmt.Errorf("create commit: %w", err)
}
response := git2go.ResolveResult{
MergeResult: git2go.MergeResult{
- CommitID: commit.String(),
+ CommitID: commitID.String(),
},
}
diff --git a/cmd/gitaly-git2go/revert.go b/cmd/gitaly-git2go/revert.go
index aaeeb00ac..725502289 100644
--- a/cmd/gitaly-git2go/revert.go
+++ b/cmd/gitaly-git2go/revert.go
@@ -85,20 +85,25 @@ func (cmd *revertSubcommand) revert(ctx context.Context, request *git2go.RevertC
return "", git2go.HasConflictsError{}
}
- tree, err := index.WriteTreeTo(repo)
+ treeOID, err := index.WriteTreeTo(repo)
if err != nil {
return "", fmt.Errorf("write tree: %w", err)
}
+ tree, err := repo.LookupTree(treeOID)
+ if err != nil {
+ return "", fmt.Errorf("lookup tree: %w", err)
+ }
- if tree.Equal(ours.TreeId()) {
+ if treeOID.Equal(ours.TreeId()) {
return "", git2go.EmptyError{}
}
committer := git.Signature(git2go.NewSignature(request.AuthorName, request.AuthorMail, request.AuthorDate))
- commit, err := repo.CreateCommitFromIds("", &committer, &committer, request.Message, tree, ours.Id())
+ commitID, err := git2goutil.NewCommitSubmitter(repo, request.SigningKey).
+ Commit(&committer, &committer, git.MessageEncodingUTF8, request.Message, tree, ours)
if err != nil {
- return "", fmt.Errorf("create revert commit: %w", err)
+ return "", fmt.Errorf("create commit: %w", err)
}
- return commit.String(), nil
+ return commitID.String(), nil
}
diff --git a/cmd/gitaly-git2go/submodule.go b/cmd/gitaly-git2go/submodule.go
index 0e52ee3f4..4596a5ff9 100644
--- a/cmd/gitaly-git2go/submodule.go
+++ b/cmd/gitaly-git2go/submodule.go
@@ -121,14 +121,16 @@ func (cmd *submoduleSubcommand) run(request git2go.SubmoduleCommand) (*git2go.Su
request.AuthorDate,
),
)
- newCommitOID, err := repo.CreateCommit(
- "", // caller should update branch with hooks
- &committer,
- &committer,
- request.Message,
- newTree,
- startCommit,
- )
+
+ newCommitOID, err := git2goutil.NewCommitSubmitter(repo, request.SigningKey).
+ Commit(
+ &committer,
+ &committer,
+ git.MessageEncodingUTF8,
+ request.Message,
+ newTree,
+ startCommit,
+ )
if err != nil {
return nil, fmt.Errorf(
"%s: %w",
diff --git a/go.mod b/go.mod
index 57a4b2da3..c6cdf8a8e 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@ module gitlab.com/gitlab-org/gitaly/v15
go 1.17
require (
+ github.com/ProtonMail/go-crypto v0.0.0-20220810064516-de89276ce0f3
github.com/beevik/ntp v0.3.0
github.com/cloudflare/tableflip v1.2.3
github.com/containerd/cgroups v1.0.4
@@ -87,6 +88,7 @@ require (
github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/client9/reopen v1.0.0 // indirect
+ github.com/cloudflare/circl v1.1.0 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-minhash v0.0.0-20170608043002-7fe510aff544 // indirect
diff --git a/go.sum b/go.sum
index 50ebd8ac0..5cbd3509a 100644
--- a/go.sum
+++ b/go.sum
@@ -140,6 +140,8 @@ github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugX
github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU=
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/ProtonMail/go-crypto v0.0.0-20220810064516-de89276ce0f3 h1:JBPdE3yq6D8k8UxTzyCN2uhpHfGKsJEurKLHLU6YBdM=
+github.com/ProtonMail/go-crypto v0.0.0-20220810064516-de89276ce0f3/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8=
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
@@ -216,6 +218,7 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.3.0 h1:t/LhUZLVitR1Ow2YOnduCsavhwFUklBMoGVYUCqmCqk=
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -232,6 +235,8 @@ github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJ
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/client9/reopen v1.0.0 h1:8tpLVR74DLpLObrn2KvsyxJY++2iORGR17WLUdSzUws=
github.com/client9/reopen v1.0.0/go.mod h1:caXVCEr+lUtoN1FlsRiOWdfQtdRHIYfcb0ai8qKWtkQ=
+github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY=
+github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
github.com/cloudflare/tableflip v1.2.3 h1:8I+B99QnnEWPHOY3fWipwVKxS70LGgUsslG7CSfmHMw=
github.com/cloudflare/tableflip v1.2.3/go.mod h1:P4gRehmV6Z2bY5ao5ml9Pd8u6kuEnlB37pUFMmv7j2E=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
diff --git a/internal/backup/storage_service_sink.go b/internal/backup/storage_service_sink.go
index 217fb322b..ef484624c 100644
--- a/internal/backup/storage_service_sink.go
+++ b/internal/backup/storage_service_sink.go
@@ -6,9 +6,9 @@ import (
"io"
"gocloud.dev/blob"
- _ "gocloud.dev/blob/azureblob" // nolint:nolintlint,golint,gci
- _ "gocloud.dev/blob/gcsblob" // nolint:nolintlint,golint,gci
- _ "gocloud.dev/blob/s3blob" // nolint:nolintlint,golint,gci
+ _ "gocloud.dev/blob/azureblob" //nolint:nolintlint,golint,gci
+ _ "gocloud.dev/blob/gcsblob" //nolint:nolintlint,golint,gci
+ _ "gocloud.dev/blob/s3blob" //nolint:nolintlint,golint,gci
"gocloud.dev/gcerrors"
)
diff --git a/internal/git/command_factory.go b/internal/git/command_factory.go
index 060082ced..dae719935 100644
--- a/internal/git/command_factory.go
+++ b/internal/git/command_factory.go
@@ -257,7 +257,7 @@ func (cf *ExecCommandFactory) GetExecutionEnvironment(ctx context.Context) Execu
}
// If none is enabled though, we simply use the first execution environment, which is also
- // the one with highest priority. This can for example happen in case we only were able to
+ // the one with the highest priority. This can for example happen in case we only were able to
// construct a single execution environment that is currently feature flagged.
return cf.execEnvs[0]
}
diff --git a/internal/git2go/apply.go b/internal/git2go/apply.go
index 2bee12857..5c2b70da2 100644
--- a/internal/git2go/apply.go
+++ b/internal/git2go/apply.go
@@ -104,8 +104,13 @@ func (b *Executor) Apply(ctx context.Context, repo repository.GitRepo, params Ap
execEnv := b.gitCmdFactory.GetExecutionEnvironment(ctx)
+ args := []string{"-git-binary-path", execEnv.BinaryPath}
+ if b.signingKey != "" {
+ args = append(args, "-signing-key", b.signingKey)
+ }
+
var result Result
- output, err := b.run(ctx, repo, reader, "apply", "-git-binary-path", execEnv.BinaryPath)
+ output, err := b.run(ctx, repo, reader, "apply", args...)
if err != nil {
return "", fmt.Errorf("run: %w", err)
}
diff --git a/internal/git2go/apply_test.go b/internal/git2go/apply_test.go
index 2888dab8b..af99a58e9 100644
--- a/internal/git2go/apply_test.go
+++ b/internal/git2go/apply_test.go
@@ -41,7 +41,7 @@ func TestExecutor_Apply(t *testing.T) {
author := NewSignature("Test Author", "test.author@example.com", time.Now())
committer := NewSignature("Test Committer", "test.committer@example.com", time.Now())
- parentCommitSHA, err := executor.Commit(ctx, repo, CommitParams{
+ parentCommitSHA, err := executor.Commit(ctx, repo, CommitCommand{
Repository: repoPath,
Author: author,
Committer: committer,
@@ -50,7 +50,7 @@ func TestExecutor_Apply(t *testing.T) {
})
require.NoError(t, err)
- noCommonAncestor, err := executor.Commit(ctx, repo, CommitParams{
+ noCommonAncestor, err := executor.Commit(ctx, repo, CommitCommand{
Repository: repoPath,
Author: author,
Committer: committer,
@@ -59,7 +59,7 @@ func TestExecutor_Apply(t *testing.T) {
})
require.NoError(t, err)
- updateToA, err := executor.Commit(ctx, repo, CommitParams{
+ updateToA, err := executor.Commit(ctx, repo, CommitCommand{
Repository: repoPath,
Author: author,
Committer: committer,
@@ -69,7 +69,7 @@ func TestExecutor_Apply(t *testing.T) {
})
require.NoError(t, err)
- updateToB, err := executor.Commit(ctx, repo, CommitParams{
+ updateToB, err := executor.Commit(ctx, repo, CommitCommand{
Repository: repoPath,
Author: author,
Committer: committer,
@@ -79,7 +79,7 @@ func TestExecutor_Apply(t *testing.T) {
})
require.NoError(t, err)
- updateFromAToB, err := executor.Commit(ctx, repo, CommitParams{
+ updateFromAToB, err := executor.Commit(ctx, repo, CommitCommand{
Repository: repoPath,
Author: author,
Committer: committer,
@@ -89,7 +89,7 @@ func TestExecutor_Apply(t *testing.T) {
})
require.NoError(t, err)
- otherFile, err := executor.Commit(ctx, repo, CommitParams{
+ otherFile, err := executor.Commit(ctx, repo, CommitCommand{
Repository: repoPath,
Author: author,
Committer: committer,
@@ -214,7 +214,7 @@ func TestExecutor_Apply(t *testing.T) {
Author: author,
Committer: committer,
Message: tc.patches[len(tc.patches)-1].Message,
- }, getCommit(t, ctx, repo, commitID))
+ }, getCommit(t, ctx, repo, commitID, false))
gittest.RequireTree(t, cfg, repoPath, commitID.String(), tc.tree)
})
}
diff --git a/internal/git2go/cherry_pick.go b/internal/git2go/cherry_pick.go
index 6724d082a..7e954f50e 100644
--- a/internal/git2go/cherry_pick.go
+++ b/internal/git2go/cherry_pick.go
@@ -8,9 +8,9 @@ import (
"gitlab.com/gitlab-org/gitaly/v15/internal/git/repository"
)
-// CherryPickCommand contains parameters to perform a cherry pick.
+// CherryPickCommand contains parameters to perform a cherry-pick.
type CherryPickCommand struct {
- // Repository is the path where to execute the cherry pick.
+ // Repository is the path where to execute the cherry-pick.
Repository string
// CommitterName is the committer name for the resulting commit.
CommitterName string
@@ -26,9 +26,13 @@ type CherryPickCommand struct {
Commit string
// Mainline is the parent to be considered the mainline
Mainline uint
+ // SigningKey is a path to the key to sign commit using OpenPGP
+ SigningKey string
}
-// CherryPick performs a cherry pick via gitaly-git2go.
+// CherryPick performs a cherry-pick via gitaly-git2go.
func (b *Executor) CherryPick(ctx context.Context, repo repository.GitRepo, m CherryPickCommand) (git.ObjectID, error) {
+ m.SigningKey = b.signingKey
+
return b.runWithGob(ctx, repo, "cherry-pick", m)
}
diff --git a/internal/git2go/commit.go b/internal/git2go/commit.go
index 13edb4faa..7197e5a37 100644
--- a/internal/git2go/commit.go
+++ b/internal/git2go/commit.go
@@ -1,9 +1,7 @@
package git2go
import (
- "bytes"
"context"
- "encoding/gob"
"fmt"
"gitlab.com/gitlab-org/gitaly/v15/internal/git"
@@ -42,8 +40,8 @@ func (err DirectoryExistsError) Error() string {
return fmt.Sprintf("directory exists: %q", string(err))
}
-// CommitParams contains the information and the steps to build a commit.
-type CommitParams struct {
+// CommitCommand contains the information and the steps to build a commit.
+type CommitCommand struct {
// Repository is the path of the repository to operate on.
Repository string
// Author is the author of the commit.
@@ -56,34 +54,14 @@ type CommitParams struct {
Parent string
// Actions are the steps to build the commit.
Actions []Action
+ // SigningKey is a path to the key to sign commit using OpenPGP
+ SigningKey string
}
// Commit builds a commit from the actions, writes it to the object database and
// returns its object id.
-func (b *Executor) Commit(ctx context.Context, repo repository.GitRepo, params CommitParams) (git.ObjectID, error) {
- input := &bytes.Buffer{}
- if err := gob.NewEncoder(input).Encode(params); err != nil {
- return "", err
- }
+func (b *Executor) Commit(ctx context.Context, repo repository.GitRepo, c CommitCommand) (git.ObjectID, error) {
+ c.SigningKey = b.signingKey
- output, err := b.run(ctx, repo, input, "commit")
- if err != nil {
- return "", err
- }
-
- var result Result
- if err := gob.NewDecoder(output).Decode(&result); err != nil {
- return "", err
- }
-
- if result.Err != nil {
- return "", result.Err
- }
-
- commitID, err := git.ObjectHashSHA1.FromHex(result.CommitID)
- if err != nil {
- return "", fmt.Errorf("could not parse commit ID: %w", err)
- }
-
- return commitID, nil
+ return b.runWithGob(ctx, repo, "commit", c)
}
diff --git a/internal/git2go/commit_test.go b/internal/git2go/commit_test.go
index 0fef58310..48deee815 100644
--- a/internal/git2go/commit_test.go
+++ b/internal/git2go/commit_test.go
@@ -7,11 +7,14 @@ import (
"context"
"errors"
"fmt"
+ "os"
"strconv"
"strings"
"testing"
"time"
+ "github.com/ProtonMail/go-crypto/openpgp"
+ "github.com/ProtonMail/go-crypto/openpgp/packet"
"github.com/stretchr/testify/require"
"gitlab.com/gitlab-org/gitaly/v15/internal/git"
"gitlab.com/gitlab-org/gitaly/v15/internal/git/gittest"
@@ -63,8 +66,9 @@ func TestExecutor_Commit(t *testing.T) {
executor := NewExecutor(cfg, gittest.NewCommandFactory(t, cfg), config.NewLocator(cfg))
for _, tc := range []struct {
- desc string
- steps []step
+ desc string
+ steps []step
+ signAndVerify bool
}{
{
desc: "create directory",
@@ -455,14 +459,34 @@ func TestExecutor_Commit(t *testing.T) {
},
},
},
+ {
+ desc: "update created file, sign commit and verify signature",
+ steps: []step{
+ {
+ actions: []Action{
+ CreateFile{Path: "file", OID: originalFile.String()},
+ UpdateFile{Path: "file", OID: updatedFile.String()},
+ },
+ treeEntries: []gittest.TreeEntry{
+ {Mode: DefaultMode, Path: "file", Content: "updated"},
+ },
+ },
+ },
+ signAndVerify: true,
+ },
} {
t.Run(tc.desc, func(t *testing.T) {
author := NewSignature("Author Name", "author.email@example.com", time.Now())
committer := NewSignature("Committer Name", "committer.email@example.com", time.Now())
+
+ if tc.signAndVerify {
+ executor.signingKey = testhelper.SigningKeyPath
+ }
+
var parentCommit git.ObjectID
for i, step := range tc.steps {
message := fmt.Sprintf("commit %d", i+1)
- commitID, err := executor.Commit(ctx, repo, CommitParams{
+ commitID, err := executor.Commit(ctx, repo, CommitCommand{
Repository: repoPath,
Author: author,
Committer: committer,
@@ -483,7 +507,7 @@ func TestExecutor_Commit(t *testing.T) {
Author: author,
Committer: committer,
Message: message,
- }, getCommit(t, ctx, repo, commitID))
+ }, getCommit(t, ctx, repo, commitID, tc.signAndVerify))
gittest.RequireTree(t, cfg, repoPath, commitID.String(), step.treeEntries)
parentCommit = commitID
@@ -492,24 +516,40 @@ func TestExecutor_Commit(t *testing.T) {
}
}
-func getCommit(tb testing.TB, ctx context.Context, repo *localrepo.Repo, oid git.ObjectID) commit {
+func getCommit(tb testing.TB, ctx context.Context, repo *localrepo.Repo, oid git.ObjectID, verifySignature bool) commit {
tb.Helper()
data, err := repo.ReadObject(ctx, oid)
require.NoError(tb, err)
+ var (
+ gpgsig, dataWithoutGpgSig string
+ gpgsigStarted bool
+ )
+
var commit commit
lines := strings.Split(string(data), "\n")
for i, line := range lines {
if line == "" {
commit.Message = strings.Join(lines[i+1:], "\n")
+ dataWithoutGpgSig += "\n" + commit.Message
break
}
+ if gpgsigStarted && strings.HasPrefix(line, " ") {
+ gpgsig += strings.TrimSpace(line) + "\n"
+ continue
+ }
+
split := strings.SplitN(line, " ", 2)
require.Len(tb, split, 2, "invalid commit: %q", data)
field, value := split[0], split[1]
+
+ if field != "gpgsig" {
+ dataWithoutGpgSig += line + "\n"
+ }
+
switch field {
case "parent":
require.Empty(tb, commit.Parent, "multi parent parsing not implemented")
@@ -520,10 +560,29 @@ func getCommit(tb testing.TB, ctx context.Context, repo *localrepo.Repo, oid git
case "committer":
require.Empty(tb, commit.Committer, "commit contained multiple committers")
commit.Committer = unmarshalSignature(tb, value)
+ case "gpgsig":
+ gpgsig = value + "\n"
+ gpgsigStarted = true
default:
}
}
+ if gpgsig != "" || verifySignature {
+ file, err := os.Open("testdata/publicKey.gpg")
+ require.NoError(tb, err)
+
+ keyring, err := openpgp.ReadKeyRing(file)
+ require.NoError(tb, err)
+
+ _, err = openpgp.CheckArmoredDetachedSignature(
+ keyring,
+ strings.NewReader(dataWithoutGpgSig),
+ strings.NewReader(gpgsig),
+ &packet.Config{},
+ )
+ require.NoError(tb, err)
+ }
+
return commit
}
diff --git a/internal/git2go/executor.go b/internal/git2go/executor.go
index 30c8ff0a3..b97a9f3b0 100644
--- a/internal/git2go/executor.go
+++ b/internal/git2go/executor.go
@@ -31,6 +31,7 @@ var (
// Executor executes gitaly-git2go.
type Executor struct {
binaryPath string
+ signingKey string
gitCmdFactory git.CommandFactory
locator storage.Locator
logFormat, logLevel string
@@ -41,6 +42,7 @@ type Executor struct {
func NewExecutor(cfg config.Cfg, gitCmdFactory git.CommandFactory, locator storage.Locator) *Executor {
return &Executor{
binaryPath: cfg.BinaryPath(BinaryName),
+ signingKey: cfg.Git.SigningKey,
gitCmdFactory: gitCmdFactory,
locator: locator,
logFormat: cfg.Logging.Format,
diff --git a/internal/git2go/merge.go b/internal/git2go/merge.go
index 7086cfa76..8669a18eb 100644
--- a/internal/git2go/merge.go
+++ b/internal/git2go/merge.go
@@ -47,6 +47,8 @@ type MergeCommand struct {
// If set to `true`, then the resulting commit will have `Ours` as its only parent.
// Otherwise, a merge commit will be created with `Ours` and `Theirs` as its parents.
Squash bool
+ // SigningKey is a path to the key to sign commit using OpenPGP
+ SigningKey string
}
// MergeResult contains results from a merge.
@@ -60,6 +62,7 @@ func (b *Executor) Merge(ctx context.Context, repo repository.GitRepo, m MergeCo
if err := m.verify(); err != nil {
return MergeResult{}, fmt.Errorf("merge: %w: %s", ErrInvalidArgument, err.Error())
}
+ m.SigningKey = b.signingKey
commitID, err := b.runWithGob(ctx, repo, "merge", m)
if err != nil {
diff --git a/internal/git2go/rebase.go b/internal/git2go/rebase.go
index 9cd4ec814..8b041dadb 100644
--- a/internal/git2go/rebase.go
+++ b/internal/git2go/rebase.go
@@ -30,9 +30,13 @@ type RebaseCommand struct {
// and which are thus empty to be skipped. If unset, empty commits will cause the rebase to
// fail.
SkipEmptyCommits bool
+ // SigningKey is a path to the key to sign commit using OpenPGP
+ SigningKey string
}
// Rebase performs the rebase via gitaly-git2go
func (b *Executor) Rebase(ctx context.Context, repo repository.GitRepo, r RebaseCommand) (git.ObjectID, error) {
+ r.SigningKey = b.signingKey
+
return b.runWithGob(ctx, repo, "rebase", r)
}
diff --git a/internal/git2go/resolve_conflicts.go b/internal/git2go/resolve_conflicts.go
index 69d8ed14f..cd1ad0d03 100644
--- a/internal/git2go/resolve_conflicts.go
+++ b/internal/git2go/resolve_conflicts.go
@@ -28,6 +28,8 @@ type ResolveResult struct {
// Resolve will attempt merging and resolving conflicts for the provided request
func (b *Executor) Resolve(ctx context.Context, repo repository.GitRepo, r ResolveCommand) (ResolveResult, error) {
+ r.SigningKey = b.signingKey
+
if err := r.verify(); err != nil {
return ResolveResult{}, fmt.Errorf("resolve: %w: %s", ErrInvalidArgument, err.Error())
}
diff --git a/internal/git2go/revert.go b/internal/git2go/revert.go
index 7442e1566..4c42e706d 100644
--- a/internal/git2go/revert.go
+++ b/internal/git2go/revert.go
@@ -26,9 +26,13 @@ type RevertCommand struct {
Revert string
// Mainline is the parent to be considered the mainline
Mainline uint
+ // SigningKey is a path to the key to sign commit using OpenPGP
+ SigningKey string
}
// Revert reverts a commit via gitaly-git2go.
func (b *Executor) Revert(ctx context.Context, repo repository.GitRepo, r RevertCommand) (git.ObjectID, error) {
+ r.SigningKey = b.signingKey
+
return b.runWithGob(ctx, repo, "revert", r)
}
diff --git a/internal/git2go/submodule.go b/internal/git2go/submodule.go
index e420ad5c5..ce4aad37f 100644
--- a/internal/git2go/submodule.go
+++ b/internal/git2go/submodule.go
@@ -37,6 +37,9 @@ type SubmoduleCommand struct {
Submodule string
// Branch where to commit submodule update
Branch string
+
+ // SigningKey is a path to the key to sign commit using OpenPGP
+ SigningKey string
}
// SubmoduleResult contains results from a committing a submodule update
@@ -47,6 +50,8 @@ type SubmoduleResult struct {
// Submodule attempts to commit the request submodule change
func (b *Executor) Submodule(ctx context.Context, repo repository.GitRepo, s SubmoduleCommand) (SubmoduleResult, error) {
+ s.SigningKey = b.signingKey
+
if err := s.verify(); err != nil {
return SubmoduleResult{}, fmt.Errorf("submodule: %w", err)
}
@@ -58,7 +63,7 @@ func (b *Executor) Submodule(ctx context.Context, repo repository.GitRepo, s Sub
}
// Ideally we would use `b.runWithGob` here to avoid the gob encoding
- // boilerplate but it is not possible here because `runWithGob` adds error
+ // boilerplate, but it is not possible here because `runWithGob` adds error
// prefixes and the `LegacyErrPrefix*` errors must match exactly.
stdout, err := b.run(ctx, repo, input, cmd)
if err != nil {
diff --git a/internal/git2go/testdata/publicKey.gpg b/internal/git2go/testdata/publicKey.gpg
new file mode 100644
index 000000000..98d02a71e
--- /dev/null
+++ b/internal/git2go/testdata/publicKey.gpg
Binary files differ
diff --git a/internal/git2go/testdata/signingKey.gpg b/internal/git2go/testdata/signingKey.gpg
new file mode 100644
index 000000000..841fbc76a
--- /dev/null
+++ b/internal/git2go/testdata/signingKey.gpg
Binary files differ
diff --git a/internal/gitaly/config/config.go b/internal/gitaly/config/config.go
index 4b816b752..f1aebe07d 100644
--- a/internal/gitaly/config/config.go
+++ b/internal/gitaly/config/config.go
@@ -108,6 +108,7 @@ type Git struct {
CatfileCacheSize int `toml:"catfile_cache_size"`
Config []GitConfig `toml:"config"`
IgnoreGitconfig bool `toml:"ignore_gitconfig"`
+ SigningKey string `toml:"signing_key"`
}
// GitConfig contains a key-value pair which is to be passed to git as configuration.
@@ -174,7 +175,7 @@ type StreamCacheConfig struct {
}
// Load initializes the Config variable from file and the environment.
-// Environment variables take precedence over the file.
+// Environment variables take precedence over the file.
func Load(file io.Reader) (Cfg, error) {
cfg := Cfg{
Prometheus: prometheus.DefaultConfig(),
diff --git a/internal/gitaly/service/operations/apply_patch_test.go b/internal/gitaly/service/operations/apply_patch_test.go
index 285977485..e1a38ab82 100644
--- a/internal/gitaly/service/operations/apply_patch_test.go
+++ b/internal/gitaly/service/operations/apply_patch_test.go
@@ -293,7 +293,7 @@ To restore the original branch and stop patching, run "git am --abort".
var baseCommit git.ObjectID
for _, action := range tc.baseCommit {
var err error
- baseCommit, err = executor.Commit(ctx, rewrittenRepo, git2go.CommitParams{
+ baseCommit, err = executor.Commit(ctx, rewrittenRepo, git2go.CommitCommand{
Repository: repoPath,
Author: author,
Committer: committer,
@@ -309,7 +309,7 @@ To restore the original branch and stop patching, run "git am --abort".
}
if tc.extraBranches != nil {
- emptyCommit, err := executor.Commit(ctx, rewrittenRepo, git2go.CommitParams{
+ emptyCommit, err := executor.Commit(ctx, rewrittenRepo, git2go.CommitCommand{
Repository: repoPath,
Author: author,
Committer: committer,
@@ -329,7 +329,7 @@ To restore the original branch and stop patching, run "git am --abort".
commit := baseCommit
for _, action := range commitActions {
var err error
- commit, err = executor.Commit(ctx, rewrittenRepo, git2go.CommitParams{
+ commit, err = executor.Commit(ctx, rewrittenRepo, git2go.CommitCommand{
Repository: repoPath,
Author: author,
Committer: committer,
diff --git a/internal/gitaly/service/operations/commit_files.go b/internal/gitaly/service/operations/commit_files.go
index 16df353e4..0637ee021 100644
--- a/internal/gitaly/service/operations/commit_files.go
+++ b/internal/gitaly/service/operations/commit_files.go
@@ -291,7 +291,7 @@ func (s *Server) userCommitFiles(ctx context.Context, header *gitalypb.UserCommi
author = git2go.NewSignature(string(header.CommitAuthorName), string(header.CommitAuthorEmail), now)
}
- commitID, err := s.git2goExecutor.Commit(ctx, quarantineRepo, git2go.CommitParams{
+ commitID, err := s.git2goExecutor.Commit(ctx, quarantineRepo, git2go.CommitCommand{
Repository: repoPath,
Author: author,
Committer: committer,
diff --git a/internal/gitaly/service/remote/update_remote_mirror_test.go b/internal/gitaly/service/remote/update_remote_mirror_test.go
index 8b74dd858..18f071787 100644
--- a/internal/gitaly/service/remote/update_remote_mirror_test.go
+++ b/internal/gitaly/service/remote/update_remote_mirror_test.go
@@ -560,7 +560,7 @@ func TestUpdateRemoteMirror(t *testing.T) {
for _, commit := range commits {
var err error
commitOID, err = executor.Commit(ctx, gittest.RewrittenRepository(ctx, t, cfg, c.repoProto),
- git2go.CommitParams{
+ git2go.CommitCommand{
Repository: c.repoPath,
Author: commitSignature,
Committer: commitSignature,
diff --git a/internal/testhelper/testhelper.go b/internal/testhelper/testhelper.go
index f1db352f5..2599eed26 100644
--- a/internal/testhelper/testhelper.go
+++ b/internal/testhelper/testhelper.go
@@ -34,6 +34,8 @@ const (
RepositoryAuthToken = "the-secret-token"
// DefaultStorageName is the default name of the Gitaly storage.
DefaultStorageName = "default"
+ // SigningKeyPath is the default path to test commit signing.
+ SigningKeyPath = "testdata/signingKey.gpg"
)
// IsPraefectEnabled returns whether this testing run is done with Praefect in front of the Gitaly.